Source code

Revision control

Copy as Markdown

Other Tools

/* 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.
*
* @returns {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.backendNodeId,
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.
*
* @returns {Runtime.RemoteObject}
* JavaScript object wrapper for given node.
*/
resolveNode(options = {}) {
const { backendNodeId, executionContextId } = options;
// Until nodeId is supported force usage of the backendNodeId
if (!["number"].includes(typeof backendNodeId)) {
throw new TypeError("backendNodeId: number 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),
};
}
}