tor-browser

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

parent-process-document-event.js (7066B)


      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  WILL_NAVIGATE_TIME_SHIFT,
      9 } = require("resource://devtools/server/actors/webconsole/listeners/document-events.js");
     10 
     11 class ParentProcessDocumentEventWatcher {
     12  /**
     13   * Start watching, from the parent process, for DOCUMENT_EVENT's "will-navigate" event related to a given Watcher Actor.
     14   *
     15   * All other DOCUMENT_EVENT events are implemented from another watcher class, running in the content process.
     16   * Note that this other content process watcher will also emit one special edgecase of will-navigate
     17   * retlated to the iframe dropdown menu.
     18   *
     19   * We have to move listen for navigation in the parent to better handle bfcache navigations
     20   * and more generally all navigations which are initiated from the parent process.
     21   * 'bfcacheInParent' feature enabled many types of navigations to be controlled from the parent process.
     22   *
     23   * This was especially important to have this implementation in the parent
     24   * because the navigation event may be fired too late in the content process.
     25   * Leading to will-navigate being emitted *after* the new target we navigate to is notified to the client.
     26   *
     27   * @param WatcherActor watcherActor
     28   *        The watcher actor from which we should observe document event
     29   * @param Object options
     30   *        Dictionary object with following attributes:
     31   *        - onAvailable: mandatory function
     32   *          This will be called for each resource.
     33   */
     34  async watch(watcherActor, { onAvailable }) {
     35    this.watcherActor = watcherActor;
     36    this.onAvailable = onAvailable;
     37 
     38    // List of listeners keyed by innerWindowId.
     39    // Listeners are called as soon as we emitted the will-navigate
     40    // resource for the related WindowGlobal.
     41    this._onceWillNavigate = new Map();
     42 
     43    // Filter browsing contexts to only have the top BrowsingContext of each tree of BrowsingContexts…
     44    const topLevelBrowsingContexts = this.watcherActor
     45      .getAllBrowsingContexts()
     46      .filter(browsingContext => browsingContext.top == browsingContext);
     47 
     48    // Only register one WebProgressListener per BrowsingContext tree.
     49    // We will be notified about children BrowsingContext navigations/state changes via the top level BrowsingContextWebProgressListener,
     50    // and BrowsingContextWebProgress.browsingContext attribute will be updated dynamically everytime
     51    // we get notified about a child BrowsingContext.
     52    // Note that regular web page toolbox will only have one BrowsingContext tree, for the given tab.
     53    // But the Browser Toolbox will have many trees to listen to, one per top-level Window, and also one per tab,
     54    // as tabs's BrowsingContext context aren't children of their top level window!
     55    //
     56    // Also save the WebProgress and not the BrowsingContext because `BrowsingContext.webProgress` will be undefined in destroy(),
     57    // while it is still valuable to call `webProgress.removeProgressListener`. Otherwise events keeps being notified!!
     58    this.webProgresses = topLevelBrowsingContexts.map(
     59      browsingContext => browsingContext.webProgress
     60    );
     61    this.webProgresses.forEach(webProgress => {
     62      webProgress.addProgressListener(
     63        this,
     64        Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
     65      );
     66    });
     67  }
     68 
     69  /**
     70   * Wait for the emission of will-navigate for a given WindowGlobal
     71   *
     72   * @param Number innerWindowId
     73   *        WindowGlobal's id we want to track
     74   * @return Promise
     75   *         Resolves immediatly if the WindowGlobal isn't tracked by any target
     76   *         -or- resolve later, once the WindowGlobal navigates to another document
     77   *         and will-navigate has been emitted.
     78   */
     79  onceWillNavigateIsEmitted(innerWindowId) {
     80    // Only delay the target-destroyed event if the target is for BrowsingContext for which we will emit will-navigate
     81    const isTracked = this.webProgresses.find(
     82      webProgress =>
     83        webProgress.browsingContext.currentWindowGlobal?.innerWindowId ==
     84        innerWindowId
     85    );
     86    if (isTracked) {
     87      return new Promise(resolve => {
     88        this._onceWillNavigate.set(innerWindowId, resolve);
     89      });
     90    }
     91    return Promise.resolve();
     92  }
     93 
     94  onStateChange(progress, request, flag) {
     95    const isStart = flag & Ci.nsIWebProgressListener.STATE_START;
     96    const isDocument = flag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
     97    if (isDocument && isStart) {
     98      const { browsingContext } = progress;
     99      // Ignore if we are still on the initial document,
    100      // as that's the navigation from it (about:blank) to the actual first location.
    101      // The target isn't created yet.
    102      if (browsingContext.currentWindowGlobal.isUncommittedInitialDocument) {
    103        return;
    104      }
    105 
    106      // Never emit will-navigate for content browsing contexts in the Browser Toolbox.
    107      // They might verify `browsingContext.top == browsingContext` because of the chrome/content
    108      // boundary, but they do not represent a top-level target for this DevTools session.
    109      if (
    110        this.watcherActor.sessionContext.type == "all" &&
    111        browsingContext.isContent
    112      ) {
    113        return;
    114      }
    115      // Also ignore will-navigate for Web Extensions as we shouldn't clear things up when
    116      // any of its WindowGlobal starts navigating away. Instead things should be cleared when
    117      // we destroy the targets.
    118      if (this.watcherActor.sessionContext.type == "webextension") {
    119        return;
    120      }
    121 
    122      // Only emit will-navigate for top-level targets.
    123      const isTopLevel = browsingContext.top == browsingContext;
    124      if (!isTopLevel) {
    125        return;
    126      }
    127 
    128      const newURI = request instanceof Ci.nsIChannel ? request.URI.spec : null;
    129      const { innerWindowId } = browsingContext.currentWindowGlobal;
    130      this.onAvailable([
    131        {
    132          browsingContextID: browsingContext.id,
    133          innerWindowId,
    134          name: "will-navigate",
    135          time: Date.now() - WILL_NAVIGATE_TIME_SHIFT,
    136          isFrameSwitching: false,
    137          newURI,
    138        },
    139      ]);
    140      const callback = this._onceWillNavigate.get(innerWindowId);
    141      if (callback) {
    142        this._onceWillNavigate.delete(innerWindowId);
    143        callback();
    144      }
    145 
    146      // Also emit the event on the watcher actor for other actors to catch this event
    147      if (browsingContext == this.watcherActor.browserElement.browsingContext) {
    148        this.watcherActor.emit("top-browsing-context-will-navigate");
    149      }
    150    }
    151  }
    152 
    153  get QueryInterface() {
    154    return ChromeUtils.generateQI([
    155      "nsIWebProgressListener",
    156      "nsISupportsWeakReference",
    157    ]);
    158  }
    159 
    160  destroy() {
    161    this.webProgresses.forEach(webProgress => {
    162      webProgress.removeProgressListener(
    163        this,
    164        Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
    165      );
    166    });
    167    this.webProgresses = null;
    168  }
    169 }
    170 
    171 module.exports = ParentProcessDocumentEventWatcher;