diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /devtools/server/actors/highlighters/node-tabbing-order.js | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/server/actors/highlighters/node-tabbing-order.js')
-rw-r--r-- | devtools/server/actors/highlighters/node-tabbing-order.js | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/devtools/server/actors/highlighters/node-tabbing-order.js b/devtools/server/actors/highlighters/node-tabbing-order.js new file mode 100644 index 0000000000..229342ee98 --- /dev/null +++ b/devtools/server/actors/highlighters/node-tabbing-order.js @@ -0,0 +1,399 @@ +/* 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.ID_CLASS_PREFIX = "tabbing-order-"; + this.markup = new CanvasFrameAnonymousContentHelper( + this.highlighterEnv, + this._buildMarkup.bind(this) + ); + this.isReady = this.markup.initialize(); + } + + _buildMarkup() { + const root = this.markup.createNode({ + attributes: { + id: "root", + class: "root highlighter-container tabbing-order", + "aria-hidden": "true", + }, + prefix: this.ID_CLASS_PREFIX, + }); + + const container = this.markup.createNode({ + parent: root, + attributes: { + id: "container", + width: "100%", + height: "100%", + hidden: "true", + }, + prefix: this.ID_CLASS_PREFIX, + }); + + // Building the SVG element + this.markup.createNode({ + parent: container, + attributes: { + class: "bounds", + id: "bounds", + }, + prefix: this.ID_CLASS_PREFIX, + }); + + // Building the nodeinfo bar markup + + const infobarContainer = this.markup.createNode({ + parent: root, + attributes: { + class: "infobar-container", + id: "infobar-container", + position: "top", + hidden: "true", + }, + prefix: this.ID_CLASS_PREFIX, + }); + + const infobar = this.markup.createNode({ + parent: infobarContainer, + attributes: { + class: "infobar", + }, + prefix: this.ID_CLASS_PREFIX, + }); + + this.markup.createNode({ + parent: infobar, + attributes: { + class: "infobar-text", + id: "infobar-text", + }, + prefix: this.ID_CLASS_PREFIX, + }); + + return root; + } + + /** + * Destroy the nodes. Remove listeners. + */ + destroy() { + this.markup.destroy(); + + AutoRefreshHighlighter.prototype.destroy.call(this); + } + + getElement(id) { + return this.markup.getElement(this.ID_CLASS_PREFIX + 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("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 <script> tag for instance) + this._hide(); + } + + return shown; + } + + /** + * Hide the highlighter, the outline and the infobar. + */ + _hide() { + setIgnoreLayoutChanges(true); + + this._hideTabbingOrder(); + this._hideInfobar(); + + setIgnoreLayoutChanges( + false, + this.highlighterEnv.window.document.documentElement + ); + } + + /** + * Hide the infobar + */ + _hideInfobar() { + this.getElement("infobar-container").setAttribute("hidden", "true"); + } + + /** + * Show the infobar + */ + _showInfobar() { + if (!this.currentNode) { + return; + } + + this.getElement("infobar-container").removeAttribute("hidden"); + this.getElement("infobar-text").setTextContent(this.options.index); + const bounds = this._getBounds(); + const container = this.getElement("infobar-container"); + + moveInfobar(container, bounds, this.win); + } + + /** + * Hide the tabbing order highlighter + */ + _hideTabbingOrder() { + this.getElement("container").setAttribute("hidden", "true"); + } + + /** + * Show the tabbing order highlighter + */ + _showTabbingOrder() { + this.getElement("container").removeAttribute("hidden"); + } + + /** + * Calculate border bounds based on the quads returned by getAdjustedQuads. + * @return {Object} A bounds object {bottom,height,left,right,top,width,x,y} + */ + _getBorderBounds() { + const quads = this.currentQuads.border; + if (!quads || !quads.length) { + return null; + } + + const bounds = { + bottom: -Infinity, + height: 0, + left: Infinity, + right: -Infinity, + top: Infinity, + width: 0, + x: 0, + y: 0, + }; + + for (const q of quads) { + bounds.bottom = Math.max(bounds.bottom, q.bounds.bottom); + bounds.top = Math.min(bounds.top, q.bounds.top); + bounds.left = Math.min(bounds.left, q.bounds.left); + bounds.right = Math.max(bounds.right, q.bounds.right); + } + bounds.x = bounds.left; + bounds.y = bounds.top; + bounds.width = bounds.right - bounds.left; + bounds.height = bounds.bottom - bounds.top; + + return bounds; + } + + /** + * Update the tabbing order index as per the current node. + * + * @return {boolean} + * True if the current node has a tabbing order index to be + * highlighted + */ + _updateTabbingOrder() { + if (!this._nodeNeedsHighlighting()) { + this._hideTabbingOrder(); + return false; + } + + const boundsEl = this.getElement("bounds"); + const { left, top, width, height } = this._getBounds(); + boundsEl.setAttribute( + "style", + `top: ${top}px; left: ${left}px; width: ${width}px; height: ${height}px;` + ); + + // Un-zoom the root wrapper if the page was zoomed. + const rootId = this.ID_CLASS_PREFIX + "container"; + this.markup.scaleRootElement(this.currentNode, rootId); + + return true; + } + + /** + * Can the current node be highlighted? Does it have quads. + * @return {Boolean} + */ + _nodeNeedsHighlighting() { + return ( + this.currentQuads.margin.length || + this.currentQuads.border.length || + this.currentQuads.padding.length || + this.currentQuads.content.length + ); + } + + _getBounds() { + const borderBounds = this._getBorderBounds(); + let bounds = { + bottom: 0, + height: 0, + left: 0, + right: 0, + top: 0, + width: 0, + x: 0, + y: 0, + }; + + if (!borderBounds) { + // Invisible element such as a script tag. + return bounds; + } + + const { bottom, height, left, right, top, width, x, y } = borderBounds; + if (width > 0 || height > 0) { + bounds = { bottom, height, left, right, top, width, x, y }; + } + + return bounds; + } +} + +/** + * Move the infobar to the right place in the highlighter. The infobar is used + * to display element's tabbing order index. + * + * @param {DOMNode} container + * The container element which will be used to position the infobar. + * @param {Object} bounds + * The content bounds of the container element. + * @param {Window} win + * The window object. + */ +function moveInfobar(container, bounds, win) { + const zoom = getCurrentZoom(win); + const { computedStyle } = container; + const margin = 2; + const arrowSize = + parseFloat( + computedStyle.getPropertyValue("--highlighter-bubble-arrow-size") + ) - 2; + const containerHeight = parseFloat(computedStyle.getPropertyValue("height")); + const containerWidth = parseFloat(computedStyle.getPropertyValue("width")); + + const topBoundary = margin; + const bottomBoundary = + win.document.scrollingElement.scrollHeight - containerHeight - margin - 1; + const leftBoundary = containerWidth / 2 + margin; + + let top = bounds.y - containerHeight - arrowSize; + let left = bounds.x + bounds.width / 2; + const bottom = bounds.bottom + arrowSize; + let positionAttribute = "top"; + + const canBePlacedOnTop = top >= topBoundary; + const canBePlacedOnBottom = bottomBoundary - bottom > 0; + + if (!canBePlacedOnTop && canBePlacedOnBottom) { + top = bottom; + positionAttribute = "bottom"; + } + + let hideArrow = false; + if (top < topBoundary) { + hideArrow = true; + top = topBoundary; + } else if (top > bottomBoundary) { + hideArrow = true; + top = bottomBoundary; + } + + if (left < leftBoundary) { + hideArrow = true; + left = leftBoundary; + } + + if (hideArrow) { + container.setAttribute("hide-arrow", "true"); + } else { + container.removeAttribute("hide-arrow"); + } + + container.setAttribute( + "style", + ` + position: absolute; + transform-origin: 0 0; + transform: scale(${1 / zoom}) translate(calc(${left}px - 50%), ${top}px)` + ); + + container.setAttribute("position", positionAttribute); +} + +exports.NodeTabbingOrderHighlighter = NodeTabbingOrderHighlighter; |