summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/highlighters/remote-node-picker-notice.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/highlighters/remote-node-picker-notice.js')
-rw-r--r--devtools/server/actors/highlighters/remote-node-picker-notice.js188
1 files changed, 188 insertions, 0 deletions
diff --git a/devtools/server/actors/highlighters/remote-node-picker-notice.js b/devtools/server/actors/highlighters/remote-node-picker-notice.js
new file mode 100644
index 0000000000..64b131d2a2
--- /dev/null
+++ b/devtools/server/actors/highlighters/remote-node-picker-notice.js
@@ -0,0 +1,188 @@
+/* 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;