Skip to content
Snippets Groups Projects
Unverified Commit 0569dede authored by Bjoern Esswein's avatar Bjoern Esswein
Browse files

implemented per dom node event handlers on the DomNodeWrapper class

parent 5aba7dc7
No related branches found
No related tags found
No related merge requests found
......@@ -30,14 +30,14 @@ namespace bessw.Unity.WebView.ChromeDevTools
this.devtools = new DevtoolsProtocolHandler(new DevtoolsWebsocket(pageTarget.WebSocketDebuggerUrl));
// register DOM event handlers
this.devtools.DOM_AttributeModifiedEventHandler += (attributeModifiedEvent) => DomNodeWrapper.onAttributeModified(this, attributeModifiedEvent);
this.devtools.DOM_AttributeRemovedEventHandler += (attributeRemovedEvent) => DomNodeWrapper.onAttributeRemoved(this, attributeRemovedEvent);
this.devtools.DOM_CharacterDataModifiedEventHandler += (characterDataModifiedEvent) => DomNodeWrapper.onCharacterDataModified(this, characterDataModifiedEvent);
this.devtools.DOM_ChildNodeCountUpdatedEventHandler += (childNodeCountUpdatedEvent) => DomNodeWrapper.onChildNodeCountUpdated(this, childNodeCountUpdatedEvent);
this.devtools.DOM_ChildNodeInsertedEventHandler += (childNodeInsertedEvent) => DomNodeWrapper.onChildNodeInserted(this, childNodeInsertedEvent);
this.devtools.DOM_ChildNodeRemovedEventHandler += (childNodeRemovedEvent) => DomNodeWrapper.onChildNodeRemoved(this, childNodeRemovedEvent);
this.devtools.DOM_DocumentUpdatedEventHandler += (documentUpdatedEvent) => DomNodeWrapper.onDocumentUpdated(this, documentUpdatedEvent);
this.devtools.DOM_SetChildNodesEventHandler += (setChildNodesEvent) => DomNodeWrapper.onSetChildNodes(this, setChildNodesEvent);
this.devtools.onDomAttributeModified += (attributeModifiedEvent) => DomNodeWrapper.AttributeModifiedEventHandler(this, attributeModifiedEvent);
this.devtools.onDomAttributeRemoved += (attributeRemovedEvent) => DomNodeWrapper.AttributeRemovedEventHandler(this, attributeRemovedEvent);
this.devtools.onDomCharacterDataModified += (characterDataModifiedEvent) => DomNodeWrapper.CharacterDataModifiedEventHandler(this, characterDataModifiedEvent);
this.devtools.onDomChildNodeCountUpdated += (childNodeCountUpdatedEvent) => DomNodeWrapper.ChildNodeCountUpdatedEventHandler(this, childNodeCountUpdatedEvent);
this.devtools.onDomChildNodeInserted += (childNodeInsertedEvent) => DomNodeWrapper.ChildNodeInsertedEventHandler(this, childNodeInsertedEvent);
this.devtools.onDomChildNodeRemoved += (childNodeRemovedEvent) => DomNodeWrapper.ChildNodeRemovedEventHandler(this, childNodeRemovedEvent);
this.devtools.onDomDocumentUpdated += (documentUpdatedEvent) => DomNodeWrapper.DocumentUpdatedEventHandler(this, documentUpdatedEvent);
this.devtools.onDomSetChildNodes += (setChildNodesEvent) => DomNodeWrapper.SetChildNodesEventHandler(this, setChildNodesEvent);
}
public IEnumerator Update()
......@@ -73,7 +73,7 @@ namespace bessw.Unity.WebView.ChromeDevTools
{
// register screencast frame event handler
// TODO: deregister on stop stream
devtools.screencastFrameEventHandler += (screencastFrameEvent frameEvent) =>
devtools.onScreencastFrame += (screencastFrameEvent frameEvent) =>
{
// send an ack for this frame
var frameAck = new screencastFrameAck
......
......@@ -23,18 +23,18 @@ namespace bessw.Unity.WebView.ChromeDevTools
private ConcurrentDictionary<long, ResponseTypeAndCallback> commandResponseDict = new ConcurrentDictionary<long, ResponseTypeAndCallback>();
// devtools events
public event Action<screencastFrameEvent> screencastFrameEventHandler;
public event Action<screencastVisibilityChangedEvent> screencastVisibilityChangedEventHandler;
public event Action<screencastFrameEvent> onScreencastFrame;
public event Action<screencastVisibilityChangedEvent> onScreencastVisibilityChanged;
#region DOM events
public event Action<attributeModifiedEvent> DOM_AttributeModifiedEventHandler;
public event Action<attributeRemovedEvent> DOM_AttributeRemovedEventHandler;
public event Action<characterDataModifiedEvent> DOM_CharacterDataModifiedEventHandler;
public event Action<childNodeCountUpdatedEvent> DOM_ChildNodeCountUpdatedEventHandler;
public event Action<childNodeInsertedEvent> DOM_ChildNodeInsertedEventHandler;
public event Action<childNodeRemovedEvent> DOM_ChildNodeRemovedEventHandler;
public event Action<documentUpdatedEvent> DOM_DocumentUpdatedEventHandler;
public event Action<setChildNodesEvent> DOM_SetChildNodesEventHandler;
public event Action<attributeModifiedEvent> onDomAttributeModified;
public event Action<attributeRemovedEvent> onDomAttributeRemoved;
public event Action<characterDataModifiedEvent> onDomCharacterDataModified;
public event Action<childNodeCountUpdatedEvent> onDomChildNodeCountUpdated;
public event Action<childNodeInsertedEvent> onDomChildNodeInserted;
public event Action<childNodeRemovedEvent> onDomChildNodeRemoved;
public event Action<documentUpdatedEvent> onDomDocumentUpdated;
public event Action<setChildNodesEvent> onDomSetChildNodes;
#endregion DOM events
public event Action<DevtoolsEventWrapper> unknownEventHandler;
......@@ -122,37 +122,37 @@ namespace bessw.Unity.WebView.ChromeDevTools
switch (ev.Method)
{
case "Page.screencastFrame":
screencastFrameEventHandler?.Invoke( ev.Params.ToObject<screencastFrameEvent>(Browser.devtoolsSerializer) );
onScreencastFrame?.Invoke( ev.Params.ToObject<screencastFrameEvent>(Browser.devtoolsSerializer) );
break;
case "Page.screencastVisibilityChanged":
screencastVisibilityChangedEventHandler?.Invoke( ev.Params.ToObject<screencastVisibilityChangedEvent>(Browser.devtoolsSerializer) );
onScreencastVisibilityChanged?.Invoke( ev.Params.ToObject<screencastVisibilityChangedEvent>(Browser.devtoolsSerializer) );
break;
// switch cases that invoke the event handlers for the DOM events
#region DOM events
case "DOM.attributeModified":
DOM_AttributeModifiedEventHandler?.Invoke( ev.Params.ToObject<attributeModifiedEvent>(Browser.devtoolsSerializer) );
onDomAttributeModified?.Invoke( ev.Params.ToObject<attributeModifiedEvent>(Browser.devtoolsSerializer) );
break;
case "DOM.attributeRemoved":
DOM_AttributeRemovedEventHandler?.Invoke( ev.Params.ToObject<attributeRemovedEvent>(Browser.devtoolsSerializer) );
onDomAttributeRemoved?.Invoke( ev.Params.ToObject<attributeRemovedEvent>(Browser.devtoolsSerializer) );
break;
case "DOM.characterDataModified":
DOM_CharacterDataModifiedEventHandler?.Invoke( ev.Params.ToObject<characterDataModifiedEvent>(Browser.devtoolsSerializer) );
onDomCharacterDataModified?.Invoke( ev.Params.ToObject<characterDataModifiedEvent>(Browser.devtoolsSerializer) );
break;
case "DOM.childNodeCountUpdated":
DOM_ChildNodeCountUpdatedEventHandler?.Invoke( ev.Params.ToObject<childNodeCountUpdatedEvent>(Browser.devtoolsSerializer) );
onDomChildNodeCountUpdated?.Invoke( ev.Params.ToObject<childNodeCountUpdatedEvent>(Browser.devtoolsSerializer) );
break;
case "DOM.childNodeInserted":
DOM_ChildNodeInsertedEventHandler?.Invoke( ev.Params.ToObject<childNodeInsertedEvent>(Browser.devtoolsSerializer) );
onDomChildNodeInserted?.Invoke( ev.Params.ToObject<childNodeInsertedEvent>(Browser.devtoolsSerializer) );
break;
case "DOM.childNodeRemoved":
DOM_ChildNodeRemovedEventHandler?.Invoke( ev.Params.ToObject<childNodeRemovedEvent>(Browser.devtoolsSerializer) );
onDomChildNodeRemoved?.Invoke( ev.Params.ToObject<childNodeRemovedEvent>(Browser.devtoolsSerializer) );
break;
case "DOM.documentUpdated":
DOM_DocumentUpdatedEventHandler?.Invoke( ev.Params.ToObject<documentUpdatedEvent>(Browser.devtoolsSerializer) );
onDomDocumentUpdated?.Invoke( ev.Params.ToObject<documentUpdatedEvent>(Browser.devtoolsSerializer) );
break;
case "DOM.setChildNodes":
DOM_SetChildNodesEventHandler?.Invoke( ev.Params.ToObject<setChildNodesEvent>(Browser.devtoolsSerializer) );
onDomSetChildNodes?.Invoke( ev.Params.ToObject<setChildNodesEvent>(Browser.devtoolsSerializer) );
break;
#endregion DOM events
......
......@@ -34,6 +34,14 @@ namespace bessw.Unity.WebView.ChromeDevTools
/// <remarks>May be null if it has not jet been requested from the browser</remarks>
public Node Node { get; protected set; }
public event Action<attributeModifiedEvent>? OnAttributeModified;
public event Action<attributeRemovedEvent>? OnDomAttributeRemoved;
public event Action<characterDataModifiedEvent>? OnDomCharacterDataModified;
public event Action<childNodeCountUpdatedEvent>? OnDomChildNodeCountUpdated;
public event Action<childNodeInsertedEvent>? OnDomChildNodeInserted;
public event Action<childNodeRemovedEvent>? OnDomChildNodeRemoved;
public event Action<setChildNodesEvent>? OnDomSetChildNodes;
/// <summary>
/// Reference to the browser tab that this dom node belongs to
......@@ -75,63 +83,68 @@ namespace bessw.Unity.WebView.ChromeDevTools
#region Event Handlers
public static void onAttributeModified(BrowserTab tab, attributeModifiedEvent eventData)
public static void AttributeModifiedEventHandler(BrowserTab tab, attributeModifiedEvent eventData)
{
var domNode = createOrUpdateNode(tab, eventData.nodeId);
if (domNode.Node != null)
{
domNode.Node.attributes[eventData.name] = eventData.value;
domNode.OnAttributeModified?.Invoke(eventData);
} else {
throw new InvalidOperationException("AttributeModified event was fired, the node info has not yet been recieved");
}
}
public static void onAttributeRemoved(BrowserTab tab, attributeRemovedEvent eventData)
public static void AttributeRemovedEventHandler(BrowserTab tab, attributeRemovedEvent eventData)
{
var domNode = createOrUpdateNode(tab, eventData.nodeId);
if (domNode.Node != null)
{
domNode.Node.attributes.Remove(eventData.name);
domNode.OnDomAttributeRemoved?.Invoke(eventData);
} else {
throw new InvalidOperationException("AttributeRemoved event was fired, the node info has not yet been recieved");
}
}
public static void onCharacterDataModified(BrowserTab tab, characterDataModifiedEvent eventData)
public static void CharacterDataModifiedEventHandler(BrowserTab tab, characterDataModifiedEvent eventData)
{
var domNode = createOrUpdateNode(tab, eventData.nodeId);
if (domNode.Node != null)
{
domNode.Node.nodeValue = eventData.characterData;
domNode.OnDomCharacterDataModified?.Invoke(eventData);
} else {
throw new InvalidOperationException("CharacterDataModified event was fired, the node info has not yet been recieved");
}
}
public static void onChildNodeCountUpdated(BrowserTab tab, childNodeCountUpdatedEvent eventData)
public static void ChildNodeCountUpdatedEventHandler(BrowserTab tab, childNodeCountUpdatedEvent eventData)
{
var domNode = createOrUpdateNode(tab, eventData.nodeId);
if (domNode.Node != null)
{
domNode.Node.childNodeCount = eventData.childNodeCount;
domNode.OnDomChildNodeCountUpdated?.Invoke(eventData);
} else {
throw new InvalidOperationException("ChildNodeCountUpdated event was fired, the node info has not yet been recieved");
}
}
public static void onChildNodeInserted(BrowserTab tab, childNodeInsertedEvent eventData)
public static void ChildNodeInsertedEventHandler(BrowserTab tab, childNodeInsertedEvent eventData)
{
var parentNode = createOrUpdateNode(tab, eventData.parentNodeId);
var childNode = createOrUpdateNode(tab, eventData.node.nodeId, eventData.parentNodeId, eventData.node.backendNodeId, eventData.node);
if (parentNode.Node != null && childNode.Node != null)
{
parentNode.Node.children.Insert(eventData.previousNodeId, childNode.Node);
parentNode.OnDomChildNodeInserted?.Invoke(eventData);
} else {
throw new InvalidOperationException("ChildNodeInserted event was fired, the node info has not yet been recieved");
}
}
public static void onChildNodeRemoved(BrowserTab tab, childNodeRemovedEvent eventData)
public static void ChildNodeRemovedEventHandler(BrowserTab tab, childNodeRemovedEvent eventData)
{
if (
tab.domNodes.TryGetValue(eventData.parentNodeId, out var parentNode) &&
......@@ -140,18 +153,19 @@ namespace bessw.Unity.WebView.ChromeDevTools
childNode.Node != null)
{
parentNode.Node.children.Remove(childNode.Node);
parentNode.OnDomChildNodeRemoved?.Invoke(eventData);
} else {
//throw new InvalidOperationException("ChildNodeRemoved event was fired, the node info has not yet been recieved");
}
}
public static void onDocumentUpdated(BrowserTab tab, documentUpdatedEvent eventData)
public static void DocumentUpdatedEventHandler(BrowserTab tab, documentUpdatedEvent eventData)
{
// clear the domNodes dictionary
tab.domNodes.Clear();
}
public static void onSetChildNodes(BrowserTab tab, setChildNodesEvent eventData)
public static void SetChildNodesEventHandler(BrowserTab tab, setChildNodesEvent eventData)
{
var parentNode = createOrUpdateNode(tab, eventData.parentId);
if (parentNode.Node != null)
......@@ -161,6 +175,7 @@ namespace bessw.Unity.WebView.ChromeDevTools
createOrUpdateNode(tab, node.nodeId, eventData.parentId, node.backendNodeId, node);
}
parentNode.Node.children = new(eventData.nodes);
parentNode.OnDomSetChildNodes?.Invoke(eventData);
} else {
throw new InvalidOperationException("SetChildNodes event was fired, the node info has not yet been recieved");
}
......@@ -218,7 +233,137 @@ namespace bessw.Unity.WebView.ChromeDevTools
return createOrUpdateNode(tab, commandResponse.nodeId);
}
public static async Task<DomNodeWrapper> getNodeForLocationAsync(BrowserTab tab, int x, int y, bool? includeUserAgentShadowDOM = null, bool? ignorePointerEventsNone = null)
{
var commandResponse = await tab.devtools.SendCommandAsync<getNodeForLocation, getNodeForLocationCommandResponse>(new getNodeForLocation
{
x = x,
y = y,
includeUserAgentShadowDOM = includeUserAgentShadowDOM,
ignorePointerEventsNone = ignorePointerEventsNone
});
if (commandResponse.nodeId == null) throw new InvalidOperationException("nodeId was not provided, is the dom domain enabled?");
return createOrUpdateNode(tab, (int)commandResponse.nodeId, backendNodeId: commandResponse.backendNodeId);
}
/// <summary>
/// Enables DOM agent for the given page.
/// </summary>
/// <param name="tab"></param>
/// <param name="includeWhitespace"></param>
/// <returns></returns>
public static Task enableAsync(BrowserTab tab, string? includeWhitespace = null)
{
return tab.devtools.SendCommandAsync(new enable
{
includeWhitespace = includeWhitespace
});
}
/// <summary>
/// Disables DOM agent for the given page.
/// </summary>
/// <param name="tab"></param>
/// <returns></returns>
public static Task disableAsync(BrowserTab tab)
{
return tab.devtools.SendCommandAsync(new disable());
}
/// <summary>
/// Focus the given node.
/// </summary>
public async Task focusAsync()
{
await tab.devtools.SendCommandAsync(new focus
{
nodeId = NodeId
});
}
/// <summary>
/// Returns attributes for the specified node.
/// </summary>
/// <returns></returns>
public async Task<Dictionary<string, string>> getAttributesAsync()
{
var commandResponse = await tab.devtools.SendCommandAsync<getAttributes, getAttributesCommandResponse>(new getAttributes
{
nodeId = NodeId
});
Node.attributes = commandResponse.attributes;
return commandResponse.attributes;
}
/// <summary>
/// Returns boxes for the currently selected nodes.
/// </summary>
/// <returns></returns>
public async Task<BoxModel> getBoxModelAsync()
{
var commandResponse = await tab.devtools.SendCommandAsync<getBoxModel, getBoxModelCommandResponse>(new getBoxModel
{
nodeId = NodeId
});
return commandResponse.model;
}
/// <summary>
/// Returns node's HTML markup.
/// </summary>
/// <returns></returns>
public async Task<string> getOuterHtmlAsync()
{
var commandResponse = await tab.devtools.SendCommandAsync<getOuterHTML, getOuterHTMLCommandResponse>(new getOuterHTML
{
nodeId = NodeId
});
return commandResponse.outerHTML;
}
/// <summary>
/// Moves node into an other container node, places it before the given node (if absent, the moved node becomes the last child of `targetNode`).
/// </summary>
/// <param name="targetNode"></param>
/// <param name="insertBeforeNode"></param>
/// <returns></returns>
/// <remarks>TODO: registered event handlers might need to be registered to the new node id again</remarks>
public async Task moveToAsync(DomNodeWrapper targetNode, DomNodeWrapper? insertBeforeNode = null)
{
var commandResponse = await tab.devtools.SendCommandAsync<moveTo, moveToCommandResponse>(new moveTo
{
nodeId = NodeId,
targetNodeId = targetNode.NodeId,
insertBeforeNodeId = insertBeforeNode?.NodeId
});
// update the new node id of the moved node
// TODO: registered event handlers might need to be registered again to the new node id
tab.domNodes.Remove(NodeId);
NodeId = commandResponse.nodeId;
tab.domNodes[NodeId] = this;
}
/// <summary>
/// Search a child node by querySelector.
/// </summary>
/// <param name="selector"></param>
/// <returns></returns>
public async Task<DomNodeWrapper> querySelectorAsync(string selector)
{
var commandResponse = await tab.devtools.SendCommandAsync<querySelector, querySelectorCommandResponse>(new querySelector
{
nodeId = NodeId,
selector = selector
});
return createOrUpdateNode(tab, commandResponse.nodeId);
}
/// <summary>
/// Search all child nodes matching the querySelector.
/// </summary>
/// <param name="selector"></param>
/// <returns></returns>
public async Task<DomNodeWrapper[]> querySelectorAllAsync(string selector)
{
var commandResponse = await tab.devtools.SendCommandAsync<querySelectorAll, querySelectorAllCommandResponse>(new querySelectorAll
......@@ -235,15 +380,134 @@ namespace bessw.Unity.WebView.ChromeDevTools
return domNodes;
}
/// <summary>
/// Removes attribute with given name from the element.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public async Task removeAttributeAsync(string name)
{
await tab.devtools.SendCommandAsync(new removeAttribute
{
nodeId = NodeId,
name = name
});
}
public async Task<Dictionary<string, string>> getAttributesAsync()
/// <summary>
/// Requests that children of the node with given id are returned to the caller in form of
/// `setChildNodes` events where not only immediate children are retrieved, but all children down
/// to the specified depth.
/// </summary>
public async Task requestChildNodesAsync(int? depth = null, bool? pierce = null)
{
var commandResponse = await tab.devtools.SendCommandAsync<getAttributes, getAttributesCommandResponse>(new getAttributes
await tab.devtools.SendCommandAsync(new requestChildNodes
{
nodeId = NodeId,
depth = depth,
pierce = pierce
});
}
/// <summary>
/// Scrolls the node into view if not already visible.
/// </summary>
/// <returns></returns>
public async Task scrollIntoViewIfNeededAsync()
{
await tab.devtools.SendCommandAsync(new scrollIntoViewIfNeeded
{
nodeId = NodeId
});
Node.attributes = commandResponse.attributes;
return commandResponse.attributes;
}
/// <summary>
/// Sets attribute for the node. This method is useful when user edits some existing
/// attribute value and types in several attribute name/value pairs.
/// </summary>
public async Task setAttributesAsText(string text, string? name = null)
{
await tab.devtools.SendCommandAsync(new setAttributesAsText
{
nodeId = NodeId,
text = text,
name = name
});
}
/// <summary>
/// Sets attribute for this element.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <returns></returns>
public async Task setAttributeValue(string name, string value)
{
await tab.devtools.SendCommandAsync(new setAttributeValue
{
nodeId = NodeId,
name = name,
value = value
});
}
/// <summary>
/// Sets files for the given file input element.
/// </summary>
public Task setFileInputFilesAsync(string[] files)
{
return tab.devtools.SendCommandAsync(new setFileInputFiles
{
nodeId = NodeId,
files = files
});
}
/// <summary>
/// Sets the nodes name.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
/// <remarks>updates the nodeId</remarks>
public async Task setNodeNameAsync(string name)
{
var commandResponse = await tab.devtools.SendCommandAsync<setNodeName,setNodeNameCommandResponse>(new setNodeName
{
nodeId = NodeId,
name = name
});
// update the new node id of the renamed node
// TODO: registered event handlers might need to be registered again to the new node id
tab.domNodes.Remove(NodeId);
NodeId = commandResponse.nodeId;
tab.domNodes[NodeId] = this;
}
public Task setNodeValueAsync(string value)
{
return tab.devtools.SendCommandAsync(new setNodeValue
{
nodeId = NodeId,
value = value
});
}
/// <summary>
/// Sets node HTML markup, changes the node id.
/// </summary>
public async Task setOuterHtmlAsync(string outerHtml)
{
var commandResponse = await tab.devtools.SendCommandAsync<setOuterHTML,setOuterHTMLCommandResponse>(new setOuterHTML
{
nodeId = NodeId,
outerHTML = outerHtml
});
// update the new node id of the renamed node
// TODO: registered event handlers might need to be registered again to the new node id
tab.domNodes.Remove(NodeId);
NodeId = commandResponse.nodeId;
tab.domNodes[NodeId] = this;
}
#endregion Async Commands
......
using bessw.Unity.WebView.ChromeDevTools;
using bessw.Unity.WebView.ChromeDevTools.Protocol.DOM;
using bessw.Unity.WebView.ChromeDevTools.Protocol.Input;
using MoreLinq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
......@@ -43,6 +46,16 @@ namespace bessw.Unity.WebView
private Browser browser;
public BrowserTab tab;
/// <summary>
/// Fired when `Document` has been totally updated. Node ids are no longer valid.
/// Other DOM events are fired directly on their `DomNodeWrapper` object.
/// </summary>
public event Action<documentUpdatedEvent> onDomDocumentUpdated
{
add => tab.devtools.onDomDocumentUpdated += value;
remove => tab.devtools.onDomDocumentUpdated -= value;
}
private RawImage rawImage;
private RectTransform rectTransform;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment