Bug 1287010 - Use sandbox instead of JSM for global separation
MozReview-Commit-ID: GSqmh0xC2hW
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
@@ -205,18 +205,17 @@ add_task(function* testBrowserActionClic
files: {
"popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8"></head></html>`,
},
});
yield extension.startup();
- const {GlobalManager} = Cu.import("resource://gre/modules/Extension.jsm", {});
- const {browserActionFor} = Cu.import("resource://gre/modules/ExtensionGlobalScope.jsm", {}).getGlobalForTesting("chrome");
+ const {GlobalManager, Management: {global: {browserActionFor}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
let ext = GlobalManager.extensionMap.get(extension.id);
let browserAction = browserActionFor(ext);
let widget = getBrowserActionWidget(extension).forWindow(window);
// Test canceled click.
EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mousedown", button: 0}, window);
--- a/browser/components/extensions/test/browser/browser_ext_currentWindow.js
+++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
@@ -85,17 +85,17 @@ add_task(function* () {
"popup.js": genericChecker,
},
background: genericChecker,
});
yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
- let {WindowManager} = Cu.import("resource://gre/modules/ExtensionGlobalScope.jsm", {}).getGlobalForTesting("chrome");
+ let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
let winId1 = WindowManager.getId(win1);
let winId2 = WindowManager.getId(win2);
function* checkWindow(kind, winId, name) {
extension.sendMessage(kind + "-check-current1");
is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 1) [${kind}]`);
extension.sendMessage(kind + "-check-current2");
--- a/browser/components/extensions/test/browser/browser_ext_getViews.js
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -99,17 +99,17 @@ add_task(function* () {
background: genericChecker,
});
yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
info("started");
- let {WindowManager} = Cu.import("resource://gre/modules/ExtensionGlobalScope.jsm", {}).getGlobalForTesting("chrome");
+ let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
let winId1 = WindowManager.getId(win1);
let winId2 = WindowManager.getId(win2);
function* openTab(winId) {
extension.sendMessage("background-open-tab", winId);
yield extension.awaitMessage("tab-ready");
}
--- a/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
@@ -171,17 +171,17 @@ add_task(function* () {
manifest: {
"permissions": ["tabs"],
},
background,
});
extension.onMessage("change-tab", (tabId, attr, on) => {
- let {TabManager} = Cu.import("resource://gre/modules/ExtensionGlobalScope.jsm", {}).getGlobalForTesting("chrome");
+ let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
let tab = TabManager.getTab(tabId);
if (attr == "muted") {
// Ideally we'd simulate a click on the tab audio icon for this, but the
// handler relies on CSS :hover states, which are complicated and fragile
// to simulate.
if (tab.muted != on) {
--- a/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
@@ -93,17 +93,17 @@ add_task(function* testDuplicateTabLazil
manifest: {
"permissions": ["tabs"],
},
background,
});
extension.onMessage("duplicate-tab", tabId => {
- let {TabManager} = Cu.import("resource://gre/modules/ExtensionGlobalScope.jsm", {}).getGlobalForTesting("chrome");
+ let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
let tab = TabManager.getTab(tabId);
// This is a bit of a hack to load a tab in the background.
let newTab = gBrowser.duplicateTab(tab, false);
BrowserTestUtils.waitForEvent(newTab, "SSTabRestored", () => true).then(() => {
extension.sendMessage("duplicate-tab-done", TabManager.getId(newTab));
});
--- a/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
@@ -196,17 +196,17 @@ add_task(function* () {
manifest: {
"permissions": ["tabs"],
},
background,
});
extension.onMessage("msg", (id, msg, ...args) => {
- let {TabManager} = Cu.import("resource://gre/modules/ExtensionGlobalScope.jsm", {}).getGlobalForTesting("chrome");
+ let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
let resp;
if (msg == "get-zoom") {
let tab = TabManager.getTab(args[0]);
resp = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
} else if (msg == "set-zoom") {
let tab = TabManager.getTab(args[0]);
ZoomManager.setZoomForBrowser(tab.linkedBrowser);
--- a/browser/components/extensions/test/browser/browser_ext_windows_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_events.js
@@ -48,17 +48,17 @@ add_task(function* testWindowsEvents() {
let extension = ExtensionTestUtils.loadExtension({
background: `(${background})()`,
});
yield extension.startup();
yield extension.awaitMessage("ready");
- let {WindowManager} = Cu.import("resource://gre/modules/ExtensionGlobalScope.jsm", {}).getGlobalForTesting("chrome");
+ let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
let currentWindow = window;
let currentWindowId = WindowManager.getId(currentWindow);
info(`Current window ID: ${currentWindowId}`);
info(`Create browser window 1`);
let win1 = yield BrowserTestUtils.openNewBrowserWindow();
let win1Id = yield extension.awaitMessage("window-created");
deleted file mode 100644
--- a/toolkit/components/extensions/ExtensionGlobalScope.jsm
+++ /dev/null
@@ -1,95 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["loadExtScriptInScope"];
-
-/*
- * This file provides the common global scope for all ext-*.js modules. Any
- * variable declared here is automatically shared with the ext-*.js files, so
- * try to keep the number of globals to a minimum.
- *
- * See loadExtScriptInScope below and ExtensionUtils.SchemaAPIManager for more
- * information.
- */
-
-const Ci = Components.interfaces;
-const Cc = Components.classes;
-const Cu = Components.utils;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
- "resource://gre/modules/ExtensionUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "require",
- "resource://devtools/shared/Loader.jsm");
-
-let _internalGlobals = new WeakMap();
-
-/**
- * Load an ext-*.js script, with the global namespace set up as follows:
- * - (invisible root) This JSM's global scope.
- * +- global The global shared by all scripts in an apiManager.
- * +- scope The implied global, visible to the script as `this`.
- *
- * By default variable declarations stay within scope `scope`. If there is a
- * need to share variables with other modules, use `global`.
- *
- * To ensure consistent behavior in SchemaAPIManagers regardless of whether an
- * addon runs in the chrome process or separate addon process, the scope
- * (invisible root) should be avoided. When an ext-*.js script uses `Cu.import`
- * without a second argument, variables appear in (invisible root).
- *
- * @param {string} scriptUrl The local script to load.
- * @param {SchemaAPIManager} apiManager The API manager that is shared with the
- * script.
- */
-this.loadExtScriptInScope = (scriptUrl, apiManager) => {
- // Different SchemaAPIManagers should have different globals.
- let global = _internalGlobals.get(apiManager);
- if (!global) {
- global = Object.create(null);
- global.extensions = apiManager;
- global.global = global;
- global._internalScriptScopes = [];
- _internalGlobals.set(apiManager, global);
- }
-
- let scope = Object.create(global, {
- console: {
- get() { return ExtensionUtils.console; },
- },
- });
-
- Services.scriptloader.loadSubScript(scriptUrl, scope, "UTF-8");
-
- // Save the scope to avoid it being garbage collected.
- global._internalScriptScopes.push(scope);
-};
-
-/**
- * Retrieve the shared global for a given type. This enables unit tests to test
- * variables that were explicitly shared via `global`. If the global is not
- * found, an error is thrown.
- *
- * @param {*} type A description of the global.
- * For now, only "chrome" is supported to get the global from the chrome
- * process. In the future support for other process types may be added.
- * @returns {object} The global for the given type.
- */
-this.getGlobalForTesting = (type) => {
- if (type === "chrome") {
- let {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
- let global = _internalGlobals.get(Management);
- if (global) {
- return global;
- }
- throw new Error("Global not found. Did you really load an ext- script?");
- }
- // For now just throw until we find a need for more types.
- throw new Error(`getGlobalForTesting: Parameter not supported yet: ${type}`);
-};
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -28,19 +28,16 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/Locale.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
"resource://gre/modules/MessageChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
"resource://gre/modules/PromiseUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "loadExtScriptInScope",
- "resource://gre/modules/ExtensionGlobalScope.jsm");
-
function getConsole() {
return new ConsoleAPI({
maxLogLevelPref: "extensions.webextensions.log.level",
prefix: "WebExtensions",
});
}
XPCOMUtils.defineLazyGetter(this, "console", getConsole);
@@ -1635,26 +1632,66 @@ class SchemaAPIManager extends EventEmit
* @param {string} processType
* "main" - The main, one and only chrome browser process.
* "addon" - An addon process.
* "content" - A content process.
*/
constructor(processType) {
super();
this.processType = processType;
+ this.global = this._createExtGlobal();
+ this._scriptScopes = [];
this._schemaApis = {
addon_parent: [],
addon_child: [],
content_parent: [],
content_child: [],
};
}
+ /**
+ * Create a global object that is used as the shared global for all ext-*.js
+ * scripts that are loaded via `loadScript`.
+ *
+ * @returns {object} A sandbox that is used as the global by `loadScript`.
+ */
+ _createExtGlobal() {
+ let global = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), {
+ wantXrays: false,
+ sandboxName: `Namespace of ext-*.js scripts for ${this.processType}`,
+ });
+ Object.defineProperty(global, "console", {get() { return console; }});
+ global.extensions = this;
+ global.global = global;
+ global.Cc = Cc;
+ global.Ci = Ci;
+ global.Cu = Cu;
+ global.Cr = Cr;
+ XPCOMUtils.defineLazyModuleGetter(global, "require",
+ "resource://devtools/shared/Loader.jsm");
+ global.XPCOMUtils = XPCOMUtils;
+ return global;
+ }
+
+ /**
+ * Load an ext-*.js script. The script runs in its own scope, if it wishes to
+ * share state with another script it can assign to the `global` variable. If
+ * it wishes to communicate with this API manager, use `extensions`.
+ *
+ * @param {string} scriptUrl The URL of the ext-*.js script.
+ */
loadScript(scriptUrl) {
- loadExtScriptInScope(scriptUrl, this);
+ // Create the object in the context of the sandbox so that the script runs
+ // in the sandbox's context instead of here.
+ let scope = this.global.Object.create(null);
+
+ Services.scriptloader.loadSubScript(scriptUrl, scope, "UTF-8");
+
+ // Save the scope to avoid it being garbage collected.
+ this._scriptScopes.push(scope);
}
/**
* Called by an ext-*.js script to register an API.
*
* @param {string} namespace The API namespace.
* Intended to match the namespace of the generated API, but not used at
* the moment - see bugzil.la/1295774.
--- a/toolkit/components/extensions/moz.build
+++ b/toolkit/components/extensions/moz.build
@@ -3,17 +3,16 @@
# 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/.
EXTRA_JS_MODULES += [
'Extension.jsm',
'ExtensionAPI.jsm',
'ExtensionContent.jsm',
- 'ExtensionGlobalScope.jsm',
'ExtensionManagement.jsm',
'ExtensionStorage.jsm',
'ExtensionUtils.jsm',
'MessageChannel.jsm',
'NativeMessaging.jsm',
'Schemas.jsm',
]