/* 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"; const { CanvasFrameAnonymousContentHelper, } = require("resource://devtools/server/actors/highlighters/utils/markup.js"); loader.lazyGetter(this, "HighlightersBundle", () => { return new Localization(["devtools/shared/highlighters.ftl"], true); }); loader.lazyGetter(this, "isAndroid", () => { return Services.appinfo.OS === "Android"; }); /** * The RemoteNodePickerNotice is a class that displays a notice in a remote debugged page. * This is used to signal to users they can click/tap an element to select it in the * about:devtools-toolbox toolbox inspector. */ class RemoteNodePickerNotice { #highlighterEnvironment; #previousHoveredElement; rootElementId = "node-picker-notice-root"; hideButtonId = "node-picker-notice-hide-button"; infoNoticeElementId = "node-picker-notice-info"; /** * @param {highlighterEnvironment} highlighterEnvironment */ constructor(highlighterEnvironment) { this.#highlighterEnvironment = highlighterEnvironment; this.markup = new CanvasFrameAnonymousContentHelper( this.#highlighterEnvironment, this.#buildMarkup ); this.isReady = this.markup.initialize(); } #buildMarkup = () => { const container = this.markup.createNode({ attributes: { class: "highlighter-container" }, }); // Wrapper element. const wrapper = this.markup.createNode({ parent: container, attributes: { id: this.rootElementId, hidden: "true", overlay: "true", }, }); const toolbar = this.markup.createNode({ parent: wrapper, attributes: { id: "node-picker-notice-toolbar", class: "toolbar", }, }); this.markup.createNode({ parent: toolbar, attributes: { id: "node-picker-notice-icon", class: isAndroid ? "touch" : "", }, }); const actionStr = HighlightersBundle.formatValueSync( isAndroid ? "remote-node-picker-notice-action-touch" : "remote-node-picker-notice-action-desktop" ); this.markup.createNode({ nodeType: "span", parent: toolbar, text: HighlightersBundle.formatValueSync("remote-node-picker-notice", { action: actionStr, }), attributes: { id: this.infoNoticeElementId, }, }); this.markup.createNode({ nodeType: "button", parent: toolbar, text: HighlightersBundle.formatValueSync( "remote-node-picker-notice-hide-button" ), attributes: { id: this.hideButtonId, }, }); return container; }; destroy() { // hide will nullify take care of this.#abortController. this.hide(); this.markup.destroy(); this.#highlighterEnvironment = null; this.#previousHoveredElement = null; } /** * We can't use event listener directly on the anonymous content because they aren't * working while the page is paused. * This is called from the NodePicker instance for easier events management. * * @param {ClickEvent} */ onClick(e) { const target = e.originalTarget || e.target; const targetId = target?.id; if (targetId === this.hideButtonId) { this.hide(); } } /** * Since we can't use :hover in the CSS for the anonymous content as it wouldn't work * when the page is paused, we have to roll our own implementation, adding a `.hover` * class for the element we want to style on hover (e.g. the close button). * This is called from the NodePicker instance for easier events management. * * @param {MouseMoveEvent} */ handleHoveredElement(e) { const hideButton = this.markup.getElement(this.hideButtonId); const target = e.originalTarget || e.target; const targetId = target?.id; // If the user didn't change targets, do nothing if (this.#previousHoveredElement?.id === targetId) { return; } if (targetId === this.hideButtonId) { hideButton.classList.add("hover"); } else { hideButton.classList.remove("hover"); } this.#previousHoveredElement = target; } getMarkupRootElement() { return this.markup.getElement(this.rootElementId); } async show() { if (this.#highlighterEnvironment.isXUL) { return false; } await this.isReady; // Show the highlighter's root element. const root = this.getMarkupRootElement(); root.removeAttribute("hidden"); root.setAttribute("overlay", "true"); return true; } hide() { if (this.#highlighterEnvironment.isXUL) { return; } // Hide the overlay. this.getMarkupRootElement().setAttribute("hidden", "true"); // Reset the hover state this.markup.getElement(this.hideButtonId).classList.remove("hover"); this.#previousHoveredElement = null; } } exports.RemoteNodePickerNotice = RemoteNodePickerNotice;