/* 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/. */ "use strict"; loader.lazyRequireGetter( this, ["setIgnoreLayoutChanges", "getCurrentZoom"], "resource://devtools/shared/layout/utils.js", true ); loader.lazyRequireGetter( this, "AutoRefreshHighlighter", "resource://devtools/server/actors/highlighters/auto-refresh.js", true ); loader.lazyRequireGetter( this, ["CanvasFrameAnonymousContentHelper"], "resource://devtools/server/actors/highlighters/utils/markup.js", true ); /** * The NodeTabbingOrderHighlighter draws an outline around a node (based on its * border bounds). * * Usage example: * * const h = new NodeTabbingOrderHighlighter(env); * await h.isReady(); * h.show(node, options); * h.hide(); * h.destroy(); * * @param {number} options.index * Tabbing index value to be displayed in the highlighter info bar. */ class NodeTabbingOrderHighlighter extends AutoRefreshHighlighter { constructor(highlighterEnv) { super(highlighterEnv); this._doNotStartRefreshLoop = true; this.markup = new CanvasFrameAnonymousContentHelper( this.highlighterEnv, this._buildMarkup.bind(this), { contentRootHostClassName: "devtools-highlighter-tabbing-order", } ); this.isReady = this.markup.initialize(); } _buildMarkup() { this.rootEl = this.markup.createNode({ attributes: { id: "tabbing-order-root", class: "tabbing-order-root highlighter-container tabbing-order", "aria-hidden": "true", }, }); const container = this.markup.createNode({ parent: this.rootEl, attributes: { id: "tabbing-order-container", width: "100%", height: "100%", hidden: "true", }, }); // Building the SVG element this.markup.createNode({ parent: container, attributes: { class: "tabbing-order-bounds", id: "tabbing-order-bounds", }, }); // Building the nodeinfo bar markup const infobarContainer = this.markup.createNode({ parent: this.rootEl, attributes: { class: "tabbing-order-infobar-container", id: "tabbing-order-infobar-container", position: "top", hidden: "true", }, }); const infobar = this.markup.createNode({ parent: infobarContainer, attributes: { class: "tabbing-order-infobar", }, }); this.markup.createNode({ parent: infobar, attributes: { class: "tabbing-order-infobar-text", id: "tabbing-order-infobar-text", }, }); return this.rootEl; } /** * Destroy the nodes. Remove listeners. */ destroy() { this.markup.destroy(); this.rootEl = null; AutoRefreshHighlighter.prototype.destroy.call(this); } getElement(id) { return this.markup.getElement(id); } /** * Update focused styling for a node tabbing index highlight. * * @param {boolean} focused * Indicates if the highlighted node needs to be focused. */ updateFocus(focused) { const root = this.getElement("tabbing-order-root"); root.classList?.toggle("focused", focused); } /** * Show the highlighter on a given node */ _show() { return this._update(); } /** * Update the highlighter on the current highlighted node (the one that was * passed as an argument to show(node)). * Should be called whenever node size or attributes change */ _update() { let shown = false; setIgnoreLayoutChanges(true); if (this._updateTabbingOrder()) { this._showInfobar(); this._showTabbingOrder(); shown = true; setIgnoreLayoutChanges( false, this.highlighterEnv.window.document.documentElement ); } else { // Nothing to highlight (0px rectangle like a