1919using System ;
2020using System . Collections . Generic ;
2121using System . Globalization ;
22+ using System . IO ;
2223using System . Threading . Tasks ;
24+ using Newtonsoft . Json ;
2325using OpenQA . Selenium . DevTools ;
26+ using OpenQA . Selenium . Internal ;
2427
2528namespace 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 ( )
0 commit comments