Bug 1287010 - Use sandbox instead of JSM for global separation draft
authorRob Wu <rob@robwu.nl>
Tue, 23 Aug 2016 16:19:33 -0700
changeset 405211 e4c7f6ec6c8e0461d3e60b4d11f4da76b03c6ecf
parent 405210 7db31fa1699417c116f005e0f813c84deec2244c
child 405212 43f63a415ae056328c3c57f53a643598fa0f33da
push id27432
push userbmo:rob@robwu.nl
push dateThu, 25 Aug 2016 02:36:24 +0000
bugs1287010
milestone51.0a1
Bug 1287010 - Use sandbox instead of JSM for global separation MozReview-Commit-ID: GSqmh0xC2hW
browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
browser/components/extensions/test/browser/browser_ext_currentWindow.js
browser/components/extensions/test/browser/browser_ext_getViews.js
browser/components/extensions/test/browser/browser_ext_tabs_audio.js
browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
browser/components/extensions/test/browser/browser_ext_windows_events.js
toolkit/components/extensions/ExtensionGlobalScope.jsm
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/moz.build
--- 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',
 ]