// A helper actor for testing highlighters. // ⚠️ This should only be used for getting data for objects using CanvasFrameAnonymousContentHelper, // that we can't get directly from tests. const {
getRect,
getAdjustedQuads,
} = require("resource://devtools/shared/layout/utils.js");
// Set up a dummy environment so that EventUtils works. We need to be careful to // pass a window object into each EventUtils method we call rather than having // it rely on the |window| global. const EventUtils = {};
EventUtils.window = {};
EventUtils.parent = {}; /* eslint-disable camelcase */
EventUtils._EU_Ci = Ci;
EventUtils._EU_Cc = Cc; /* eslint-disable camelcase */
Services.scriptloader.loadSubScript( "chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
EventUtils
);
// We're an actor so we don't run in the browser test environment, so // we need to import TestUtils manually despite what the linter thinks. // eslint-disable-next-line mozilla/no-redeclare-with-import-autofix const { TestUtils } = ChromeUtils.importESModule( "resource://testing-common/TestUtils.sys.mjs"
);
/** * Get the instance of CanvasFrameAnonymousContentHelper used by a given * highlighter actor. * The instance provides methods to get/set attributes/text/style on nodes of * the highlighter, inserted into the nsCanvasFrame. * @see /devtools/server/actors/highlighters.js * @param {String} actorID
*/ function getHighlighterCanvasFrameHelper(conn, actorID) { // Retrieve the CustomHighlighterActor by its actorID: const actor = conn.getActor(actorID); if (!actor) { returnnull;
}
// Retrieve the sub class instance specific to each highlighter type:
let highlighter = actor.instance;
// SelectorHighlighter and TabbingOrderHighlighter can hold multiple highlighters. // For now, only retrieve the first highlighter. if (
highlighter._highlighters &&
Array.isArray(highlighter._highlighters) &&
highlighter._highlighters.length
) {
highlighter = highlighter._highlighters[0];
}
// Now, `highlighter` should be a final highlighter class, exposing // `CanvasFrameAnonymousContentHelper` via a `markup` attribute. if (highlighter.markup) { return highlighter.markup;
}
// Here we didn't find any highlighter; it can happen if the actor is a // FontsHighlighter (which does not use a CanvasFrameAnonymousContentHelper). returnnull;
}
var highlighterTestSpec = protocol.generateActorSpec({
typeName: "highlighterTest",
class HighlighterTestActor extends protocol.Actor {
constructor(conn, targetActor) { super(conn, highlighterTestSpec);
this.targetActor = targetActor;
}
get content() { returnthis.targetActor.window;
}
/** * Helper to retrieve a DOM element. * @param {string | array} selector Either a regular selector string * or a selector array. If an array, each item, except the last one * are considered matching an iframe, so that we can query element * within deep iframes.
*/
_querySelector(selector) {
let document = this.content.document; if (Array.isArray(selector)) { const fullSelector = selector.join(" >> "); while (selector.length > 1) { const str = selector.shift(); const iframe = document.querySelector(str); if (!iframe) { thrownew Error( 'Unable to find element with selector "' +
str + '"' + " (full selector:" +
fullSelector + ")"
);
} if (!iframe.contentWindow) { thrownew Error( "Iframe selector doesn't target an iframe \"" +
str + '"' + " (full selector:" +
fullSelector + ")"
);
}
document = iframe.contentWindow.document;
}
selector = selector.shift();
} const node = document.querySelector(selector); if (!node) { thrownew Error( 'Unable to find element with selector "' + selector + '"'
);
} return node;
}
/** * Get a value for a given attribute name, on one of the elements of the box * model highlighter, given its ID. * @param {String} nodeID The full ID of the element to get the attribute for * @param {String} name The name of the attribute to get * @param {String} actorID The highlighter actor ID * @return {String} The value, if found, null otherwise
*/
getHighlighterAttribute(nodeID, name, actorID) { const helper = getHighlighterCanvasFrameHelper(this.conn, actorID);
if (!helper) { thrownew Error(`Highlighter not found`);
}
/** * Get the bounding client rect for an highlighter element, given its ID. * * @param {String} nodeID The full ID of the element to get the DOMRect for * @param {String} actorID The highlighter actor ID * @return {DOMRect} The value, if found, null otherwise
*/
getHighlighterBoundingClientRect(nodeID, actorID) { const helper = getHighlighterCanvasFrameHelper(this.conn, actorID);
if (!helper) { thrownew Error(`Highlighter not found`);
}
return helper.getBoundingClientRect(nodeID);
}
/** * Get the computed style for a given property, on one of the elements of the * box model highlighter, given its ID. * @param {String} nodeID The full ID of the element to get the attribute for * @param {String} property The name of the property * @param {String} actorID The highlighter actor ID * @return {String} The computed style of the property
*/
getHighlighterComputedStyle(nodeID, property, actorID) { const helper = getHighlighterCanvasFrameHelper(this.conn, actorID);
if (!helper) { thrownew Error(`Highlighter not found`);
}
/** * Get the textcontent of one of the elements of the box model highlighter, * given its ID. * @param {String} nodeID The full ID of the element to get the attribute for * @param {String} actorID The highlighter actor ID * @return {String} The textcontent value
*/
getHighlighterNodeTextContent(nodeID, actorID) {
let value; const helper = getHighlighterCanvasFrameHelper(this.conn, actorID); if (helper) {
value = helper.getTextContentForElement(nodeID);
} return value;
}
/** * Get the number of box-model highlighters created by the SelectorHighlighter * @param {String} actorID The highlighter actor ID * @return {Number} The number of box-model highlighters created, or null if the * SelectorHighlighter was not found.
*/
getSelectorHighlighterBoxNb(actorID) { const highlighter = this.conn.getActor(actorID); const { _highlighter: h } = highlighter; if (!h || !h._highlighters) { returnnull;
} return h._highlighters.length;
}
/** * Subscribe to the box-model highlighter's update event, modify an attribute of * the currently highlighted node and send a message when the highlighter has * updated. * @param {String} the name of the attribute to be changed * @param {String} the new value for the attribute * @param {String} actorID The highlighter actor ID
*/
changeHighlightedNodeWaitForUpdate(name, value, actorID) { returnnew Promise(resolve => { const highlighter = this.conn.getActor(actorID); const { _highlighter: h } = highlighter;
h.once("updated", resolve);
h.currentNode.setAttribute(name, value);
});
}
/** * Register a one-time "updated" event listener. * The method does not wait for the "updated" event itself so the response can be sent * back and the client would know the event listener is properly set. * A separate event, "highlighter-updated", will be emitted when the highlighter updates. * * @param {String} actorID The highlighter actor ID
*/
registerOneTimeHighlighterUpdate(actorID) { const { _highlighter } = this.conn.getActor(actorID);
_highlighter.once("updated").then(() => this.emit("highlighter-updated"));
// Return directly so the client knows the event listener is set
}
/** * @returns {PausedDebuggerOverlay} The paused overlay instance
*/
_getPausedDebuggerOverlay() { // We use `_pauseOverlay` since it's the cached value; `pauseOverlay` is a getter that // will create the overlay when called (if it does not exist yet). returnthis.targetActor?.threadActor?._pauseOverlay;
}
/** * Simulates a click on a button of the debugger pause overlay. * * @param {String} id: The id of the element (e.g. "paused-dbg-resume-button").
*/
async clickPausedDebuggerOverlayButton(id) { const pauseOverlay = this._getPausedDebuggerOverlay(); if (!pauseOverlay) { return;
}
// Because the highlighter markup elements live inside an anonymous content frame which // does not expose an API to dispatch events to them, we can't directly dispatch // events to the nodes themselves. // We're directly calling `handleEvent` on the pause overlay, which is the mouse events // listener callback on the overlay.
pauseOverlay.handleEvent({ type: "mousedown", target: { id } });
}
// It might happen that while the eyedropper isn't hidden anymore, the color-value // is not set yet. const color = await TestUtils.waitForCondition(() => { const colorValueElement = eyeDropper.getElement("color-value"); const textContent = colorValueElement.getTextContent(); return textContent;
}, "Couldn't get a non-empty text content for the color-value element");
return color;
}
/** * Get the TabbingOrderHighlighter for the associated targetActor * * @returns {TabbingOrderHighlighter}
*/
_getTabbingOrderHighlighter() { const form = this.targetActor.form(); const accessibilityActor = this.conn._getOrCreateActor(
form.accessibilityActor
);
if (!accessibilityActor) { returnnull;
} // We use `_tabbingOrderHighlighter` since it's the cached value; `tabbingOrderHighlighter` // is a getter that will create the highlighter when called (if it does not exist yet). return accessibilityActor.walker?._tabbingOrderHighlighter;
}
/** * Get a representation of the NodeTabbingOrderHighlighters created by the * TabbingOrderHighlighter of a given targetActor. * * @returns {Array<String>} An array which will contain as many entry as they are * NodeTabbingOrderHighlighters displayed. * Each item will be of the form `nodename[#id]: index`. * For example: * [ * `button#top-btn-1 : 1`, * `html : 2`, * `button#iframe-btn-1 : 3`, * `button#iframe-btn-2 : 4`, * `button#top-btn-2 : 5`, * ]
*/
getTabbingOrderHighlighterData() { const highlighter = this._getTabbingOrderHighlighter(); if (!highlighter) { return [];
}
class HighlighterTestFront extends protocol.FrontClassWithSpec(
highlighterTestSpec
) {
constructor(client, targetFront, parentFront) { super(client, targetFront, parentFront); this.formAttributeName = "highlighterTestActor"; // The currently active highlighter is obtained by calling a custom getter // provided manually after requesting TestFront. See `getHighlighterTestFront(toolbox)` this._highlighter = null;
}
/** * Override the highlighter getter with a custom method that returns * the currently active highlighter instance. * * @param {Function|Highlighter} _customHighlighterGetter
*/
set highlighter(_customHighlighterGetter) { this._highlighter = _customHighlighterGetter;
}
/** * The currently active highlighter instance. * If there is a custom getter for the highlighter, return its result. * * @return {Highlighter|null}
*/
get highlighter() { returntypeofthis._highlighter === "function"
? this._highlighter()
: this._highlighter;
}
/** * Get the value of an attribute on one of the highlighter's node. * @param {String} nodeID The Id of the node in the highlighter. * @param {String} name The name of the attribute. * @param {Object} highlighter Optional custom highlighter to target * @return {String} value
*/
getHighlighterNodeAttribute(nodeID, name, highlighter) { returnthis.getHighlighterAttribute(
nodeID,
name,
(highlighter || this.highlighter).actorID
);
}
/** * Get the computed style of a property on one of the highlighter's node. * @param {String} nodeID The Id of the node in the highlighter. * @param {String} property The name of the property. * @param {Object} highlighter Optional custom highlighter to target * @return {String} value
*/
getHighlighterComputedStyle(nodeID, property, highlighter) { returnsuper.getHighlighterComputedStyle(
nodeID,
property,
(highlighter || this.highlighter).actorID
);
}
/** * Is the highlighter currently visible on the page?
*/
async isHighlighting() { // Once the highlighter is hidden, the reference to it is lost. // Assume it is not highlighting. if (!this.highlighter) { returnfalse;
}
/** * Get the current rect of the border region of the box-model highlighter
*/
async getSimpleBorderRect() { const { border } = await this.getBoxModelStatus(); const { p1, p2, p4 } = border.points;
/** * Get the current positions and visibility of the various box-model highlighter * elements.
*/
async getBoxModelStatus() { const isVisible = await this.isHighlighting();
const ret = {
visible: isVisible,
};
for (const region of ["margin", "border", "padding", "content"]) { const points = await this._getPointsForRegion(region); const visible = await this._isRegionHidden(region);
ret[region] = { points, visible };
}
ret.guides = {}; for (const guide of ["top", "right", "bottom", "left"]) {
ret.guides[guide] = await this._getGuideStatus(guide);
}
return ret;
}
/** * Check that the box-model highlighter is currently highlighting the node matching the * given selector. * @param {String} selector * @return {Boolean}
*/
async assertHighlightedNode(selector) { const rect = await this.getNodeRect(selector); returnthis.isNodeRectHighlighted(rect);
}
/** * Check that the box-model highlighter is currently highlighting the text node that can * be found at a given index within the list of childNodes of a parent element matching * the given selector. * @param {String} parentSelector * @param {Number} childNodeIndex * @return {Boolean}
*/
async assertHighlightedTextNode(parentSelector, childNodeIndex) { const rect = await this.getTextNodeRect(parentSelector, childNodeIndex); returnthis.isNodeRectHighlighted(rect);
}
/** * Check that the box-model highlighter is currently highlighting the given rect. * @param {Object} rect * @return {Boolean}
*/
async isNodeRectHighlighted({ left, top, width, height }) { const { visible, border } = await this.getBoxModelStatus();
let points = border.points; if (!visible) { returnfalse;
}
// Check that the node is within the box model const right = left + width; const bottom = top + height;
// Converts points dictionnary into an array const list = []; for (let i = 1; i <= 4; i++) { const p = points["p" + i];
list.push([p.x, p.y]);
}
points = list;
// Check that each point of the node is within the box model return (
isInside([left, top], points) &&
isInside([right, top], points) &&
isInside([right, bottom], points) &&
isInside([left, bottom], points)
);
}
/** * Get the coordinate (points attribute) from one of the polygon elements in the * box model highlighter.
*/
async _getPointsForRegion(region) { const d = await this.getHighlighterNodeAttribute( "box-model-" + region, "d"
);
if (!d) { returnnull;
}
const polygons = d.match(/M[^M]+/g); if (!polygons) { returnnull;
}
/** * Is a given region polygon element of the box-model highlighter currently * hidden?
*/
async _isRegionHidden(region) { const value = await this.getHighlighterNodeAttribute( "box-model-" + region, "hidden"
); return value !== null;
}
async _getGuideStatus(location) { const id = "box-model-guide-" + location;
/** * Get the coordinates of the rectangle that is defined by the 4 guides displayed * in the toolbox box-model highlighter. * @return {Object} Null if at least one guide is hidden. Otherwise an object * with p1, p2, p3, p4 properties being {x, y} objects.
*/
async getGuidesRectangle() { const tGuide = await this._getGuideStatus("top"); const rGuide = await this._getGuideStatus("right"); const bGuide = await this._getGuideStatus("bottom"); const lGuide = await this._getGuideStatus("left");
/** * Get the "d" attribute value for one of the box-model highlighter's region * <path> elements, and parse it to a list of points. * @param {String} region The box model region name. * @param {Front} highlighter The front of the highlighter. * @return {Object} The object returned has the following form: * - d {String} the d attribute value * - points {Array} an array of all the polygons defined by the path. Each box * is itself an Array of points, themselves being [x,y] coordinates arrays.
*/
async getHighlighterRegionPath(region, highlighter) { const d = await this.getHighlighterNodeAttribute(
`box-model-${region}`, "d",
highlighter
); if (!d) { return { d: null };
}
const polygons = d.match(/M[^M]+/g); if (!polygons) { return { d };
}
return { d, points };
}
}
protocol.registerFront(HighlighterTestFront); /** * Check whether a point is included in a polygon. * Taken and tweaked from: * https://github.com/iominh/point-in-polygon-extended/blob/master/src/index.js#L30-L85 * @param {Array} point [x,y] coordinates * @param {Array} polygon An array of [x,y] points * @return {Boolean}
*/ function isInside(point, polygon) { if (polygon.length === 0) { returnfalse;
}
// Reduce the length of the fractional part because this is likely to cause errors when // the point is on the edge of the polygon.
point = point.map(n => n.toFixed(2));
polygon = polygon.map(p => p.map(n => n.toFixed(2)));
const n = polygon.length; const newPoints = polygon.slice(0);
newPoints.push(polygon[0]);
let wn = 0;
// loop through all edges of the polygon for (let i = 0; i < n; i++) { // Accept points on the edges const r = isLeft(newPoints[i], newPoints[i + 1], point); if (r === 0) { returntrue;
} if (newPoints[i][1] <= point[1]) { if (newPoints[i + 1][1] > point[1] && r > 0) {
wn++;
}
} elseif (newPoints[i + 1][1] <= point[1] && r < 0) {
wn--;
}
} if (wn === 0) {
dumpn(JSON.stringify(point) + " is outside of " + JSON.stringify(polygon));
} // the point is outside only when this winding number wn===0, otherwise it's inside return wn !== 0;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.