diff options
Diffstat (limited to '')
-rw-r--r-- | remote/cdp/domains/content/DOM.sys.mjs | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/remote/cdp/domains/content/DOM.sys.mjs b/remote/cdp/domains/content/DOM.sys.mjs new file mode 100644 index 0000000000..8f920756e6 --- /dev/null +++ b/remote/cdp/domains/content/DOM.sys.mjs @@ -0,0 +1,246 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { ContentProcessDomain } from "chrome://remote/content/cdp/domains/ContentProcessDomain.sys.mjs"; + +export class DOM extends ContentProcessDomain { + constructor(session) { + super(session); + this.enabled = false; + } + + destructor() { + this.disable(); + } + + // commands + + async enable() { + if (!this.enabled) { + this.enabled = true; + } + } + + /** + * Describes node given its id. + * + * Does not require domain to be enabled. Does not start tracking any objects. + * + * @param {Object} options + * @param {number=} options.backendNodeId [not supported] + * Identifier of the backend node. + * @param {number=} options.depth [not supported] + * The maximum depth at which children should be retrieved, defaults to 1. + * Use -1 for the entire subtree or provide an integer larger than 0. + * @param {number=} options.nodeId [not supported] + * Identifier of the node. + * @param {string} options.objectId + * JavaScript object id of the node wrapper. + * @param {boolean=} options.pierce [not supported] + * Whether or not iframes and shadow roots should be traversed + * when returning the subtree, defaults to false. + * + * @return {DOM.Node} + * Node description. + */ + describeNode(options = {}) { + const { objectId } = options; + + // Until nodeId/backendNodeId is supported force usage of the objectId + if (!["string"].includes(typeof objectId)) { + throw new TypeError("objectId: string value expected"); + } + + const Runtime = this.session.domains.get("Runtime"); + const debuggerObj = Runtime._getRemoteObject(objectId); + if (!debuggerObj) { + throw new Error("Could not find object with given id"); + } + + if (typeof debuggerObj.nodeId == "undefined") { + throw new Error("Object id doesn't reference a Node"); + } + + const unsafeObj = debuggerObj.unsafeDereference(); + + const attributes = []; + if (unsafeObj.attributes) { + // Flatten the list of attributes for name and value + for (const attribute of unsafeObj.attributes) { + attributes.push(attribute.name, attribute.value); + } + } + + let context = this.docShell.browsingContext; + if (HTMLIFrameElement.isInstance(unsafeObj)) { + context = unsafeObj.contentWindow.docShell.browsingContext; + } + + const node = { + nodeId: debuggerObj.nodeId, + backendNodeId: debuggerObj.nodeId, + nodeType: unsafeObj.nodeType, + nodeName: unsafeObj.nodeName, + localName: unsafeObj.localName, + nodeValue: unsafeObj.nodeValue ? unsafeObj.nodeValue.toString() : "", + childNodeCount: unsafeObj.childElementCount, + attributes: attributes.length ? attributes : undefined, + frameId: context.id.toString(), + }; + + return { node }; + } + + disable() { + if (this.enabled) { + this.enabled = false; + } + } + + getContentQuads(options = {}) { + const { objectId } = options; + const Runtime = this.session.domains.get("Runtime"); + const debuggerObj = Runtime._getRemoteObject(objectId); + if (!debuggerObj) { + throw new Error(`Cannot find object with id: ${objectId}`); + } + const unsafeObject = debuggerObj.unsafeDereference(); + if (!unsafeObject.getBoxQuads) { + throw new Error("RemoteObject is not a node"); + } + let quads = unsafeObject.getBoxQuads({ relativeTo: this.content.document }); + quads = quads.map(quad => { + return [ + quad.p1.x, + quad.p1.y, + quad.p2.x, + quad.p2.y, + quad.p3.x, + quad.p3.y, + quad.p4.x, + quad.p4.y, + ].map(Math.round); + }); + return { quads }; + } + + getBoxModel(options = {}) { + const { objectId } = options; + const Runtime = this.session.domains.get("Runtime"); + const debuggerObj = Runtime._getRemoteObject(objectId); + if (!debuggerObj) { + throw new Error(`Cannot find object with id: ${objectId}`); + } + const unsafeObject = debuggerObj.unsafeDereference(); + const bounding = unsafeObject.getBoundingClientRect(); + const model = { + width: Math.round(bounding.width), + height: Math.round(bounding.height), + }; + for (const box of ["content", "padding", "border", "margin"]) { + const quads = unsafeObject.getBoxQuads({ + box, + relativeTo: this.content.document, + }); + + // getBoxQuads may return more than one element. In this case we have to compute the bounding box + // of all these boxes. + let bounding = { + p1: { x: Infinity, y: Infinity }, + p2: { x: -Infinity, y: Infinity }, + p3: { x: -Infinity, y: -Infinity }, + p4: { x: Infinity, y: -Infinity }, + }; + quads.forEach(quad => { + bounding = { + p1: { + x: Math.min(bounding.p1.x, quad.p1.x), + y: Math.min(bounding.p1.y, quad.p1.y), + }, + p2: { + x: Math.max(bounding.p2.x, quad.p2.x), + y: Math.min(bounding.p2.y, quad.p2.y), + }, + p3: { + x: Math.max(bounding.p3.x, quad.p3.x), + y: Math.max(bounding.p3.y, quad.p3.y), + }, + p4: { + x: Math.min(bounding.p4.x, quad.p4.x), + y: Math.max(bounding.p4.y, quad.p4.y), + }, + }; + }); + + model[box] = [ + bounding.p1.x, + bounding.p1.y, + bounding.p2.x, + bounding.p2.y, + bounding.p3.x, + bounding.p3.y, + bounding.p4.x, + bounding.p4.y, + ].map(Math.round); + } + return { + model, + }; + } + + /** + * Resolves the JavaScript node object for a given NodeId or BackendNodeId. + * + * @param {Object} options + * @param {number} options.backendNodeId [required for now] + * Backend identifier of the node to resolve. + * @param {number=} options.executionContextId + * Execution context in which to resolve the node. + * @param {number=} options.nodeId [not supported] + * Id of the node to resolve. + * @param {string=} options.objectGroup [not supported] + * Symbolic group name that can be used to release multiple objects. + * + * @return {Runtime.RemoteObject} + * JavaScript object wrapper for given node. + */ + resolveNode(options = {}) { + const { backendNodeId, executionContextId } = options; + + // Until nodeId is supported force usage of the backendNodeId + // Bug 1625417 - CDP expects the id as number + if (!["string"].includes(typeof backendNodeId)) { + throw new TypeError("backendNodeId: string value expected"); + } + if (!["undefined", "number"].includes(typeof executionContextId)) { + throw new TypeError("executionContextId: integer value expected"); + } + + const Runtime = this.session.domains.get("Runtime"); + + // Retrieve the node to resolve, and its context + const debuggerObj = Runtime._getRemoteObjectByNodeId(backendNodeId); + + if (!debuggerObj) { + throw new Error(`No node with given id found`); + } + + // If execution context isn't specified use the default one for the node + let context; + if (typeof executionContextId != "undefined") { + context = Runtime.contexts.get(executionContextId); + if (!context) { + throw new Error(`Node with given id does not belong to the document`); + } + } else { + context = Runtime._getDefaultContextForWindow(); + } + + Runtime._setRemoteObject(debuggerObj, context); + + return { + object: Runtime._serializeRemoteObject(debuggerObj, context.id), + }; + } +} |