245 lines
7.5 KiB
JavaScript
245 lines
7.5 KiB
JavaScript
/* 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),
|
|
};
|
|
}
|
|
}
|