tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

paused-debugger.js (6900B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const {
      8  CanvasFrameAnonymousContentHelper,
      9 } = require("resource://devtools/server/actors/highlighters/utils/markup.js");
     10 
     11 loader.lazyGetter(this, "PausedReasonsBundle", () => {
     12  return new Localization(
     13    ["devtools/shared/debugger-paused-reasons.ftl"],
     14    true
     15  );
     16 });
     17 
     18 /**
     19 * The PausedDebuggerOverlay is a class that displays a semi-transparent mask on top of
     20 * the whole page and a toolbar at the top of the page.
     21 * This is used to signal to users that script execution is current paused.
     22 * The toolbar is used to display the reason for the pause in script execution as well as
     23 * buttons to resume or step through the program.
     24 */
     25 class PausedDebuggerOverlay {
     26  constructor(highlighterEnv, options = {}) {
     27    this.env = highlighterEnv;
     28    this.resume = options.resume;
     29    this.stepOver = options.stepOver;
     30 
     31    this.lastTarget = null;
     32 
     33    this.markup = new CanvasFrameAnonymousContentHelper(
     34      highlighterEnv,
     35      this._buildMarkup.bind(this),
     36      {
     37        contentRootHostClassName: "devtools-highlighter-paused-debugger",
     38        waitForDocumentToLoad: false,
     39      }
     40    );
     41    this.isReady = this.markup.initialize();
     42  }
     43 
     44  _buildMarkup() {
     45    const container = this.markup.createNode({
     46      attributes: { class: "highlighter-container" },
     47    });
     48 
     49    // Wrapper element.
     50    const wrapper = this.markup.createNode({
     51      parent: container,
     52      attributes: {
     53        id: "paused-dbg-root",
     54        class: "paused-dbg-root",
     55        hidden: "true",
     56        overlay: "true",
     57      },
     58    });
     59 
     60    const toolbar = this.markup.createNode({
     61      parent: wrapper,
     62      attributes: {
     63        id: "paused-dbg-toolbar",
     64        class: "paused-dbg-toolbar",
     65      },
     66    });
     67 
     68    this.markup.createNode({
     69      nodeType: "span",
     70      parent: toolbar,
     71      attributes: {
     72        id: "paused-dbg-reason",
     73        class: "paused-dbg-reason",
     74      },
     75      text: PausedReasonsBundle.formatValueSync("whypaused-other"),
     76    });
     77 
     78    this.markup.createNode({
     79      parent: toolbar,
     80      attributes: {
     81        id: "paused-dbg-divider",
     82        class: "paused-dbg-divider",
     83      },
     84    });
     85 
     86    const stepWrapper = this.markup.createNode({
     87      parent: toolbar,
     88      attributes: {
     89        id: "paused-dbg-step-button-wrapper",
     90        class: "paused-dbg-step-button-wrapper",
     91      },
     92    });
     93 
     94    this.markup.createNode({
     95      nodeType: "button",
     96      parent: stepWrapper,
     97      attributes: {
     98        id: "paused-dbg-step-button",
     99        class: "paused-dbg-step-button",
    100      },
    101    });
    102 
    103    const resumeWrapper = this.markup.createNode({
    104      parent: toolbar,
    105      attributes: {
    106        id: "paused-dbg-resume-button-wrapper",
    107        class: "paused-dbg-resume-button-wrapper",
    108      },
    109    });
    110 
    111    this.markup.createNode({
    112      nodeType: "button",
    113      parent: resumeWrapper,
    114      attributes: {
    115        id: "paused-dbg-resume-button",
    116        class: "paused-dbg-resume-button",
    117      },
    118    });
    119 
    120    return container;
    121  }
    122 
    123  destroy() {
    124    this.hide();
    125    this.markup.destroy();
    126    this.env = null;
    127    this.lastTarget = null;
    128  }
    129 
    130  onClick(target) {
    131    const { id } = target;
    132    if (!id) {
    133      return;
    134    }
    135 
    136    if (id.includes("paused-dbg-step-button")) {
    137      this.stepOver();
    138    } else if (id.includes("paused-dbg-resume-button")) {
    139      this.resume();
    140    }
    141  }
    142 
    143  onMouseMove(target) {
    144    // Not an element we care about
    145    if (!target || !target.id) {
    146      return;
    147    }
    148 
    149    // If the user didn't change targets, do nothing
    150    if (this.lastTarget && this.lastTarget.id === target.id) {
    151      return;
    152    }
    153 
    154    if (
    155      target.id.includes("step-button") ||
    156      target.id.includes("resume-button")
    157    ) {
    158      // The hover should be applied to the wrapper (icon's parent node)
    159      const newTarget = target.parentNode.id.includes("wrapper")
    160        ? target.parentNode
    161        : target;
    162 
    163      // Remove the hover class if the user has changed buttons
    164      if (this.lastTarget && this.lastTarget != newTarget) {
    165        this.lastTarget.classList.remove("hover");
    166      }
    167      newTarget.classList.add("hover");
    168      this.lastTarget = newTarget;
    169    } else if (this.lastTarget) {
    170      // Remove the hover class if the user isn't on a button
    171      this.lastTarget.classList.remove("hover");
    172    }
    173  }
    174 
    175  handleEvent(e) {
    176    switch (e.type) {
    177      case "mousedown":
    178        this.onClick(e.target);
    179        break;
    180      case "DOMMouseScroll":
    181        // Prevent scrolling. That's because we only took a screenshot of the viewport, so
    182        // scrolling out of the viewport wouldn't draw the expected things. In the future
    183        // we can take the screenshot again on scroll, but for now it doesn't seem
    184        // important.
    185        e.preventDefault();
    186        break;
    187 
    188      case "mousemove":
    189        this.onMouseMove(e.target);
    190        break;
    191    }
    192  }
    193 
    194  getElement(id) {
    195    return this.markup.getElement(id);
    196  }
    197 
    198  show(reason) {
    199    if (this.env.isXUL || !reason) {
    200      return false;
    201    }
    202 
    203    // Only track mouse movement when the the overlay is shown
    204    // Prevents mouse tracking when the user isn't paused
    205    const { pageListenerTarget } = this.env;
    206    pageListenerTarget.addEventListener("mousemove", this);
    207 
    208    // Show the highlighter's root element.
    209    const root = this.getElement("paused-dbg-root");
    210    root.removeAttribute("hidden");
    211    root.setAttribute("overlay", "true");
    212 
    213    // Set the text to appear in the toolbar.
    214    const toolbar = this.getElement("paused-dbg-toolbar");
    215    toolbar.removeAttribute("hidden");
    216 
    217    // When the debugger pauses execution in a page, events will not be delivered
    218    // to any handlers added to elements on that page. So here we use the
    219    // document's setSuppressedEventListener interface to still be able to act on mouse
    220    // events (they'll be handled by the `handleEvent` method)
    221    this.env.window.document.setSuppressedEventListener(this);
    222    // Ensure layout is initialized so that we show the highlighter no matter what,
    223    // even if the page is not done loading, see bug 1580394.
    224    this.env.window.document.documentElement?.getBoundingClientRect();
    225    return true;
    226  }
    227 
    228  hide() {
    229    if (this.env.isXUL) {
    230      return;
    231    }
    232 
    233    const { pageListenerTarget } = this.env;
    234    pageListenerTarget.removeEventListener("mousemove", this);
    235 
    236    // Hide the overlay.
    237    this.getElement("paused-dbg-root").setAttribute("hidden", "true");
    238    // Remove the hover state
    239    this.getElement("paused-dbg-step-button-wrapper").classList?.remove(
    240      "hover"
    241    );
    242    this.getElement("paused-dbg-resume-button-wrapper").classList?.remove(
    243      "hover"
    244    );
    245  }
    246 }
    247 exports.PausedDebuggerOverlay = PausedDebuggerOverlay;