summaryrefslogtreecommitdiffstats
path: root/mobile/android/modules/geckoview/GeckoViewTab.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/modules/geckoview/GeckoViewTab.sys.mjs')
-rw-r--r--mobile/android/modules/geckoview/GeckoViewTab.sys.mjs219
1 files changed, 219 insertions, 0 deletions
diff --git a/mobile/android/modules/geckoview/GeckoViewTab.sys.mjs b/mobile/android/modules/geckoview/GeckoViewTab.sys.mjs
new file mode 100644
index 0000000000..53f43f153c
--- /dev/null
+++ b/mobile/android/modules/geckoview/GeckoViewTab.sys.mjs
@@ -0,0 +1,219 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs";
+
+import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
+
+const { ExtensionError } = ExtensionUtils;
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
+ mobileWindowTracker: "resource://gre/modules/GeckoViewWebExtension.sys.mjs",
+});
+
+class Tab {
+ constructor(window) {
+ this.id = GeckoViewTabBridge.windowIdToTabId(window.docShell.outerWindowID);
+ this.browser = window.browser;
+ this.active = false;
+ }
+
+ get linkedBrowser() {
+ return this.browser;
+ }
+
+ getActive() {
+ return this.active;
+ }
+
+ get userContextId() {
+ return this.browser.ownerGlobal.moduleManager.settings
+ .unsafeSessionContextId;
+ }
+}
+
+// Because of bug 1410749, we can't use 0, though, and just to be safe
+// we choose a value that is unlikely to overlap with Fennec's tab IDs.
+const TAB_ID_BASE = 10000;
+
+export const GeckoViewTabBridge = {
+ /**
+ * Converts windowId to tabId as in GeckoView every browser window has exactly one tab.
+ *
+ * @param {number} windowId outerWindowId
+ *
+ * @returns {number} tabId
+ */
+ windowIdToTabId(windowId) {
+ return TAB_ID_BASE + windowId;
+ },
+
+ /**
+ * Converts tabId to windowId.
+ *
+ * @param {number} tabId
+ *
+ * @returns {number}
+ * outerWindowId of browser window to which the tab belongs.
+ */
+ tabIdToWindowId(tabId) {
+ return tabId - TAB_ID_BASE;
+ },
+
+ /**
+ * Delegates openOptionsPage handling to the app.
+ *
+ * @param {number} extensionId
+ * The ID of the extension requesting the options menu.
+ *
+ * @returns {Promise<Void>}
+ * A promise resolved after successful handling.
+ */
+ async openOptionsPage(extensionId) {
+ debug`openOptionsPage for extensionId ${extensionId}`;
+
+ try {
+ await lazy.EventDispatcher.instance.sendRequestForResult({
+ type: "GeckoView:WebExtension:OpenOptionsPage",
+ extensionId,
+ });
+ } catch (errorMessage) {
+ // The error message coming from GeckoView is about :OpenOptionsPage not
+ // being registered so we need to have one that's extension friendly
+ // here.
+ throw new ExtensionError("runtime.openOptionsPage is not supported");
+ }
+ },
+
+ /**
+ * Request the GeckoView App to create a new tab (GeckoSession).
+ *
+ * @param {object} options
+ * @param {string} options.extensionId
+ * The ID of the extension that requested a new tab.
+ * @param {object} options.createProperties
+ * The properties for the new tab, see tabs.create reference for details.
+ *
+ * @returns {Promise<Tab>}
+ * A promise resolved to the newly created tab.
+ * @throws {Error}
+ * Throws an error if the GeckoView app doesn't support tabs.create or fails to handle the request.
+ */
+ async createNewTab({ extensionId, createProperties } = {}) {
+ debug`createNewTab`;
+
+ const newSessionId = Services.uuid
+ .generateUUID()
+ .toString()
+ .slice(1, -1)
+ .replace(/-/g, "");
+
+ // The window might already be open by the time we get the response, so we
+ // need to start waiting before we send the message.
+ const windowPromise = new Promise(resolve => {
+ const handler = {
+ observe(aSubject, aTopic, aData) {
+ if (
+ aTopic === "geckoview-window-created" &&
+ aSubject.name === newSessionId
+ ) {
+ Services.obs.removeObserver(handler, "geckoview-window-created");
+ resolve(aSubject);
+ }
+ },
+ };
+ Services.obs.addObserver(handler, "geckoview-window-created");
+ });
+
+ let didOpenSession = false;
+ try {
+ didOpenSession = await lazy.EventDispatcher.instance.sendRequestForResult(
+ {
+ type: "GeckoView:WebExtension:NewTab",
+ extensionId,
+ createProperties,
+ newSessionId,
+ }
+ );
+ } catch (errorMessage) {
+ // The error message coming from GeckoView is about :NewTab not being
+ // registered so we need to have one that's extension friendly here.
+ throw new ExtensionError("tabs.create is not supported");
+ }
+
+ if (!didOpenSession) {
+ throw new ExtensionError("Cannot create new tab");
+ }
+
+ const window = await windowPromise;
+ if (!window.tab) {
+ window.tab = new Tab(window);
+ }
+ return window.tab;
+ },
+
+ /**
+ * Request the GeckoView App to close a tab (GeckoSession).
+ *
+ *
+ * @param {object} options
+ * @param {Window} options.window The window owning the tab to close
+ * @param {string} options.extensionId
+ *
+ * @returns {Promise<Void>}
+ * A promise resolved after GeckoSession is closed.
+ * @throws {Error}
+ * Throws an error if the GeckoView app doesn't allow extension to close tab.
+ */
+ async closeTab({ window, extensionId } = {}) {
+ try {
+ await window.WindowEventDispatcher.sendRequestForResult({
+ type: "GeckoView:WebExtension:CloseTab",
+ extensionId,
+ });
+ } catch (errorMessage) {
+ throw new ExtensionError(errorMessage);
+ }
+ },
+
+ async updateTab({ window, extensionId, updateProperties } = {}) {
+ try {
+ await window.WindowEventDispatcher.sendRequestForResult({
+ type: "GeckoView:WebExtension:UpdateTab",
+ extensionId,
+ updateProperties,
+ });
+ } catch (errorMessage) {
+ throw new ExtensionError(errorMessage);
+ }
+ },
+};
+
+export class GeckoViewTab extends GeckoViewModule {
+ onInit() {
+ const { window } = this;
+ if (!window.tab) {
+ window.tab = new Tab(window);
+ }
+
+ this.registerListener(["GeckoView:WebExtension:SetTabActive"]);
+ }
+
+ onEvent(aEvent, aData, aCallback) {
+ debug`onEvent: event=${aEvent}, data=${aData}`;
+
+ switch (aEvent) {
+ case "GeckoView:WebExtension:SetTabActive": {
+ const { active } = aData;
+ lazy.mobileWindowTracker.setTabActive(this.window, active);
+ break;
+ }
+ }
+ }
+}
+
+const { debug, warn } = GeckoViewTab.initLogging("GeckoViewTab");