summaryrefslogtreecommitdiffstats
path: root/remote/cdp/domains/content/DOM.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--remote/cdp/domains/content/DOM.sys.mjs246
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),
+ };
+ }
+}