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([