19
19
using System ;
20
20
using System . Collections . Generic ;
21
21
using System . Globalization ;
22
+ using System . IO ;
22
23
using System . Threading . Tasks ;
24
+ using Newtonsoft . Json ;
23
25
using OpenQA . Selenium . DevTools ;
26
+ using OpenQA . Selenium . Internal ;
24
27
25
28
namespace OpenQA . Selenium
26
29
{
@@ -29,6 +32,8 @@ namespace OpenQA.Selenium
29
32
/// </summary>
30
33
public class JavaScriptEngine : IJavaScriptEngine
31
34
{
35
+ private readonly string MonitorBindingName = "__webdriver_attribute" ;
36
+
32
37
private IWebDriver driver ;
33
38
private Lazy < DevToolsSession > session ;
34
39
private Dictionary < string , InitializationScript > initializationScripts = new Dictionary < string , InitializationScript > ( ) ;
@@ -73,6 +78,11 @@ public JavaScriptEngine(IWebDriver driver)
73
78
/// </summary>
74
79
public event EventHandler < JavaScriptConsoleApiCalledEventArgs > JavaScriptConsoleApiCalled ;
75
80
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
+
76
86
/// <summary>
77
87
/// Gets the read-only list of initialization scripts added for this JavaScript engine.
78
88
/// </summary>
@@ -119,6 +129,30 @@ public void StopEventMonitoring()
119
129
this . session . Value . Domains . JavaScript . BindingCalled -= OnScriptBindingCalled ;
120
130
}
121
131
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
+
122
156
/// <summary>
123
157
/// Asynchronously adds JavaScript to be loaded on every document load.
124
158
/// </summary>
@@ -273,7 +307,7 @@ public async Task ClearAll()
273
307
/// <returns>A task that represents the asynchronous operation.</returns>
274
308
public async Task Reset ( )
275
309
{
276
- StopEventMonitoring ( ) ;
310
+ this . StopEventMonitoring ( ) ;
277
311
await ClearAll ( ) ;
278
312
}
279
313
@@ -298,8 +332,34 @@ private async Task EnableDomains()
298
332
}
299
333
}
300
334
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
+
301
349
private void OnScriptBindingCalled ( object sender , BindingCalledEventArgs e )
302
350
{
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
+
303
363
if ( this . JavaScriptCallbackExecuted != null )
304
364
{
305
365
this . JavaScriptCallbackExecuted ( this , new JavaScriptCallbackExecutedEventArgs ( )
0 commit comments