230 lines
6.5 KiB
JavaScript
230 lines
6.5 KiB
JavaScript
/* 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) {
|
|
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",
|
|
"GeckoView:FlushSessionState",
|
|
]);
|
|
}
|
|
|
|
onEvent(aEvent, aData) {
|
|
debug`onEvent: event=${aEvent}, data=${aData}`;
|
|
|
|
switch (aEvent) {
|
|
case "GeckoView:WebExtension:SetTabActive": {
|
|
const { active } = aData;
|
|
lazy.mobileWindowTracker.setTabActive(this.window, active);
|
|
break;
|
|
}
|
|
case "GeckoView:FlushSessionState": {
|
|
if (Services.appinfo.sessionHistoryInParent) {
|
|
if (this.browser && this.browser.frameLoader) {
|
|
this.browser.frameLoader.requestTabStateFlush();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const { debug, warn } = GeckoViewTab.initLogging("GeckoViewTab");
|