tor-browser

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

NavigableManager.sys.mjs (9751B)


      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 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  BiMap: "chrome://remote/content/shared/BiMap.sys.mjs",
      9  BrowsingContextListener:
     10    "chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs",
     11  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
     12  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
     13 });
     14 
     15 /**
     16 * The navigable manager is intended to be used as a singleton and is
     17 * responsible for tracking open browsing contexts by assigning each a
     18 * unique identifier. This allows them to be referenced unambiguously.
     19 * For top-level browsing contexts, the content browser instance itself
     20 * is used as the anchor, since cross-origin navigations can result in
     21 * browsing context replacements. Using the browser as a stable reference
     22 * ensures that protocols like WebDriver BiDi and Marionette can reliably
     23 * point to the intended "navigable" — a concept from the HTML specification
     24 * that is not implemented in Firefox.
     25 */
     26 class NavigableManagerClass {
     27  #browserIds;
     28  #chromeNavigables;
     29  #contextListener;
     30  #navigableIds;
     31  #tracking;
     32 
     33  constructor() {
     34    this.#tracking = false;
     35 
     36    // Maps browser's `permanentKey` to an uuid: WeakMap.<Object, string>
     37    //
     38    // It's required as a fallback, since in the case when a context was
     39    // discarded embedderElement is gone, and we cannot retrieve the
     40    // context id from the formerly known browser.
     41    this.#browserIds = new WeakMap();
     42 
     43    // Maps canonical browsing contexts from the parent process
     44    // to a uuid and vice versa.
     45    this.#chromeNavigables = new lazy.BiMap();
     46 
     47    // Maps browsing contexts to uuid: WeakMap.<BrowsingContext, string>.
     48    this.#navigableIds = new WeakMap();
     49 
     50    // Start tracking by default when the class gets instantiated.
     51    this.startTracking();
     52  }
     53 
     54  /**
     55   * Retrieve the browser element corresponding to the provided unique id,
     56   * previously generated via getIdForBrowser.
     57   *
     58   * TODO: To avoid creating strong references on browser elements and
     59   * potentially leaking those elements, this method loops over all windows and
     60   * all tabs. It should be replaced by a faster implementation in Bug 1750065.
     61   *
     62   * @param {string} id
     63   *     A browser unique id created by getIdForBrowser.
     64   *
     65   * @returns {XULBrowser}
     66   *     The <xul:browser> corresponding to the provided id. Will return
     67   *     `null` if no matching browser element is found.
     68   */
     69  getBrowserById(id) {
     70    for (const tab of lazy.TabManager.allTabs) {
     71      const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
     72      if (this.getIdForBrowser(contentBrowser) == id) {
     73        return contentBrowser;
     74      }
     75    }
     76 
     77    return null;
     78  }
     79 
     80  /**
     81   * Retrieve the browsing context corresponding to the provided navigabl id.
     82   *
     83   * @param {string} id
     84   *     A browsing context unique id (created by getIdForBrowsingContext).
     85   *
     86   * @returns {BrowsingContext|null}
     87   *     The browsing context found for this id, null if none was found or
     88   *     browsing context is discarded.
     89   */
     90  getBrowsingContextById(id) {
     91    let browsingContext;
     92 
     93    if (this.#chromeNavigables.hasId(id)) {
     94      // Chrome browsing context
     95      browsingContext = this.#chromeNavigables.getObject(id);
     96    } else {
     97      // Content browsing context
     98      const browser = this.getBrowserById(id);
     99      if (browser) {
    100        // Top-level browsing context
    101        browsingContext = browser.browsingContext;
    102      } else {
    103        // Content child browsing contexts
    104        const context = BrowsingContext.get(id);
    105        if (context && context.isContent && context.parent) {
    106          browsingContext = context;
    107        }
    108      }
    109    }
    110 
    111    if (!browsingContext || browsingContext.isDiscarded) {
    112      return null;
    113    }
    114 
    115    return browsingContext;
    116  }
    117 
    118  /**
    119   * Retrieve the unique id for the given xul browser element. The id is a
    120   * dynamically generated uuid associated with the permanentKey property of the
    121   * given browser element. This method is preferable over getIdForBrowsingContext
    122   * in case of working with browser element of a tab, since we can not guarantee
    123   * that browsing context is attached to it.
    124   *
    125   * @param {XULBrowser} browser
    126   *     The <xul:browser> for which we want to retrieve the id.
    127   *
    128   * @returns {string|null}
    129   *     The unique id for this browser or `null` if invalid.
    130   */
    131  getIdForBrowser(browser) {
    132    if (!(XULElement.isInstance(browser) && browser.permanentKey)) {
    133      // Ignore those browsers that do not have a permanentKey
    134      // attached like the print preview (bug 1990485), but which
    135      // we need to uniquely identify a top-level browsing context.
    136      return null;
    137    }
    138 
    139    return this.#browserIds.getOrInsertComputed(
    140      browser.permanentKey,
    141      lazy.generateUUID
    142    );
    143  }
    144 
    145  /**
    146   * Retrieve the id of a Browsing Context.
    147   *
    148   * For a top-level browsing context a custom unique id will be returned.
    149   *
    150   * @param {BrowsingContext=} browsingContext
    151   *     The browsing context to get the id from.
    152   *
    153   * @returns {string|null}
    154   *     The unique id of the browsing context or `null` if invalid.
    155   */
    156  getIdForBrowsingContext(browsingContext) {
    157    if (!BrowsingContext.isInstance(browsingContext)) {
    158      return null;
    159    }
    160 
    161    if (!browsingContext.isContent) {
    162      // When the browsing context runs in the parent process we
    163      // can use the browsing context as key because it's stable.
    164      return this.#chromeNavigables.getOrInsert(browsingContext);
    165    }
    166 
    167    if (!browsingContext.parent) {
    168      // For top-level browsing contexts always try to use the browser
    169      // as navigable first because it survives a cross-process navigation.
    170      const browser = this.#getBrowserForBrowsingContext(browsingContext);
    171      if (browser) {
    172        return this.getIdForBrowser(browser);
    173      }
    174 
    175      // If no browser can be found fallback to use the navigable id instead.
    176      return this.#navigableIds.has(browsingContext)
    177        ? this.#navigableIds.get(browsingContext)
    178        : null;
    179    }
    180 
    181    // Child browsing context (frame)
    182    return browsingContext.id.toString();
    183  }
    184 
    185  /**
    186   * Get the navigable for the given browsing context.
    187   *
    188   * Because Gecko doesn't support the Navigable concept in content
    189   * scope the content browser could be used to uniquely identify
    190   * top-level browsing contexts.
    191   *
    192   * @param {BrowsingContext} browsingContext
    193   *
    194   * @returns {BrowsingContext|XULBrowser} The navigable
    195   *
    196   * @throws {TypeError}
    197   *     If `browsingContext` is not a CanonicalBrowsingContext instance.
    198   */
    199  getNavigableForBrowsingContext(browsingContext) {
    200    if (!lazy.TabManager.isValidCanonicalBrowsingContext(browsingContext)) {
    201      throw new TypeError(
    202        `Expected browsingContext to be a CanonicalBrowsingContext, got ${browsingContext}`
    203      );
    204    }
    205 
    206    if (browsingContext.isContent && browsingContext.parent === null) {
    207      return this.#getBrowserForBrowsingContext(browsingContext);
    208    }
    209 
    210    return browsingContext;
    211  }
    212 
    213  startTracking() {
    214    if (this.#tracking) {
    215      return;
    216    }
    217 
    218    this.#contextListener = new lazy.BrowsingContextListener();
    219    this.#contextListener.on("attached", this.#onContextAttached);
    220    this.#contextListener.on("discarded", this.#onContextDiscarded);
    221    this.#contextListener.startListening();
    222 
    223    // Register as well all browsing contexts from already open tabs.
    224    lazy.TabManager.getBrowsers().forEach(browser =>
    225      this.#setIdForBrowsingContext(browser.browsingContext)
    226    );
    227 
    228    this.#tracking = true;
    229  }
    230 
    231  stopTracking() {
    232    if (!this.#tracking) {
    233      return;
    234    }
    235 
    236    this.#contextListener.off("attached", this.#onContextAttached);
    237    this.#contextListener.off("discarded", this.#onContextDiscarded);
    238    this.#contextListener.stopListening();
    239    this.#contextListener = null;
    240 
    241    this.#chromeNavigables.clear();
    242    this.#browserIds = new WeakMap();
    243    this.#navigableIds = new WeakMap();
    244 
    245    this.#tracking = false;
    246  }
    247 
    248  /** Private methods */
    249 
    250  /**
    251   * Try to find the browser element to browsing context is attached to.
    252   *
    253   * @param {BrowsingContext} browsingContext
    254   *     The browsing context to find the related browser for.
    255   *
    256   * @returns {XULBrowser|null}
    257   *     The <xul:browser> element, or `null` if no browser exists.
    258   */
    259  #getBrowserForBrowsingContext(browsingContext) {
    260    return browsingContext.top.embedderElement
    261      ? browsingContext.top.embedderElement
    262      : null;
    263  }
    264 
    265  /**
    266   * Update the internal maps for a new browsing context.
    267   *
    268   * @param {BrowsingContext} browsingContext
    269   *     The browsing context that needs to be observed.
    270   */
    271  #setIdForBrowsingContext(browsingContext) {
    272    const id = this.getIdForBrowsingContext(browsingContext);
    273 
    274    // Add a fallback to the navigable weak map so that an id can
    275    // also be retrieved when the related browser was closed.
    276    this.#navigableIds.set(browsingContext, id);
    277  }
    278 
    279  /** Event handlers */
    280 
    281  #onContextAttached = (_, data = {}) => {
    282    const { browsingContext } = data;
    283 
    284    if (!browsingContext.isContent) {
    285      this.#chromeNavigables.getOrInsert(browsingContext);
    286      return;
    287    }
    288 
    289    this.#setIdForBrowsingContext(browsingContext);
    290  };
    291 
    292  #onContextDiscarded = (_, data = {}) => {
    293    const { browsingContext } = data;
    294 
    295    if (!browsingContext.isContent) {
    296      this.#chromeNavigables.deleteByObject(browsingContext);
    297    }
    298  };
    299 }
    300 
    301 // Expose a shared singleton.
    302 export const NavigableManager = new NavigableManagerClass();