Skip to content

Commit

Permalink
[dotnet] Add ability to monitor DOM mutations
Browse files Browse the repository at this point in the history
  • Loading branch information
jimevans committed Oct 12, 2021
1 parent 11934cf commit 7f29755
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 1 deletion.
4 changes: 4 additions & 0 deletions dotnet/src/webdriver/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 = [
Expand Down
39 changes: 39 additions & 0 deletions dotnet/src/webdriver/DomMutatedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// <copyright file="DomMutatedEventArgs.cs" company="WebDriver Committers">
// 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.
// </copyright>

using System;

namespace OpenQA.Selenium
{
/// <summary>
/// Provides data for the AttributeValueChanged event
/// </summary>
public class DomMutatedEventArgs : EventArgs
{
private DomMutationData attributeData;

/// <summary>
/// Gets the data about the attribute being changed.
/// </summary>
public DomMutationData AttributeData
{
get { return this.attributeData; }
internal set { this.attributeData = value; }
}
}
}
82 changes: 82 additions & 0 deletions dotnet/src/webdriver/DomMutationData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// <copyright file="DomMutationData.cs" company="WebDriver Committers">
// 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.
// </copyright>

using Newtonsoft.Json;

namespace OpenQA.Selenium
{
/// <summary>
/// Provides data about the changes in the value of an attribute on an element.
/// </summary>
public class DomMutationData
{
private string targetId;
private string attributeName;
private string attributeValue;
private string attributeOriginalValue;

/// <summary>
/// Gets the ID of the element whose value is changing.
/// </summary>
[JsonProperty(PropertyName = "target")]
public string TargetId
{
get { return this.targetId; }
internal set { this.targetId = value; }
}

/// <summary>
/// Gets the name of the attribute that is changing.
/// </summary>
[JsonProperty(PropertyName = "name")]
public string AttributeName
{
get { return this.attributeName; }
internal set { this.attributeName = value; }
}

/// <summary>
/// Gets the value to which the attribute is being changed.
/// </summary>
[JsonProperty(PropertyName = "value")]
public string AttributeValue
{
get { return this.attributeValue; }
internal set { this.attributeValue = value; }
}

/// <summary>
/// Gets the value from which the attribute has been changed.
/// </summary>
[JsonProperty(PropertyName = "oldValue")]
public string AttributeOriginalValue
{
get { return this.attributeOriginalValue; }
internal set { this.attributeOriginalValue = value; }
}

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>A string that represents the current object.</returns>
public override string ToString()
{
return string.Format("target: {0}, name: {1}, value: {2}, originalValue: {3}", this.targetId, this.attributeName, this.attributeValue, this.attributeOriginalValue);
}
}
}
17 changes: 17 additions & 0 deletions dotnet/src/webdriver/IJavaScriptEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public interface IJavaScriptEngine
/// </summary>
event EventHandler<JavaScriptConsoleApiCalledEventArgs> JavaScriptConsoleApiCalled;

/// <summary>
/// Occurs when a value of an attribute in an element is being changed.
/// </summary>
event EventHandler<DomMutatedEventArgs> DomMutated;

/// <summary>
/// Gets the read-only list of initialization scripts added for this JavaScript engine.
/// </summary>
Expand All @@ -63,6 +68,18 @@ public interface IJavaScriptEngine
/// </summary>
void StopEventMonitoring();

/// <summary>
/// Enables monitoring for DOM changes.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
Task EnableDomMutationMonitoring();

/// <summary>
/// Disables monitoring for DOM changes.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
Task DisableDomMutationMonitoring();

/// <summary>
/// Asynchronously adds JavaScript to be loaded on every document load.
/// </summary>
Expand Down
62 changes: 61 additions & 1 deletion dotnet/src/webdriver/JavaScriptEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -29,6 +32,8 @@ namespace OpenQA.Selenium
/// </summary>
public class JavaScriptEngine : IJavaScriptEngine
{
private readonly string MonitorBindingName = "__webdriver_attribute";

private IWebDriver driver;
private Lazy<DevToolsSession> session;
private Dictionary<string, InitializationScript> initializationScripts = new Dictionary<string, InitializationScript>();
Expand Down Expand Up @@ -73,6 +78,11 @@ public JavaScriptEngine(IWebDriver driver)
/// </summary>
public event EventHandler<JavaScriptConsoleApiCalledEventArgs> JavaScriptConsoleApiCalled;

/// <summary>
/// Occurs when a value of an attribute in an element is being changed.
/// </summary>
public event EventHandler<DomMutatedEventArgs> DomMutated;

/// <summary>
/// Gets the read-only list of initialization scripts added for this JavaScript engine.
/// </summary>
Expand Down Expand Up @@ -119,6 +129,30 @@ public void StopEventMonitoring()
this.session.Value.Domains.JavaScript.BindingCalled -= OnScriptBindingCalled;
}

/// <summary>
/// Enables monitoring for DOM changes.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
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);
}

/// <summary>
/// Disables monitoring for DOM changes.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task DisableDomMutationMonitoring()
{
await this.RemoveScriptCallbackBinding(MonitorBindingName);
await this.RemoveInitializationScript(MonitorBindingName);
}

/// <summary>
/// Asynchronously adds JavaScript to be loaded on every document load.
/// </summary>
Expand Down Expand Up @@ -273,7 +307,7 @@ public async Task ClearAll()
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task Reset()
{
StopEventMonitoring();
this.StopEventMonitoring();
await ClearAll();
}

Expand All @@ -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<DomMutationData>(e.Payload);
if (this.DomMutated != null)
{
this.DomMutated(this, new DomMutatedEventArgs()
{
AttributeData = valueChangeData
});
}
}

if (this.JavaScriptCallbackExecuted != null)
{
this.JavaScriptCallbackExecuted(this, new JavaScriptCallbackExecutedEventArgs()
Expand Down
4 changes: 4 additions & 0 deletions dotnet/src/webdriver/WebDriver.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@
<Visible>False</Visible>
<LogicalName>find-elements.js</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="$(ProjectDir)..\..\..\bazel-bin\javascript\cdp-support\mutation-listener.js">
<Visible>False</Visible>
<LogicalName>mutation-listener.js</LogicalName>
</EmbeddedResource>
</ItemGroup>

<ItemGroup Condition="'$(OS)' != 'WINDOWS_NT'">
Expand Down
1 change: 1 addition & 0 deletions javascript/cdp-support/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package(default_visibility = [
"//java/src/org/openqa/selenium/devtools:__pkg__",
"//dotnet/src/webdriver:__pkg__",
])

exports_files([
Expand Down

0 comments on commit 7f29755

Please sign in to comment.