tor-browser

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

GeckoViewTab.sys.mjs (6624B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs";
      6 
      7 import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
      8 
      9 const { ExtensionError } = ExtensionUtils;
     10 
     11 const lazy = {};
     12 
     13 ChromeUtils.defineESModuleGetters(lazy, {
     14  EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
     15  mobileWindowTracker: "resource://gre/modules/GeckoViewWebExtension.sys.mjs",
     16 });
     17 
     18 class Tab {
     19  constructor(window) {
     20    this.id = GeckoViewTabBridge.windowIdToTabId(window.docShell.outerWindowID);
     21    this.browser = window.browser;
     22    this.active = false;
     23  }
     24 
     25  get linkedBrowser() {
     26    return this.browser;
     27  }
     28 
     29  getActive() {
     30    return this.active;
     31  }
     32 
     33  get userContextId() {
     34    return this.browser.ownerGlobal.moduleManager.settings
     35      .unsafeSessionContextId;
     36  }
     37 }
     38 
     39 // Because of bug 1410749, we can't use 0, though, and just to be safe
     40 // we choose a value that is unlikely to overlap with Fennec's tab IDs.
     41 const TAB_ID_BASE = 10000;
     42 
     43 export const GeckoViewTabBridge = {
     44  /**
     45   * Converts windowId to tabId as in GeckoView every browser window has exactly one tab.
     46   *
     47   * @param {number} windowId outerWindowId
     48   *
     49   * @returns {number} tabId
     50   */
     51  windowIdToTabId(windowId) {
     52    return TAB_ID_BASE + windowId;
     53  },
     54 
     55  /**
     56   * Converts tabId to windowId.
     57   *
     58   * @param {number} tabId
     59   *
     60   * @returns {number}
     61   *          outerWindowId of browser window to which the tab belongs.
     62   */
     63  tabIdToWindowId(tabId) {
     64    return tabId - TAB_ID_BASE;
     65  },
     66 
     67  /**
     68   * Delegates openOptionsPage handling to the app.
     69   *
     70   * @param {number} extensionId
     71   *        The ID of the extension requesting the options menu.
     72   *
     73   * @returns {Promise<Void>}
     74   *          A promise resolved after successful handling.
     75   */
     76  async openOptionsPage(extensionId) {
     77    debug`openOptionsPage for extensionId ${extensionId}`;
     78 
     79    try {
     80      await lazy.EventDispatcher.instance.sendRequestForResult({
     81        type: "GeckoView:WebExtension:OpenOptionsPage",
     82        extensionId,
     83      });
     84    } catch (errorMessage) {
     85      // The error message coming from GeckoView is about :OpenOptionsPage not
     86      // being registered so we need to have one that's extension friendly
     87      // here.
     88      throw new ExtensionError("runtime.openOptionsPage is not supported");
     89    }
     90  },
     91 
     92  /**
     93   * Request the GeckoView App to create a new tab (GeckoSession).
     94   *
     95   * @param {object} options
     96   * @param {string} options.extensionId
     97   *        The ID of the extension that requested a new tab.
     98   * @param {object} options.createProperties
     99   *        The properties for the new tab, see tabs.create reference for details.
    100   *
    101   * @returns {Promise<Tab>}
    102   *          A promise resolved to the newly created tab.
    103   * @throws {Error}
    104   *         Throws an error if the GeckoView app doesn't support tabs.create or fails to handle the request.
    105   */
    106  async createNewTab({ extensionId, createProperties } = {}) {
    107    debug`createNewTab`;
    108 
    109    const newSessionId = Services.uuid
    110      .generateUUID()
    111      .toString()
    112      .slice(1, -1)
    113      .replace(/-/g, "");
    114 
    115    // The window might already be open by the time we get the response, so we
    116    // need to start waiting before we send the message.
    117    const windowPromise = new Promise(resolve => {
    118      const handler = {
    119        observe(aSubject, aTopic) {
    120          if (
    121            aTopic === "geckoview-window-created" &&
    122            aSubject.name === newSessionId
    123          ) {
    124            Services.obs.removeObserver(handler, "geckoview-window-created");
    125            resolve(aSubject);
    126          }
    127        },
    128      };
    129      Services.obs.addObserver(handler, "geckoview-window-created");
    130    });
    131 
    132    let didOpenSession = false;
    133    try {
    134      didOpenSession = await lazy.EventDispatcher.instance.sendRequestForResult(
    135        {
    136          type: "GeckoView:WebExtension:NewTab",
    137          extensionId,
    138          createProperties,
    139          newSessionId,
    140        }
    141      );
    142    } catch (errorMessage) {
    143      // The error message coming from GeckoView is about :NewTab not being
    144      // registered so we need to have one that's extension friendly here.
    145      throw new ExtensionError("tabs.create is not supported");
    146    }
    147 
    148    if (!didOpenSession) {
    149      throw new ExtensionError("Cannot create new tab");
    150    }
    151 
    152    const window = await windowPromise;
    153    if (!window.tab) {
    154      window.tab = new Tab(window);
    155    }
    156    return window.tab;
    157  },
    158 
    159  /**
    160   * Request the GeckoView App to close a tab (GeckoSession).
    161   *
    162   * @param {object} options
    163   * @param {Window} options.window The window owning the tab to close
    164   * @param {string} options.extensionId
    165   *
    166   * @returns {Promise<Void>}
    167   *          A promise resolved after GeckoSession is closed.
    168   * @throws {Error}
    169   *         Throws an error if the GeckoView app doesn't allow extension to close tab.
    170   */
    171  async closeTab({ window, extensionId } = {}) {
    172    try {
    173      await window.WindowEventDispatcher.sendRequestForResult({
    174        type: "GeckoView:WebExtension:CloseTab",
    175        extensionId,
    176      });
    177    } catch (errorMessage) {
    178      throw new ExtensionError(errorMessage);
    179    }
    180  },
    181 
    182  async updateTab({ window, extensionId, updateProperties } = {}) {
    183    try {
    184      await window.WindowEventDispatcher.sendRequestForResult({
    185        type: "GeckoView:WebExtension:UpdateTab",
    186        extensionId,
    187        updateProperties,
    188      });
    189    } catch (errorMessage) {
    190      throw new ExtensionError(errorMessage);
    191    }
    192  },
    193 };
    194 
    195 export class GeckoViewTab extends GeckoViewModule {
    196  onInit() {
    197    const { window } = this;
    198    if (!window.tab) {
    199      window.tab = new Tab(window);
    200    }
    201 
    202    this.registerListener([
    203      "GeckoView:WebExtension:SetTabActive",
    204      "GeckoView:FlushSessionState",
    205    ]);
    206  }
    207 
    208  onEvent(aEvent, aData) {
    209    debug`onEvent: event=${aEvent}, data=${aData}`;
    210 
    211    switch (aEvent) {
    212      case "GeckoView:WebExtension:SetTabActive": {
    213        const { active } = aData;
    214        lazy.mobileWindowTracker.setTabActive(this.window, active);
    215        break;
    216      }
    217      case "GeckoView:FlushSessionState": {
    218        if (Services.appinfo.sessionHistoryInParent) {
    219          if (this.browser && this.browser.frameLoader) {
    220            this.browser.frameLoader.requestTabStateFlush();
    221          }
    222        }
    223        break;
    224      }
    225    }
    226  }
    227 }
    228 
    229 const { debug, warn } = GeckoViewTab.initLogging("GeckoViewTab");