tor-browser

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

inspector.js (9516B)


      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  FrontClassWithSpec,
      9  registerFront,
     10 } = require("resource://devtools/shared/protocol.js");
     11 const {
     12  inspectorSpec,
     13 } = require("resource://devtools/shared/specs/inspector.js");
     14 
     15 loader.lazyRequireGetter(
     16  this,
     17  "captureScreenshot",
     18  "resource://devtools/client/shared/screenshot.js",
     19  true
     20 );
     21 const lazy = {};
     22 
     23 ChromeUtils.defineESModuleGetters(lazy, {
     24  TYPES: "resource://devtools/shared/highlighters.mjs",
     25 });
     26 
     27 const SHOW_ALL_ANONYMOUS_CONTENT_PREF =
     28  "devtools.inspector.showAllAnonymousContent";
     29 
     30 /**
     31 * Client side of the inspector actor, which is used to create
     32 * inspector-related actors, including the walker.
     33 */
     34 class InspectorFront extends FrontClassWithSpec(inspectorSpec) {
     35  constructor(client, targetFront, parentFront) {
     36    super(client, targetFront, parentFront);
     37 
     38    this._client = client;
     39    this._highlighters = new Map();
     40 
     41    // Attribute name from which to retrieve the actorID out of the target actor's form
     42    this.formAttributeName = "inspectorActor";
     43 
     44    // Map of highlighter types to unsettled promises to create a highlighter of that type
     45    this._pendingGetHighlighterMap = new Map();
     46 
     47    this.noopStylesheetListener = () => {};
     48  }
     49 
     50  // async initialization
     51  async initialize() {
     52    if (this.initialized) {
     53      return this.initialized;
     54    }
     55 
     56    // Watch STYLESHEET resources to fill the ResourceCommand cache.
     57    // StyleRule front's `get parentStyleSheet()` will query the cache to
     58    // retrieve the resource corresponding to the parent stylesheet of a rule.
     59    const { resourceCommand } = this.targetFront.commands;
     60    // Backup resourceCommand, targetFront.commands might be null in `destroy`.
     61    this.resourceCommand = resourceCommand;
     62    await resourceCommand.watchResources([resourceCommand.TYPES.STYLESHEET], {
     63      onAvailable: this.noopStylesheetListener,
     64    });
     65 
     66    // Bail out if the inspector is closed while watchResources was pending
     67    if (this.isDestroyed()) {
     68      return null;
     69    }
     70 
     71    const promises = [this._getWalker(), this._getPageStyle()];
     72    if (this.targetFront.commands.descriptorFront.isTabDescriptor) {
     73      promises.push(this._enableViewportSizeOnResizeHighlighter());
     74    }
     75 
     76    this.initialized = await Promise.all(promises);
     77 
     78    return this.initialized;
     79  }
     80 
     81  async _getWalker() {
     82    const showAllAnonymousContent = Services.prefs.getBoolPref(
     83      SHOW_ALL_ANONYMOUS_CONTENT_PREF
     84    );
     85    this.walker = await this.getWalker({
     86      showAllAnonymousContent,
     87    });
     88 
     89    // We need to reparent the RootNode of remote iframe Walkers
     90    // so that their parent is the NodeFront of the <iframe>
     91    // element, coming from another process/target/WalkerFront.
     92    await this.walker.reparentRemoteFrame();
     93  }
     94 
     95  hasHighlighter(type) {
     96    return this._highlighters.has(type);
     97  }
     98 
     99  async _getPageStyle() {
    100    this.pageStyle = await super.getPageStyle();
    101  }
    102 
    103  async _enableViewportSizeOnResizeHighlighter() {
    104    const highlighter = await this.getOrCreateHighlighterByType(
    105      lazy.TYPES.VIEWPORT_SIZE_ON_RESIZE
    106    );
    107    await highlighter.show(this);
    108  }
    109 
    110  async getCompatibilityFront() {
    111    if (!this._compatibility) {
    112      this._compatibility = await super.getCompatibility();
    113    }
    114 
    115    return this._compatibility;
    116  }
    117 
    118  destroy() {
    119    if (this.isDestroyed()) {
    120      return;
    121    }
    122    this._compatibility = null;
    123 
    124    const { resourceCommand } = this;
    125    resourceCommand.unwatchResources([resourceCommand.TYPES.STYLESHEET], {
    126      onAvailable: this.noopStylesheetListener,
    127    });
    128    this.resourceCommand = null;
    129 
    130    this.walker = null;
    131 
    132    // CustomHighlighter fronts are managed by InspectorFront and so will be
    133    // automatically destroyed. But we have to clear the `_highlighters`
    134    // Map as well as explicitly call `finalize` request on all of them.
    135    this.destroyHighlighters();
    136    super.destroy();
    137  }
    138 
    139  destroyHighlighters() {
    140    for (const type of this._highlighters.keys()) {
    141      if (this._highlighters.has(type)) {
    142        const highlighter = this._highlighters.get(type);
    143        if (!highlighter.isDestroyed()) {
    144          highlighter.finalize();
    145        }
    146        this._highlighters.delete(type);
    147      }
    148    }
    149  }
    150 
    151  async getHighlighterByType(typeName) {
    152    let highlighter = null;
    153    try {
    154      highlighter = await super.getHighlighterByType(typeName);
    155    } catch (_) {
    156      throw new Error(
    157        "The target doesn't support " +
    158          `creating highlighters by types or ${typeName} is unknown`
    159      );
    160    }
    161    return highlighter;
    162  }
    163 
    164  getKnownHighlighter(type) {
    165    return this._highlighters.get(type);
    166  }
    167 
    168  /**
    169   * Return a highlighter instance of the given type.
    170   * If an instance was previously created, return it. Else, create and return a new one.
    171   *
    172   * Store a promise for the request to create a new highlighter. If another request
    173   * comes in before that promise is resolved, wait for it to resolve and return the
    174   * highlighter instance it resolved with instead of creating a new request.
    175   *
    176   * @param  {string} type
    177   *         Highlighter type
    178   * @return {Promise}
    179   *         Promise which resolves with a highlighter instance of the given type
    180   */
    181  async getOrCreateHighlighterByType(type) {
    182    let front = this._highlighters.get(type);
    183    let pendingGetHighlighter = this._pendingGetHighlighterMap.get(type);
    184 
    185    if (!front && !pendingGetHighlighter) {
    186      pendingGetHighlighter = (async () => {
    187        const highlighter = await this.getHighlighterByType(type);
    188        this._highlighters.set(type, highlighter);
    189        this._pendingGetHighlighterMap.delete(type);
    190        return highlighter;
    191      })();
    192 
    193      this._pendingGetHighlighterMap.set(type, pendingGetHighlighter);
    194    }
    195 
    196    if (pendingGetHighlighter) {
    197      front = await pendingGetHighlighter;
    198    }
    199 
    200    return front;
    201  }
    202 
    203  async pickColorFromPage(options) {
    204    let screenshot = null;
    205 
    206    // @backward-compat { version 87 } ScreenshotContentActor was only added in 87.
    207    //                  When connecting to older server, the eyedropper will  use drawWindow
    208    //                  to retrieve the screenshot of the page (that's a decent fallback,
    209    //                  even if it doesn't handle remote frames).
    210    if (this.targetFront.hasActor("screenshotContent")) {
    211      try {
    212        // We use the screenshot actors as it can retrieve an image of the current viewport,
    213        // handling remote frame if need be.
    214        const { data } = await captureScreenshot(this.targetFront, {
    215          browsingContextID: this.targetFront.browsingContextID,
    216          disableFlash: true,
    217          ignoreDprForFileScale: true,
    218        });
    219        screenshot = data;
    220      } catch (e) {
    221        // We simply log the error and still call pickColorFromPage as it will default to
    222        // use drawWindow in order to get the screenshot of the page (that's a decent
    223        // fallback, even if it doesn't handle remote frames).
    224        console.error(
    225          "Error occured when taking a screenshot for the eyedropper",
    226          e
    227        );
    228      }
    229    }
    230 
    231    await super.pickColorFromPage({
    232      ...options,
    233      screenshot,
    234    });
    235 
    236    if (options?.fromMenu) {
    237      Glean.devtools.menuEyedropperOpenedCount.add(1);
    238    } else {
    239      Glean.devtools.eyedropperOpenedCount.add(1);
    240    }
    241  }
    242 
    243  /**
    244   * Given a node grip, return a NodeFront on the right context.
    245   *
    246   * @param {object} grip: The node grip.
    247   * @returns {Promise<NodeFront|null>} A promise that resolves with  a NodeFront or null
    248   *                                    if the NodeFront couldn't be created/retrieved.
    249   */
    250  async getNodeFrontFromNodeGrip(grip) {
    251    return this.getNodeActorFromContentDomReference(grip.contentDomReference);
    252  }
    253 
    254  async getNodeActorFromContentDomReference(contentDomReference) {
    255    const { browsingContextId } = contentDomReference;
    256    // If the contentDomReference lives in the same browsing context id than the
    257    // current one, we can directly use the current walker.
    258    if (this.targetFront.browsingContextID === browsingContextId) {
    259      return this.walker.getNodeActorFromContentDomReference(
    260        contentDomReference
    261      );
    262    }
    263 
    264    // If the contentDomReference has a different browsing context than the current one,
    265    // we are either in Fission or in the Multiprocess Browser Toolbox, so we need to
    266    // retrieve the walker of the WindowGlobalTarget.
    267    // Get the target for this remote frame element
    268 
    269    // Tab and Process Descriptors expose a Watcher, which should be used to
    270    // fetch the node's target.
    271    let target;
    272    const { watcherFront } = this.targetFront.commands;
    273    if (watcherFront) {
    274      target = await watcherFront.getWindowGlobalTarget(browsingContextId);
    275    } else {
    276      // For descriptors which don't expose a watcher (e.g. WebExtension)
    277      // we used to call RootActor::getBrowsingContextDescriptor, but it was
    278      // removed in FF77.
    279      // Support for watcher in WebExtension descriptors is Bug 1644341.
    280      throw new Error(
    281        `Unable to call getNodeActorFromContentDomReference for ${this.targetFront.actorID}`
    282      );
    283    }
    284    const { walker } = await target.getFront("inspector");
    285    return walker.getNodeActorFromContentDomReference(contentDomReference);
    286  }
    287 }
    288 
    289 registerFront(InspectorFront);