Skip to content

Commit 7f29755

Browse files
committed
[dotnet] Add ability to monitor DOM mutations
1 parent 11934cf commit 7f29755

File tree

7 files changed

+208
-1
lines changed

7 files changed

+208
-1
lines changed

dotnet/src/webdriver/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ generated_assembly_info(
5656
"//javascript/atoms/fragments:find-elements.js",
5757
"//javascript/atoms/fragments:is-displayed.js",
5858
"//javascript/webdriver/atoms:get-attribute.js",
59+
"//javascript/cdp-support:mutation-listener.js",
5960
"//third_party/js/selenium:webdriver_json",
6061
],
6162
target_frameworks = [
@@ -101,6 +102,7 @@ generated_assembly_info(
101102
"//javascript/atoms/fragments:find-elements.js",
102103
"//javascript/atoms/fragments:is-displayed.js",
103104
"//javascript/webdriver/atoms:get-attribute.js",
105+
"//javascript/cdp-support:mutation-listener.js",
104106
"//third_party/js/selenium:webdriver_json",
105107
],
106108
target_frameworks = [
@@ -138,6 +140,7 @@ generated_assembly_info(
138140
"//javascript/atoms/fragments:find-elements.js",
139141
"//javascript/atoms/fragments:is-displayed.js",
140142
"//javascript/webdriver/atoms:get-attribute.js",
143+
"//javascript/cdp-support:mutation-listener.js",
141144
"//third_party/js/selenium:webdriver_json",
142145
],
143146
target_frameworks = [
@@ -184,6 +187,7 @@ generated_assembly_info(
184187
"//javascript/atoms/fragments:find-elements.js",
185188
"//javascript/atoms/fragments:is-displayed.js",
186189
"//javascript/webdriver/atoms:get-attribute.js",
190+
"//javascript/cdp-support:mutation-listener.js",
187191
"//third_party/js/selenium:webdriver_json",
188192
],
189193
target_frameworks = [
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// <copyright file="DomMutatedEventArgs.cs" company="WebDriver Committers">
2+
// Licensed to the Software Freedom Conservancy (SFC) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The SFC licenses this file
6+
// to you under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
// </copyright>
18+
19+
using System;
20+
21+
namespace OpenQA.Selenium
22+
{
23+
/// <summary>
24+
/// Provides data for the AttributeValueChanged event
25+
/// </summary>
26+
public class DomMutatedEventArgs : EventArgs
27+
{
28+
private DomMutationData attributeData;
29+
30+
/// <summary>
31+
/// Gets the data about the attribute being changed.
32+
/// </summary>
33+
public DomMutationData AttributeData
34+
{
35+
get { return this.attributeData; }
36+
internal set { this.attributeData = value; }
37+
}
38+
}
39+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// <copyright file="DomMutationData.cs" company="WebDriver Committers">
2+
// Licensed to the Software Freedom Conservancy (SFC) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The SFC licenses this file
6+
// to you under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
// </copyright>
18+
19+
using Newtonsoft.Json;
20+
21+
namespace OpenQA.Selenium
22+
{
23+
/// <summary>
24+
/// Provides data about the changes in the value of an attribute on an element.
25+
/// </summary>
26+
public class DomMutationData
27+
{
28+
private string targetId;
29+
private string attributeName;
30+
private string attributeValue;
31+
private string attributeOriginalValue;
32+
33+
/// <summary>
34+
/// Gets the ID of the element whose value is changing.
35+
/// </summary>
36+
[JsonProperty(PropertyName = "target")]
37+
public string TargetId
38+
{
39+
get { return this.targetId; }
40+
internal set { this.targetId = value; }
41+
}
42+
43+
/// <summary>
44+
/// Gets the name of the attribute that is changing.
45+
/// </summary>
46+
[JsonProperty(PropertyName = "name")]
47+
public string AttributeName
48+
{
49+
get { return this.attributeName; }
50+
internal set { this.attributeName = value; }
51+
}
52+
53+
/// <summary>
54+
/// Gets the value to which the attribute is being changed.
55+
/// </summary>
56+
[JsonProperty(PropertyName = "value")]
57+
public string AttributeValue
58+
{
59+
get { return this.attributeValue; }
60+
internal set { this.attributeValue = value; }
61+
}
62+
63+
/// <summary>
64+
/// Gets the value from which the attribute has been changed.
65+
/// </summary>
66+
[JsonProperty(PropertyName = "oldValue")]
67+
public string AttributeOriginalValue
68+
{
69+
get { return this.attributeOriginalValue; }
70+
internal set { this.attributeOriginalValue = value; }
71+
}
72+
73+
/// <summary>
74+
/// Returns a string that represents the current object.
75+
/// </summary>
76+
/// <returns>A string that represents the current object.</returns>
77+
public override string ToString()
78+
{
79+
return string.Format("target: {0}, name: {1}, value: {2}, originalValue: {3}", this.targetId, this.attributeName, this.attributeValue, this.attributeOriginalValue);
80+
}
81+
}
82+
}

dotnet/src/webdriver/IJavaScriptEngine.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public interface IJavaScriptEngine
4242
/// </summary>
4343
event EventHandler<JavaScriptConsoleApiCalledEventArgs> JavaScriptConsoleApiCalled;
4444

45+
/// <summary>
46+
/// Occurs when a value of an attribute in an element is being changed.
47+
/// </summary>
48+
event EventHandler<DomMutatedEventArgs> DomMutated;
49+
4550
/// <summary>
4651
/// Gets the read-only list of initialization scripts added for this JavaScript engine.
4752
/// </summary>
@@ -63,6 +68,18 @@ public interface IJavaScriptEngine
6368
/// </summary>
6469
void StopEventMonitoring();
6570

71+
/// <summary>
72+
/// Enables monitoring for DOM changes.
73+
/// </summary>
74+
/// <returns>A task that represents the asynchronous operation.</returns>
75+
Task EnableDomMutationMonitoring();
76+
77+
/// <summary>
78+
/// Disables monitoring for DOM changes.
79+
/// </summary>
80+
/// <returns>A task that represents the asynchronous operation.</returns>
81+
Task DisableDomMutationMonitoring();
82+
6683
/// <summary>
6784
/// Asynchronously adds JavaScript to be loaded on every document load.
6885
/// </summary>

dotnet/src/webdriver/JavaScriptEngine.cs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
using System;
2020
using System.Collections.Generic;
2121
using System.Globalization;
22+
using System.IO;
2223
using System.Threading.Tasks;
24+
using Newtonsoft.Json;
2325
using OpenQA.Selenium.DevTools;
26+
using OpenQA.Selenium.Internal;
2427

2528
namespace OpenQA.Selenium
2629
{
@@ -29,6 +32,8 @@ namespace OpenQA.Selenium
2932
/// </summary>
3033
public class JavaScriptEngine : IJavaScriptEngine
3134
{
35+
private readonly string MonitorBindingName = "__webdriver_attribute";
36+
3237
private IWebDriver driver;
3338
private Lazy<DevToolsSession> session;
3439
private Dictionary<string, InitializationScript> initializationScripts = new Dictionary<string, InitializationScript>();
@@ -73,6 +78,11 @@ public JavaScriptEngine(IWebDriver driver)
7378
/// </summary>
7479
public event EventHandler<JavaScriptConsoleApiCalledEventArgs> JavaScriptConsoleApiCalled;
7580

81+
/// <summary>
82+
/// Occurs when a value of an attribute in an element is being changed.
83+
/// </summary>
84+
public event EventHandler<DomMutatedEventArgs> DomMutated;
85+
7686
/// <summary>
7787
/// Gets the read-only list of initialization scripts added for this JavaScript engine.
7888
/// </summary>
@@ -119,6 +129,30 @@ public void StopEventMonitoring()
119129
this.session.Value.Domains.JavaScript.BindingCalled -= OnScriptBindingCalled;
120130
}
121131

132+
/// <summary>
133+
/// Enables monitoring for DOM changes.
134+
/// </summary>
135+
/// <returns>A task that represents the asynchronous operation.</returns>
136+
public async Task EnableDomMutationMonitoring()
137+
{
138+
// Execute the script to have it enabled on the currently loaded page.
139+
string script = GetMutationListenerScript();
140+
await this.session.Value.Domains.JavaScript.Evaluate(script);
141+
142+
await this.AddScriptCallbackBinding(MonitorBindingName);
143+
await this.AddInitializationScript(MonitorBindingName, script);
144+
}
145+
146+
/// <summary>
147+
/// Disables monitoring for DOM changes.
148+
/// </summary>
149+
/// <returns>A task that represents the asynchronous operation.</returns>
150+
public async Task DisableDomMutationMonitoring()
151+
{
152+
await this.RemoveScriptCallbackBinding(MonitorBindingName);
153+
await this.RemoveInitializationScript(MonitorBindingName);
154+
}
155+
122156
/// <summary>
123157
/// Asynchronously adds JavaScript to be loaded on every document load.
124158
/// </summary>
@@ -273,7 +307,7 @@ public async Task ClearAll()
273307
/// <returns>A task that represents the asynchronous operation.</returns>
274308
public async Task Reset()
275309
{
276-
StopEventMonitoring();
310+
this.StopEventMonitoring();
277311
await ClearAll();
278312
}
279313

@@ -298,8 +332,34 @@ private async Task EnableDomains()
298332
}
299333
}
300334

335+
private string GetMutationListenerScript()
336+
{
337+
string listenerScript = string.Empty;
338+
using (Stream resourceStream = ResourceUtilities.GetResourceStream("mutation-listener.js", "mutation-listener.js"))
339+
{
340+
using (StreamReader resourceReader = new StreamReader(resourceStream))
341+
{
342+
listenerScript = resourceReader.ReadToEnd();
343+
}
344+
}
345+
346+
return listenerScript;
347+
}
348+
301349
private void OnScriptBindingCalled(object sender, BindingCalledEventArgs e)
302350
{
351+
if (e.Name == MonitorBindingName)
352+
{
353+
DomMutationData valueChangeData = JsonConvert.DeserializeObject<DomMutationData>(e.Payload);
354+
if (this.DomMutated != null)
355+
{
356+
this.DomMutated(this, new DomMutatedEventArgs()
357+
{
358+
AttributeData = valueChangeData
359+
});
360+
}
361+
}
362+
303363
if (this.JavaScriptCallbackExecuted != null)
304364
{
305365
this.JavaScriptCallbackExecuted(this, new JavaScriptCallbackExecutedEventArgs()

dotnet/src/webdriver/WebDriver.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@
119119
<Visible>False</Visible>
120120
<LogicalName>find-elements.js</LogicalName>
121121
</EmbeddedResource>
122+
<EmbeddedResource Include="$(ProjectDir)..\..\..\bazel-bin\javascript\cdp-support\mutation-listener.js">
123+
<Visible>False</Visible>
124+
<LogicalName>mutation-listener.js</LogicalName>
125+
</EmbeddedResource>
122126
</ItemGroup>
123127

124128
<ItemGroup Condition="'$(OS)' != 'WINDOWS_NT'">

javascript/cdp-support/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package(default_visibility = [
22
"//java/src/org/openqa/selenium/devtools:__pkg__",
3+
"//dotnet/src/webdriver:__pkg__",
34
])
45

56
exports_files([

0 commit comments

Comments
 (0)