mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
b821d119a0
Differential Revision: https://phabricator.services.mozilla.com/D4279 --HG-- extra : rebase_source : f57e09927871a23ed3c105325369e5c35ffd3d93
441 lines
14 KiB
JavaScript
441 lines
14 KiB
JavaScript
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set sts=2 sw=2 et tw=80: */
|
|
/* 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/. */
|
|
"use strict";
|
|
|
|
/* exported ExtensionPageChild */
|
|
|
|
var EXPORTED_SYMBOLS = ["ExtensionPageChild"];
|
|
|
|
/**
|
|
* This file handles privileged extension page logic that runs in the
|
|
* child process.
|
|
*/
|
|
|
|
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
ChromeUtils.defineModuleGetter(this, "ExtensionChildDevToolsUtils",
|
|
"resource://gre/modules/ExtensionChildDevToolsUtils.jsm");
|
|
ChromeUtils.defineModuleGetter(this, "Schemas",
|
|
"resource://gre/modules/Schemas.jsm");
|
|
ChromeUtils.defineModuleGetter(this, "WebNavigationFrames",
|
|
"resource://gre/modules/WebNavigationFrames.jsm");
|
|
|
|
XPCOMUtils.defineLazyGetter(
|
|
this, "processScript",
|
|
() => Cc["@mozilla.org/webextensions/extension-process-script;1"]
|
|
.getService().wrappedJSObject);
|
|
|
|
const CATEGORY_EXTENSION_SCRIPTS_ADDON = "webextension-scripts-addon";
|
|
const CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS = "webextension-scripts-devtools";
|
|
|
|
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
|
|
ChromeUtils.import("resource://gre/modules/ExtensionChild.jsm");
|
|
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
|
|
|
|
const {
|
|
getInnerWindowID,
|
|
promiseEvent,
|
|
} = ExtensionUtils;
|
|
|
|
const {
|
|
BaseContext,
|
|
CanOfAPIs,
|
|
SchemaAPIManager,
|
|
defineLazyGetter,
|
|
} = ExtensionCommon;
|
|
|
|
const {
|
|
ChildAPIManager,
|
|
Messenger,
|
|
} = ExtensionChild;
|
|
|
|
var ExtensionPageChild;
|
|
|
|
const initializeBackgroundPage = (context) => {
|
|
// Override the `alert()` method inside background windows;
|
|
// we alias it to console.log().
|
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1203394
|
|
let alertDisplayedWarning = false;
|
|
const innerWindowID = getInnerWindowID(context.contentWindow);
|
|
|
|
function logWarningMessage({text, filename, lineNumber, columnNumber}) {
|
|
let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
|
|
consoleMsg.initWithWindowID(text, filename, null, lineNumber, columnNumber,
|
|
Ci.nsIScriptError.warningFlag, "webextension",
|
|
innerWindowID);
|
|
Services.console.logMessage(consoleMsg);
|
|
}
|
|
|
|
let alertOverwrite = text => {
|
|
const {filename, columnNumber, lineNumber} = Components.stack.caller;
|
|
|
|
if (!alertDisplayedWarning) {
|
|
context.childManager.callParentAsyncFunction("runtime.openBrowserConsole", []);
|
|
|
|
logWarningMessage({
|
|
text: "alert() is not supported in background windows; please use console.log instead.",
|
|
filename, lineNumber, columnNumber,
|
|
});
|
|
|
|
alertDisplayedWarning = true;
|
|
}
|
|
|
|
logWarningMessage({text, filename, lineNumber, columnNumber});
|
|
};
|
|
Cu.exportFunction(alertOverwrite, context.contentWindow, {defineAs: "alert"});
|
|
};
|
|
|
|
function getFrameData(global) {
|
|
return processScript.getFrameData(global, true);
|
|
}
|
|
|
|
var apiManager = new class extends SchemaAPIManager {
|
|
constructor() {
|
|
super("addon", Schemas);
|
|
this.initialized = false;
|
|
}
|
|
|
|
lazyInit() {
|
|
if (!this.initialized) {
|
|
this.initialized = true;
|
|
this.initGlobal();
|
|
for (let {value} of Services.catMan.enumerateCategory(CATEGORY_EXTENSION_SCRIPTS_ADDON)) {
|
|
this.loadScript(value);
|
|
}
|
|
}
|
|
}
|
|
}();
|
|
|
|
var devtoolsAPIManager = new class extends SchemaAPIManager {
|
|
constructor() {
|
|
super("devtools", Schemas);
|
|
this.initialized = false;
|
|
}
|
|
|
|
lazyInit() {
|
|
if (!this.initialized) {
|
|
this.initialized = true;
|
|
this.initGlobal();
|
|
for (let {value} of Services.catMan.enumerateCategory(CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS)) {
|
|
this.loadScript(value);
|
|
}
|
|
}
|
|
}
|
|
}();
|
|
|
|
class ExtensionBaseContextChild extends BaseContext {
|
|
/**
|
|
* This ExtensionBaseContextChild represents an addon execution environment
|
|
* that is running in an addon or devtools child process.
|
|
*
|
|
* @param {BrowserExtensionContent} extension This context's owner.
|
|
* @param {object} params
|
|
* @param {string} params.envType One of "addon_child" or "devtools_child".
|
|
* @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
|
|
* @param {string} params.viewType One of "background", "popup", "tab",
|
|
* "sidebar", "devtools_page" or "devtools_panel".
|
|
* @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
|
|
*/
|
|
constructor(extension, params) {
|
|
if (!params.envType) {
|
|
throw new Error("Missing envType");
|
|
}
|
|
|
|
super(params.envType, extension);
|
|
let {viewType = "tab", uri, contentWindow, tabId} = params;
|
|
this.viewType = viewType;
|
|
this.uri = uri || extension.baseURI;
|
|
|
|
this.setContentWindow(contentWindow);
|
|
|
|
// This is the MessageSender property passed to extension.
|
|
let sender = {id: extension.id};
|
|
if (viewType == "tab") {
|
|
sender.frameId = WebNavigationFrames.getFrameId(contentWindow);
|
|
sender.tabId = tabId;
|
|
Object.defineProperty(this, "tabId",
|
|
{value: tabId,
|
|
enumerable: true,
|
|
configurable: true});
|
|
}
|
|
if (uri) {
|
|
sender.url = uri.spec;
|
|
}
|
|
this.sender = sender;
|
|
|
|
Schemas.exportLazyGetter(contentWindow, "browser", () => {
|
|
let browserObj = Cu.createObjectIn(contentWindow);
|
|
this.childManager.inject(browserObj);
|
|
return browserObj;
|
|
});
|
|
|
|
Schemas.exportLazyGetter(contentWindow, "chrome", () => {
|
|
let chromeApiWrapper = Object.create(this.childManager);
|
|
chromeApiWrapper.isChromeCompat = true;
|
|
|
|
let chromeObj = Cu.createObjectIn(contentWindow);
|
|
chromeApiWrapper.inject(chromeObj);
|
|
return chromeObj;
|
|
});
|
|
}
|
|
|
|
get cloneScope() {
|
|
return this.contentWindow;
|
|
}
|
|
|
|
get principal() {
|
|
return this.contentWindow.document.nodePrincipal;
|
|
}
|
|
|
|
get windowId() {
|
|
if (["tab", "popup", "sidebar"].includes(this.viewType)) {
|
|
let frameData = getFrameData(this.messageManager);
|
|
return frameData ? frameData.windowId : -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
get tabId() {
|
|
// Will be overwritten in the constructor if necessary.
|
|
return -1;
|
|
}
|
|
|
|
// Called when the extension shuts down.
|
|
shutdown() {
|
|
this.unload();
|
|
}
|
|
|
|
// This method is called when an extension page navigates away or
|
|
// its tab is closed.
|
|
unload() {
|
|
// Note that without this guard, we end up running unload code
|
|
// multiple times for tab pages closed by the "page-unload" handlers
|
|
// triggered below.
|
|
if (this.unloaded) {
|
|
return;
|
|
}
|
|
|
|
if (this.contentWindow) {
|
|
this.contentWindow.close();
|
|
}
|
|
|
|
super.unload();
|
|
}
|
|
}
|
|
|
|
defineLazyGetter(ExtensionBaseContextChild.prototype, "messenger", function() {
|
|
let filter = {extensionId: this.extension.id};
|
|
let optionalFilter = {};
|
|
// Addon-generated messages (not necessarily from the same process as the
|
|
// addon itself) are sent to the main process, which forwards them via the
|
|
// parent process message manager. Specific replies can be sent to the frame
|
|
// message manager.
|
|
return new Messenger(this, [Services.cpmm, this.messageManager], this.sender,
|
|
filter, optionalFilter);
|
|
});
|
|
|
|
class ExtensionPageContextChild extends ExtensionBaseContextChild {
|
|
/**
|
|
* This ExtensionPageContextChild represents a privileged addon
|
|
* execution environment that has full access to the WebExtensions
|
|
* APIs (provided that the correct permissions have been requested).
|
|
*
|
|
* This is the child side of the ExtensionPageContextParent class
|
|
* defined in ExtensionParent.jsm.
|
|
*
|
|
* @param {BrowserExtensionContent} extension This context's owner.
|
|
* @param {object} params
|
|
* @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
|
|
* @param {string} params.viewType One of "background", "popup", "sidebar" or "tab".
|
|
* "background", "sidebar" and "tab" are used by `browser.extension.getViews`.
|
|
* "popup" is only used internally to identify page action and browser
|
|
* action popups and options_ui pages.
|
|
* @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
|
|
*/
|
|
constructor(extension, params) {
|
|
super(extension, Object.assign(params, {envType: "addon_child"}));
|
|
|
|
this.extension.views.add(this);
|
|
}
|
|
|
|
unload() {
|
|
super.unload();
|
|
this.extension.views.delete(this);
|
|
}
|
|
}
|
|
|
|
defineLazyGetter(ExtensionPageContextChild.prototype, "childManager", function() {
|
|
this.extension.apiManager.lazyInit();
|
|
|
|
let localApis = {};
|
|
let can = new CanOfAPIs(this, this.extension.apiManager, localApis);
|
|
|
|
let childManager = new ChildAPIManager(this, this.messageManager, can, {
|
|
envType: "addon_parent",
|
|
viewType: this.viewType,
|
|
url: this.uri.spec,
|
|
incognito: this.incognito,
|
|
});
|
|
|
|
this.callOnClose(childManager);
|
|
|
|
if (this.viewType == "background") {
|
|
initializeBackgroundPage(this);
|
|
}
|
|
|
|
return childManager;
|
|
});
|
|
|
|
class DevToolsContextChild extends ExtensionBaseContextChild {
|
|
/**
|
|
* This DevToolsContextChild represents a devtools-related addon execution
|
|
* environment that has access to the devtools API namespace and to the same subset
|
|
* of APIs available in a content script execution environment.
|
|
*
|
|
* @param {BrowserExtensionContent} extension This context's owner.
|
|
* @param {object} params
|
|
* @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
|
|
* @param {string} params.viewType One of "devtools_page" or "devtools_panel".
|
|
* @param {object} [params.devtoolsToolboxInfo] This devtools toolbox's information,
|
|
* used if viewType is "devtools_page" or "devtools_panel".
|
|
*/
|
|
constructor(extension, params) {
|
|
super(extension, Object.assign(params, {envType: "devtools_child"}));
|
|
|
|
this.devtoolsToolboxInfo = params.devtoolsToolboxInfo;
|
|
ExtensionChildDevToolsUtils.initThemeChangeObserver(
|
|
params.devtoolsToolboxInfo.themeName, this);
|
|
|
|
this.extension.devtoolsViews.add(this);
|
|
}
|
|
|
|
unload() {
|
|
super.unload();
|
|
this.extension.devtoolsViews.delete(this);
|
|
}
|
|
}
|
|
|
|
defineLazyGetter(DevToolsContextChild.prototype, "childManager", function() {
|
|
devtoolsAPIManager.lazyInit();
|
|
|
|
let localApis = {};
|
|
let can = new CanOfAPIs(this, devtoolsAPIManager, localApis);
|
|
|
|
let childManager = new ChildAPIManager(this, this.messageManager, can, {
|
|
envType: "devtools_parent",
|
|
viewType: this.viewType,
|
|
url: this.uri.spec,
|
|
incognito: this.incognito,
|
|
});
|
|
|
|
this.callOnClose(childManager);
|
|
|
|
return childManager;
|
|
});
|
|
|
|
ExtensionPageChild = {
|
|
// Map<innerWindowId, ExtensionPageContextChild>
|
|
extensionContexts: new Map(),
|
|
|
|
initialized: false,
|
|
|
|
apiManager,
|
|
|
|
_init() {
|
|
if (this.initialized) {
|
|
return;
|
|
}
|
|
this.initialized = true;
|
|
|
|
Services.obs.addObserver(this, "inner-window-destroyed"); // eslint-ignore-line mozilla/balanced-listeners
|
|
},
|
|
|
|
observe(subject, topic, data) {
|
|
if (topic === "inner-window-destroyed") {
|
|
let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
|
|
|
this.destroyExtensionContext(windowId);
|
|
}
|
|
},
|
|
|
|
expectViewLoad(global, viewType) {
|
|
if (["popup", "sidebar"].includes(viewType)) {
|
|
global.docShell.isAppTab = true;
|
|
}
|
|
|
|
promiseEvent(global, "DOMContentLoaded", true, event => event.target.location != "about:blank").then(() => {
|
|
let windowId = getInnerWindowID(global.content);
|
|
let context = this.extensionContexts.get(windowId);
|
|
|
|
global.sendAsyncMessage("Extension:ExtensionViewLoaded",
|
|
{childId: context && context.childManager.id});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Create a privileged context at document-element-inserted.
|
|
*
|
|
* @param {BrowserExtensionContent} extension
|
|
* The extension for which the context should be created.
|
|
* @param {nsIDOMWindow} contentWindow The global of the page.
|
|
*/
|
|
initExtensionContext(extension, contentWindow) {
|
|
this._init();
|
|
|
|
if (!WebExtensionPolicy.isExtensionProcess) {
|
|
throw new Error("Cannot create an extension page context in current process");
|
|
}
|
|
|
|
let windowId = getInnerWindowID(contentWindow);
|
|
let context = this.extensionContexts.get(windowId);
|
|
if (context) {
|
|
if (context.extension !== extension) {
|
|
throw new Error("A different extension context already exists for this frame");
|
|
}
|
|
throw new Error("An extension context was already initialized for this frame");
|
|
}
|
|
|
|
let mm = contentWindow.docShell.messageManager;
|
|
|
|
let {viewType, tabId, devtoolsToolboxInfo} = getFrameData(mm) || {};
|
|
|
|
let uri = contentWindow.document.documentURIObject;
|
|
|
|
if (devtoolsToolboxInfo) {
|
|
context = new DevToolsContextChild(extension, {
|
|
viewType, contentWindow, uri, tabId, devtoolsToolboxInfo,
|
|
});
|
|
} else {
|
|
context = new ExtensionPageContextChild(extension, {viewType, contentWindow, uri, tabId});
|
|
}
|
|
|
|
this.extensionContexts.set(windowId, context);
|
|
},
|
|
|
|
/**
|
|
* Close the ExtensionPageContextChild belonging to the given window, if any.
|
|
*
|
|
* @param {number} windowId The inner window ID of the destroyed context.
|
|
*/
|
|
destroyExtensionContext(windowId) {
|
|
let context = this.extensionContexts.get(windowId);
|
|
if (context) {
|
|
context.unload();
|
|
this.extensionContexts.delete(windowId);
|
|
}
|
|
},
|
|
|
|
shutdownExtension(extensionId) {
|
|
for (let [windowId, context] of this.extensionContexts) {
|
|
if (context.extension.id == extensionId) {
|
|
context.shutdown();
|
|
this.extensionContexts.delete(windowId);
|
|
}
|
|
}
|
|
},
|
|
};
|