diff --git a/dotnet/src/webdriver/BUILD.bazel b/dotnet/src/webdriver/BUILD.bazel index 4d03a35edb387..93606169001e4 100644 --- a/dotnet/src/webdriver/BUILD.bazel +++ b/dotnet/src/webdriver/BUILD.bazel @@ -56,6 +56,7 @@ generated_assembly_info( "//javascript/atoms/fragments:find-elements.js", "//javascript/atoms/fragments:is-displayed.js", "//javascript/webdriver/atoms:get-attribute.js", + "//javascript/cdp-support:mutation-listener.js", "//third_party/js/selenium:webdriver_json", ], target_frameworks = [ @@ -101,6 +102,7 @@ generated_assembly_info( "//javascript/atoms/fragments:find-elements.js", "//javascript/atoms/fragments:is-displayed.js", "//javascript/webdriver/atoms:get-attribute.js", + "//javascript/cdp-support:mutation-listener.js", "//third_party/js/selenium:webdriver_json", ], target_frameworks = [ @@ -138,6 +140,7 @@ generated_assembly_info( "//javascript/atoms/fragments:find-elements.js", "//javascript/atoms/fragments:is-displayed.js", "//javascript/webdriver/atoms:get-attribute.js", + "//javascript/cdp-support:mutation-listener.js", "//third_party/js/selenium:webdriver_json", ], target_frameworks = [ @@ -184,6 +187,7 @@ generated_assembly_info( "//javascript/atoms/fragments:find-elements.js", "//javascript/atoms/fragments:is-displayed.js", "//javascript/webdriver/atoms:get-attribute.js", + "//javascript/cdp-support:mutation-listener.js", "//third_party/js/selenium:webdriver_json", ], target_frameworks = [ diff --git a/dotnet/src/webdriver/DomMutatedEventArgs.cs b/dotnet/src/webdriver/DomMutatedEventArgs.cs new file mode 100644 index 0000000000000..18270726183f2 --- /dev/null +++ b/dotnet/src/webdriver/DomMutatedEventArgs.cs @@ -0,0 +1,39 @@ +// +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; + +namespace OpenQA.Selenium +{ + /// + /// Provides data for the AttributeValueChanged event + /// + public class DomMutatedEventArgs : EventArgs + { + private DomMutationData attributeData; + + /// + /// Gets the data about the attribute being changed. + /// + public DomMutationData AttributeData + { + get { return this.attributeData; } + internal set { this.attributeData = value; } + } + } +} diff --git a/dotnet/src/webdriver/DomMutationData.cs b/dotnet/src/webdriver/DomMutationData.cs new file mode 100644 index 0000000000000..e12ebe0bc4e22 --- /dev/null +++ b/dotnet/src/webdriver/DomMutationData.cs @@ -0,0 +1,82 @@ +// +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using Newtonsoft.Json; + +namespace OpenQA.Selenium +{ + /// + /// Provides data about the changes in the value of an attribute on an element. + /// + public class DomMutationData + { + private string targetId; + private string attributeName; + private string attributeValue; + private string attributeOriginalValue; + + /// + /// Gets the ID of the element whose value is changing. + /// + [JsonProperty(PropertyName = "target")] + public string TargetId + { + get { return this.targetId; } + internal set { this.targetId = value; } + } + + /// + /// Gets the name of the attribute that is changing. + /// + [JsonProperty(PropertyName = "name")] + public string AttributeName + { + get { return this.attributeName; } + internal set { this.attributeName = value; } + } + + /// + /// Gets the value to which the attribute is being changed. + /// + [JsonProperty(PropertyName = "value")] + public string AttributeValue + { + get { return this.attributeValue; } + internal set { this.attributeValue = value; } + } + + /// + /// Gets the value from which the attribute has been changed. + /// + [JsonProperty(PropertyName = "oldValue")] + public string AttributeOriginalValue + { + get { return this.attributeOriginalValue; } + internal set { this.attributeOriginalValue = value; } + } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return string.Format("target: {0}, name: {1}, value: {2}, originalValue: {3}", this.targetId, this.attributeName, this.attributeValue, this.attributeOriginalValue); + } + } +} diff --git a/dotnet/src/webdriver/IJavaScriptEngine.cs b/dotnet/src/webdriver/IJavaScriptEngine.cs index 802bd89bdf0c8..d3582ae51c022 100644 --- a/dotnet/src/webdriver/IJavaScriptEngine.cs +++ b/dotnet/src/webdriver/IJavaScriptEngine.cs @@ -42,6 +42,11 @@ public interface IJavaScriptEngine /// event EventHandler JavaScriptConsoleApiCalled; + /// + /// Occurs when a value of an attribute in an element is being changed. + /// + event EventHandler DomMutated; + /// /// Gets the read-only list of initialization scripts added for this JavaScript engine. /// @@ -63,6 +68,18 @@ public interface IJavaScriptEngine /// void StopEventMonitoring(); + /// + /// Enables monitoring for DOM changes. + /// + /// A task that represents the asynchronous operation. + Task EnableDomMutationMonitoring(); + + /// + /// Disables monitoring for DOM changes. + /// + /// A task that represents the asynchronous operation. + Task DisableDomMutationMonitoring(); + /// /// Asynchronously adds JavaScript to be loaded on every document load. /// diff --git a/dotnet/src/webdriver/JavaScriptEngine.cs b/dotnet/src/webdriver/JavaScriptEngine.cs index b4bf6f47e4aac..b988ef0788048 100644 --- a/dotnet/src/webdriver/JavaScriptEngine.cs +++ b/dotnet/src/webdriver/JavaScriptEngine.cs @@ -19,8 +19,11 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Threading.Tasks; +using Newtonsoft.Json; using OpenQA.Selenium.DevTools; +using OpenQA.Selenium.Internal; namespace OpenQA.Selenium { @@ -29,6 +32,8 @@ namespace OpenQA.Selenium /// public class JavaScriptEngine : IJavaScriptEngine { + private readonly string MonitorBindingName = "__webdriver_attribute"; + private IWebDriver driver; private Lazy session; private Dictionary initializationScripts = new Dictionary(); @@ -73,6 +78,11 @@ public JavaScriptEngine(IWebDriver driver) /// public event EventHandler JavaScriptConsoleApiCalled; + /// + /// Occurs when a value of an attribute in an element is being changed. + /// + public event EventHandler DomMutated; + /// /// Gets the read-only list of initialization scripts added for this JavaScript engine. /// @@ -119,6 +129,30 @@ public void StopEventMonitoring() this.session.Value.Domains.JavaScript.BindingCalled -= OnScriptBindingCalled; } + /// + /// Enables monitoring for DOM changes. + /// + /// A task that represents the asynchronous operation. + public async Task EnableDomMutationMonitoring() + { + // Execute the script to have it enabled on the currently loaded page. + string script = GetMutationListenerScript(); + await this.session.Value.Domains.JavaScript.Evaluate(script); + + await this.AddScriptCallbackBinding(MonitorBindingName); + await this.AddInitializationScript(MonitorBindingName, script); + } + + /// + /// Disables monitoring for DOM changes. + /// + /// A task that represents the asynchronous operation. + public async Task DisableDomMutationMonitoring() + { + await this.RemoveScriptCallbackBinding(MonitorBindingName); + await this.RemoveInitializationScript(MonitorBindingName); + } + /// /// Asynchronously adds JavaScript to be loaded on every document load. /// @@ -273,7 +307,7 @@ public async Task ClearAll() /// A task that represents the asynchronous operation. public async Task Reset() { - StopEventMonitoring(); + this.StopEventMonitoring(); await ClearAll(); } @@ -298,8 +332,34 @@ private async Task EnableDomains() } } + private string GetMutationListenerScript() + { + string listenerScript = string.Empty; + using (Stream resourceStream = ResourceUtilities.GetResourceStream("mutation-listener.js", "mutation-listener.js")) + { + using (StreamReader resourceReader = new StreamReader(resourceStream)) + { + listenerScript = resourceReader.ReadToEnd(); + } + } + + return listenerScript; + } + private void OnScriptBindingCalled(object sender, BindingCalledEventArgs e) { + if (e.Name == MonitorBindingName) + { + DomMutationData valueChangeData = JsonConvert.DeserializeObject(e.Payload); + if (this.DomMutated != null) + { + this.DomMutated(this, new DomMutatedEventArgs() + { + AttributeData = valueChangeData + }); + } + } + if (this.JavaScriptCallbackExecuted != null) { this.JavaScriptCallbackExecuted(this, new JavaScriptCallbackExecutedEventArgs() diff --git a/dotnet/src/webdriver/WebDriver.csproj b/dotnet/src/webdriver/WebDriver.csproj index e19d104c62d60..7726e3863c51d 100644 --- a/dotnet/src/webdriver/WebDriver.csproj +++ b/dotnet/src/webdriver/WebDriver.csproj @@ -119,6 +119,10 @@ False find-elements.js + + False + mutation-listener.js + diff --git a/javascript/cdp-support/BUILD.bazel b/javascript/cdp-support/BUILD.bazel index 250719e8e5f82..b399564504d38 100644 --- a/javascript/cdp-support/BUILD.bazel +++ b/javascript/cdp-support/BUILD.bazel @@ -1,5 +1,6 @@ package(default_visibility = [ "//java/src/org/openqa/selenium/devtools:__pkg__", + "//dotnet/src/webdriver:__pkg__", ]) exports_files([