tor-browser

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

ext-browserAction.js (6146B)


      1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim: set sts=2 sw=2 et tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 "use strict";
      8 
      9 ChromeUtils.defineESModuleGetters(this, {
     10  GeckoViewWebExtension: "resource://gre/modules/GeckoViewWebExtension.sys.mjs",
     11  ExtensionActionHelper: "resource://gre/modules/GeckoViewWebExtension.sys.mjs",
     12 });
     13 
     14 const { BrowserActionBase } = ChromeUtils.importESModule(
     15  "resource://gre/modules/ExtensionActions.sys.mjs"
     16 );
     17 
     18 const BROWSER_ACTION_PROPERTIES = [
     19  "title",
     20  "icon",
     21  "popup",
     22  "badgeText",
     23  "badgeBackgroundColor",
     24  "badgeTextColor",
     25  "enabled",
     26  "patternMatching",
     27 ];
     28 
     29 class BrowserAction extends BrowserActionBase {
     30  constructor(extension, clickDelegate) {
     31    const tabContext = new TabContext(() => this.getContextData(null));
     32    super(tabContext, extension);
     33    this.clickDelegate = clickDelegate;
     34    this.helper = new ExtensionActionHelper({
     35      extension,
     36      tabTracker,
     37      windowTracker,
     38      tabContext,
     39      properties: BROWSER_ACTION_PROPERTIES,
     40    });
     41  }
     42 
     43  updateOnChange(tab) {
     44    const tabId = tab ? tab.id : null;
     45    const action = tab
     46      ? this.getContextData(tab)
     47      : this.helper.extractProperties(this.globals);
     48    this.helper.sendRequest(tabId, {
     49      action,
     50      type: "GeckoView:BrowserAction:Update",
     51    });
     52  }
     53 
     54  openPopup(tab, openPopupWithoutUserInteraction = false) {
     55    const popupUri = openPopupWithoutUserInteraction
     56      ? this.getPopupUrl(tab)
     57      : this.triggerClickOrPopup(tab);
     58    const actionObject = this.getContextData(tab);
     59    const action = this.helper.extractProperties(actionObject);
     60    this.helper.sendRequest(tab.id, {
     61      action,
     62      type: "GeckoView:BrowserAction:OpenPopup",
     63      popupUri,
     64    });
     65  }
     66 
     67  triggerClickOrPopup(tab = tabTracker.activeTab) {
     68    return super.triggerClickOrPopup(tab);
     69  }
     70 
     71  getTab(tabId) {
     72    return this.helper.getTab(tabId);
     73  }
     74 
     75  getWindow(windowId) {
     76    return this.helper.getWindow(windowId);
     77  }
     78 
     79  dispatchClick() {
     80    this.clickDelegate.onClick();
     81  }
     82 }
     83 
     84 this.browserAction = class extends ExtensionAPIPersistent {
     85  static for(extension) {
     86    return GeckoViewWebExtension.browserActions.get(extension);
     87  }
     88 
     89  async onManifestEntry() {
     90    const { extension } = this;
     91    this.action = new BrowserAction(extension, this);
     92    await this.action.loadIconData();
     93 
     94    GeckoViewWebExtension.browserActions.set(extension, this.action);
     95 
     96    // Notify the embedder of this action
     97    this.action.updateOnChange(null);
     98  }
     99 
    100  onShutdown() {
    101    const { extension } = this;
    102    this.action.onShutdown();
    103    GeckoViewWebExtension.browserActions.delete(extension);
    104  }
    105 
    106  onClick() {
    107    this.emit("click", tabTracker.activeTab);
    108  }
    109 
    110  PERSISTENT_EVENTS = {
    111    onClicked({ fire }) {
    112      const { extension } = this;
    113      const { tabManager } = extension;
    114      async function listener(_event, tab) {
    115        if (fire.wakeup) {
    116          await fire.wakeup();
    117        }
    118        // TODO: we should double-check if the tab is already being closed by the time
    119        // the background script got started and we converted the primed listener.
    120        fire.sync(tabManager.convert(tab));
    121      }
    122      this.on("click", listener);
    123      return {
    124        unregister: () => {
    125          this.off("click", listener);
    126        },
    127        convert(newFire) {
    128          fire = newFire;
    129        },
    130      };
    131    },
    132    onUserSettingsChanged() {
    133      // isOnToolBar is not supported, so this event will never fire.
    134      // We stub out an implementation to avoid breaking extensions
    135      // that are compatible with desktop and mobile browsers
    136      return {
    137        unregister: () => {},
    138        convert() {},
    139      };
    140    },
    141  };
    142 
    143  getAPI(context) {
    144    const { extension } = context;
    145    const { action } = this;
    146    const namespace =
    147      extension.manifestVersion < 3 ? "browserAction" : "action";
    148 
    149    return {
    150      [namespace]: {
    151        ...action.api(context),
    152 
    153        onClicked: new EventManager({
    154          context,
    155          // module name is "browserAction" because it the name used in the
    156          // ext-android.json, independently from the manifest version.
    157          module: "browserAction",
    158          event: "onClicked",
    159          inputHandling: true,
    160          extensionApi: this,
    161        }).api(),
    162 
    163        onUserSettingsChanged: new EventManager({
    164          context,
    165          // module name is "browserAction" because it the name used in the
    166          // ext-android.json, independently from the manifest version.
    167          module: "browserAction",
    168          event: "onUserSettingsChanged",
    169          extensionApi: this,
    170        }).api(),
    171 
    172        getUserSettings: () => {
    173          return {
    174            // isOnToolbar is not supported on Android.
    175            // We intentionally omit the property, in case
    176            // extensions would like to feature-detect support
    177            // for this feature.
    178          };
    179        },
    180        openPopup: options => {
    181          const isHandlingUserInput =
    182            context.callContextData?.isHandlingUserInput;
    183 
    184          if (
    185            !Services.prefs.getBoolPref(
    186              "extensions.openPopupWithoutUserGesture.enabled"
    187            ) &&
    188            !isHandlingUserInput
    189          ) {
    190            throw new ExtensionError("openPopup requires a user gesture");
    191          }
    192 
    193          const currentWindow = windowTracker.getCurrentWindow(context);
    194 
    195          const window =
    196            typeof options?.windowId === "number"
    197              ? windowTracker.getWindow(options.windowId, context)
    198              : currentWindow;
    199 
    200          if (window !== currentWindow) {
    201            throw new ExtensionError(
    202              "Only the current window is supported on Android."
    203            );
    204          }
    205 
    206          if (this.action.getPopupUrl(window.tab, true)) {
    207            action.openPopup(window.tab, !isHandlingUserInput);
    208          }
    209        },
    210      },
    211    };
    212  }
    213 };
    214 
    215 global.browserActionFor = this.browserAction.for;