Bug 1013997 - Use only one compartment for all devtools modules. r=mossop, r=past
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 07 Jul 2014 06:07:00 -0400
changeset 192933 ee555b84f272e78da11acbc2d74a1976a1f280af
parent 192932 05cfda67b9dbff01f29b4f2629422c93ad34771a
child 192934 5d67017305554752ec2da4a9ba649a76b42775b3
push id45987
push userkwierso@gmail.com
push dateWed, 09 Jul 2014 03:04:57 +0000
treeherdermozilla-inbound@79b1fc2308b7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmossop, past
bugs1013997
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1013997 - Use only one compartment for all devtools modules. r=mossop, r=past
addon-sdk/source/lib/sdk/base64.js
addon-sdk/source/lib/toolkit/loader.js
addon-sdk/source/test/test-loader.js
browser/devtools/app-manager/app-projects.js
browser/devtools/framework/toolbox-options.js
browser/devtools/projecteditor/lib/shells.js
browser/devtools/webide/modules/runtimes.js
toolkit/devtools/DevToolsUtils.js
toolkit/devtools/Loader.jsm
toolkit/devtools/event-emitter.js
toolkit/devtools/server/actors/script.js
toolkit/devtools/server/actors/storage.js
toolkit/devtools/server/actors/tracer.js
toolkit/devtools/server/actors/webconsole.js
toolkit/devtools/server/main.js
toolkit/devtools/server/tests/unit/testactors.js
toolkit/devtools/tests/unit/test_invisible_loader.js
toolkit/devtools/transport/packets.js
toolkit/devtools/transport/stream-utils.js
toolkit/devtools/transport/tests/unit/testactors.js
toolkit/devtools/transport/transport.js
toolkit/devtools/worker-loader.js
--- a/addon-sdk/source/lib/sdk/base64.js
+++ b/addon-sdk/source/lib/sdk/base64.js
@@ -6,17 +6,19 @@
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cu } = require("chrome");
 
 // Passing an empty object as second argument to avoid scope's pollution
-const { atob, btoa } = Cu.import("resource://gre/modules/Services.jsm", {});
+// (devtools loader injects these symbols as global and prevent using
+// const here)
+var { atob, btoa } = Cu.import("resource://gre/modules/Services.jsm", {});
 
 function isUTF8(charset) {
   let type = typeof charset;
 
   if (type === "undefined")
     return false;
 
   if (type === "string" && charset.toLowerCase() === "utf-8")
--- a/addon-sdk/source/lib/toolkit/loader.js
+++ b/addon-sdk/source/lib/toolkit/loader.js
@@ -278,27 +278,41 @@ const load = iced(function load(loader, 
     exports: module.exports,
     get Components() {
       // Expose `Components` property to throw error on usage with
       // additional information
       throw new ReferenceError(COMPONENT_ERROR);
     }
   });
 
-  let sandbox = sandboxes[module.uri] = Sandbox({
-    name: module.uri,
-    prototype: create(globals, descriptors),
-    wantXrays: false,
-    wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [],
-    invisibleToDebugger: loader.invisibleToDebugger,
-    metadata: {
-      addonID: loader.id,
-      URI: module.uri
-    }
-  });
+  let sandbox;
+  if (loader.sharedGlobalSandbox &&
+      loader.sharedGlobalBlacklist.indexOf(module.id) == -1) {
+    // Create a new object in this sandbox, that will be used as
+    // the scope object for this particular module
+    sandbox = new loader.sharedGlobalSandbox.Object();
+    // Inject all expected globals in the scope object
+    getOwnPropertyNames(globals).forEach(function(name) {
+      descriptors[name] = getOwnPropertyDescriptor(globals, name)
+    });
+    define(sandbox, descriptors);
+  } else {
+    sandbox = Sandbox({
+      name: module.uri,
+      prototype: create(globals, descriptors),
+      wantXrays: false,
+      wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [],
+      invisibleToDebugger: loader.invisibleToDebugger,
+      metadata: {
+        addonID: loader.id,
+        URI: module.uri
+      }
+    });
+  }
+  sandboxes[module.uri] = sandbox;
 
   try {
     evaluate(sandbox, module.uri);
   } catch (error) {
     let { message, fileName, lineNumber } = error;
     let stack = error.stack || Error().stack;
     let frames = parseStack(stack).filter(isntLoaderFrame);
     let toString = String(error);
@@ -686,27 +700,28 @@ exports.unload = unload;
 //   If `resolve` does not returns `uri` string exception will be thrown by
 //   an associated `require` call.
 const Loader = iced(function Loader(options) {
   let console = new ConsoleAPI({
     consoleID: options.id ? "addon/" + options.id : ""
   });
 
   let {
-    modules, globals, resolve, paths, rootURI,
-    manifest, requireMap, isNative, metadata
+    modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative,
+    metadata, sharedGlobal, sharedGlobalBlacklist
   } = override({
     paths: {},
     modules: {},
     globals: {
       console: console
     },
     resolve: options.isNative ?
       exports.nodeResolve :
       exports.resolve,
+    sharedGlobalBlacklist: ["sdk/indexed-db"]
   }, options);
 
   // We create an identity object that will be dispatched on an unload
   // event as subject. This way unload listeners will be able to assert
   // which loader is unloaded. Please note that we intentionally don't
   // use `loader` as subject to prevent a loader access leakage through
   // observer notifications.
   let destructor = freeze(create(null));
@@ -733,26 +748,46 @@ const Loader = iced(function Loader(opti
     if (isNative && !uri)
       uri = id;
     let module = Module(id, uri);
     module.exports = freeze(modules[id]);
     result[uri] = freeze(module);
     return result;
   }, {});
 
+  let sharedGlobalSandbox;
+  if (sharedGlobal) {
+    // Create the unique sandbox we will be using for all modules,
+    // so that we prevent creating a new comportment per module.
+    // The side effect is that all modules will share the same
+    // global objects.
+    sharedGlobalSandbox = Sandbox({
+      name: "Addon-SDK",
+      wantXrays: false,
+      wantGlobalProperties: [],
+      invisibleToDebugger: options.invisibleToDebugger || false,
+      metadata: {
+        addonID: options.id,
+        URI: "Addon-SDK"
+      }
+    });
+  }
+
   // Loader object is just a representation of a environment
   // state. We freeze it and mark make it's properties non-enumerable
   // as they are pure implementation detail that no one should rely upon.
   let returnObj = {
     destructor: { enumerable: false, value: destructor },
     globals: { enumerable: false, value: globals },
     mapping: { enumerable: false, value: mapping },
     // Map of module objects indexed by module URIs.
     modules: { enumerable: false, value: modules },
     metadata: { enumerable: false, value: metadata },
+    sharedGlobalSandbox: { enumerable: false, value: sharedGlobalSandbox },
+    sharedGlobalBlacklist: { enumerable: false, value: sharedGlobalBlacklist },
     // Map of module sandboxes indexed by module URIs.
     sandboxes: { enumerable: false, value: {} },
     resolve: { enumerable: false, value: resolve },
     // ID of the addon, if provided.
     id: { enumerable: false, value: options.id },
     // Whether the modules loaded should be ignored by the debugger
     invisibleToDebugger: { enumerable: false,
                            value: options.invisibleToDebugger || false },
--- a/addon-sdk/source/test/test-loader.js
+++ b/addon-sdk/source/test/test-loader.js
@@ -338,9 +338,32 @@ exports['test console global by default'
   let loader2 = Loader({ paths: { '': uri }, globals: { console: fakeConsole }});
   let program2 = main(loader2, 'main');
 
   assert.equal(program2.console, fakeConsole,
     'global console can be overridden with Loader options');
   function fakeConsole () {};
 };
 
+exports['test shared globals'] = function(assert) {
+  let uri = root + '/fixtures/loader/cycles/';
+  let loader = Loader({ paths: { '': uri }, sharedGlobal: true,
+                        sharedGlobalBlacklist: ['b'] });
+
+  let program = main(loader, 'main');
+
+  // As it is hard to verify what is the global of an object
+  // (due to wrappers) we check that we see the `foo` symbol
+  // being manually injected into the shared global object
+  loader.sharedGlobalSandbox.foo = true;
+
+  let m = loader.sandboxes[uri + 'main.js'];
+  let a = loader.sandboxes[uri + 'a.js'];
+  let b = loader.sandboxes[uri + 'b.js'];
+
+  assert.ok(Cu.getGlobalForObject(m).foo, "main is shared");
+  assert.ok(Cu.getGlobalForObject(a).foo, "a is shared");
+  assert.ok(!Cu.getGlobalForObject(b).foo, "b isn't shared");
+
+  unload(loader);
+}
+
 require('test').run(exports);
--- a/browser/devtools/app-manager/app-projects.js
+++ b/browser/devtools/app-manager/app-projects.js
@@ -9,24 +9,23 @@ const { indexedDB } = require("sdk/index
 
 /**
  * IndexedDB wrapper that just save project objects
  *
  * The only constraint is that project objects have to have
  * a unique `location` object.
  */
 
-const global = this;
 const IDB = {
   _db: null,
 
   open: function () {
     let deferred = promise.defer();
 
-    let request = global.indexedDB.open("AppProjects", 5);
+    let request = indexedDB.open("AppProjects", 5);
     request.onerror = function(event) {
       deferred.reject("Unable to open AppProjects indexedDB. " +
                       "Error code: " + event.target.errorCode);
     };
     request.onupgradeneeded = function(event) {
       let db = event.target.result;
       db.createObjectStore("projects", { keyPath: "location" });
     };
--- a/browser/devtools/framework/toolbox-options.js
+++ b/browser/devtools/framework/toolbox-options.js
@@ -1,16 +1,17 @@
 /* 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";
 
 const {Cu, Cc, Ci} = require("chrome");
 const Services = require("Services");
+const promise = require("promise");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 
 exports.OptionsPanel = OptionsPanel;
 
 XPCOMUtils.defineLazyGetter(this, "l10n", function() {
   let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
   let l10n = function(aName, ...aArgs) {
--- a/browser/devtools/projecteditor/lib/shells.js
+++ b/browser/devtools/projecteditor/lib/shells.js
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { Cu } = require("chrome");
 const { Class } = require("sdk/core/heritage");
 const { EventTarget } = require("sdk/event/target");
 const { emit } = require("sdk/event/core");
 const { EditorTypeForResource } = require("projecteditor/editors");
 const NetworkHelper = require("devtools/toolkit/webconsole/network-helper");
+const promise = require("promise");
 
 /**
  * The Shell is the object that manages the editor for a single resource.
  * It is in charge of selecting the proper Editor (text/image/plugin-defined)
  * and instantiating / appending the editor.
  * This object is not exported, it is just used internally by the ShellDeck.
  *
  * This object has a promise `editorAppended`, that will resolve once the editor
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -4,16 +4,17 @@
 
 const {Cu} = require("chrome");
 const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
 const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
 const {DebuggerServer} = require("resource://gre/modules/devtools/dbg-server.jsm");
 const discovery = require("devtools/toolkit/discovery/discovery");
+const promise = require("promise");
 
 const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
 
 function USBRuntime(id) {
   this.id = id;
 }
 
 USBRuntime.prototype = {
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -1,20 +1,20 @@
 /* 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";
 
 /* General utilities used throughout devtools. */
 
-// hasChrome is provided as a global by the loader. It is true if we are running
-// on the main thread, and false if we are running on a worker thread.
 var { Ci, Cu } = require("chrome");
 var Services = require("Services");
+var promise = require("promise");
+var { setTimeout } = require("Timer");
 
 /**
  * Turn the error |aError| into a string, without fail.
  */
 exports.safeErrorString = function safeErrorString(aError) {
   try {
     let errorString = aError.toString();
     if (typeof errorString == "string") {
--- a/toolkit/devtools/Loader.jsm
+++ b/toolkit/devtools/Loader.jsm
@@ -39,46 +39,52 @@ this.EXPORTED_SYMBOLS = ["DevToolsLoader
 /**
  * Providers are different strategies for loading the devtools.
  */
 
 let Timer = Cu.import("resource://gre/modules/Timer.jsm", {});
 
 let loaderGlobals = {
   isWorker: false,
-  Debugger: Debugger,
-  promise: promise,
   reportError: Cu.reportError,
-  setInterval: Timer.setInterval,
-  setTimeout: Timer.setTimeout,
-  clearInterval: Timer.clearInterval,
-  clearTimeout: Timer.clearTimeout,
-  xpcInspector: xpcInspector,
 
   btoa: btoa,
   console: console,
   _Iterator: Iterator,
-  ChromeWorker: ChromeWorker,
   loader: {
     lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils),
     lazyImporter: XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils),
     lazyServiceGetter: XPCOMUtils.defineLazyServiceGetter.bind(XPCOMUtils)
   },
 };
 
+let loaderModules = {
+  "Debugger": Debugger,
+  "Services": Object.create(Services),
+  "Timer": Object.create(Timer),
+  "toolkit/loader": loader,
+  "xpcInspector": xpcInspector,
+  "promise": promise,
+};
+try {
+  let { indexedDB } = Cu.Sandbox(this, {wantGlobalProperties:["indexedDB"]});
+  loaderModules.indexedDB = indexedDB;
+} catch(e) {
+  // On xpcshell, we can't instantiate indexedDB without crashing
+}
+
+let sharedGlobalBlacklist = ["sdk/indexed-db", "devtools/toolkit/qrcode/decoder/index"];
+
 // Used when the tools should be loaded from the Firefox package itself (the default)
 function BuiltinProvider() {}
 BuiltinProvider.prototype = {
   load: function() {
     this.loader = new loader.Loader({
       id: "fx-devtools",
-      modules: {
-        "Services": Object.create(Services),
-        "toolkit/loader": loader,
-      },
+      modules: loaderModules,
       paths: {
         // When you add a line to this mapping, don't forget to make a
         // corresponding addition to the SrcdirProvider mapping below as well.
         "": "resource://gre/modules/commonjs/",
         "main": "resource:///modules/devtools/main.js",
         "devtools": "resource:///modules/devtools",
         "devtools/toolkit": "resource://gre/modules/devtools",
         "devtools/server": "resource://gre/modules/devtools/server",
@@ -98,17 +104,19 @@ BuiltinProvider.prototype = {
         "acorn/util/walk": "resource://gre/modules/devtools/acorn/walk.js",
         "tern": "resource://gre/modules/devtools/tern",
         "source-map": "resource://gre/modules/devtools/SourceMap.jsm",
 
         // Allow access to xpcshell test items from the loader.
         "xpcshell-test": "resource://test"
       },
       globals: loaderGlobals,
-      invisibleToDebugger: this.invisibleToDebugger
+      invisibleToDebugger: this.invisibleToDebugger,
+      sharedGlobal: true,
+      sharedGlobalBlacklist: sharedGlobalBlacklist
     });
 
     return promise.resolve(undefined);
   },
 
   unload: function(reason) {
     loader.unload(this.loader, reason);
     delete this.loader;
@@ -148,20 +156,17 @@ SrcdirProvider.prototype = {
     let gcliURI = this.fileURI(OS.Path.join(toolkitDir, "gcli", "source", "lib", "gcli"));
     let projecteditorURI = this.fileURI(OS.Path.join(devtoolsDir, "projecteditor"));
     let acornURI = this.fileURI(OS.Path.join(toolkitDir, "acorn"));
     let acornWalkURI = OS.Path.join(acornURI, "walk.js");
     let ternURI = OS.Path.join(toolkitDir, "tern");
     let sourceMapURI = this.fileURI(OS.Path.join(toolkitDir), "SourceMap.jsm");
     this.loader = new loader.Loader({
       id: "fx-devtools",
-      modules: {
-        "Services": Object.create(Services),
-        "toolkit/loader": loader,
-      },
+      modules: loaderModules,
       paths: {
         "": "resource://gre/modules/commonjs/",
         "main": mainURI,
         "devtools": devtoolsURI,
         "devtools/toolkit": toolkitURI,
         "devtools/server": serverURI,
         "devtools/toolkit/webconsole": webconsoleURI,
         "devtools/app-actor-front": appActorURI,
@@ -176,17 +181,19 @@ SrcdirProvider.prototype = {
         "gcli": gcliURI,
         "projecteditor": projecteditorURI,
         "acorn": acornURI,
         "acorn/util/walk": acornWalkURI,
         "tern": ternURI,
         "source-map": sourceMapURI,
       },
       globals: loaderGlobals,
-      invisibleToDebugger: this.invisibleToDebugger
+      invisibleToDebugger: this.invisibleToDebugger,
+      sharedGlobal: true,
+      sharedGlobalBlacklist: sharedGlobalBlacklist
     });
 
     return this._writeManifest(devtoolsDir).then(null, Cu.reportError);
   },
 
   unload: function(reason) {
     loader.unload(this.loader, reason);
     delete this.loader;
--- a/toolkit/devtools/event-emitter.js
+++ b/toolkit/devtools/event-emitter.js
@@ -19,16 +19,17 @@
   }
 }).call(this, function (require, exports, module) {
 
 this.EventEmitter = function EventEmitter() {};
 module.exports = EventEmitter;
 
 const { Cu, components } = require("chrome");
 const Services = require("Services");
+const promise = require("promise");
 
 /**
  * Decorate an object with event emitter functionality.
  *
  * @param Object aObjectToDecorate
  *        Bind all public methods of EventEmitter to
  *        the aObjectToDecorate object.
  */
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -2,22 +2,26 @@
 /* vim: set ft=javascript ts=2 et sw=2 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";
 
 const Services = require("Services");
-const { Cc, Ci, Cu, components } = require("chrome");
+const { Cc, Ci, Cu, components, ChromeWorker } = require("chrome");
 const { ActorPool } = require("devtools/server/actors/common");
 const { DebuggerServer } = require("devtools/server/main");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dbg_assert, dumpn, update } = DevToolsUtils;
 const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
+const promise = require("promise");
+const Debugger = require("Debugger");
+const xpcInspector = require("xpcInspector");
+
 const { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");
 const { CssLogic } = require("devtools/styleinspector/css-logic");
 
 DevToolsUtils.defineLazyGetter(this, "NetUtil", () => {
   return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
 });
 
 let B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
--- a/toolkit/devtools/server/actors/storage.js
+++ b/toolkit/devtools/server/actors/storage.js
@@ -1179,17 +1179,17 @@ StorageActors.createActor({
     if (/^(about:|chrome:)/.test(host)) {
       principal = Services.scriptSecurityManager.getSystemPrincipal();
     }
     else {
       let uri = Services.io.newURI(host, null, null);
       principal = Services.scriptSecurityManager.getCodebasePrincipal(uri);
     }
 
-    return indexedDB.openForPrincipal(principal, name);
+    return require("indexedDB").openForPrincipal(principal, name);
   },
 
   /**
    * Fetches and stores all the metadata information for the given database
    * `name` for the given `host`. The stored metadata information is of
    * `DatabaseMetadata` type.
    */
   getDBMetaData: function(host, name) {
--- a/toolkit/devtools/server/actors/tracer.js
+++ b/toolkit/devtools/server/actors/tracer.js
@@ -2,19 +2,17 @@
  * 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";
 
 const { Cu } = require("chrome");
 const { DebuggerServer } = require("devtools/server/main");
 const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
-
-Cu.import("resource://gre/modules/jsdebugger.jsm");
-addDebuggerToGlobal(this);
+const Debugger = require("Debugger");
 
 // TODO bug 943125: remove this polyfill and use Debugger.Frame.prototype.depth
 // once it is implemented.
 if (!Object.getOwnPropertyDescriptor(Debugger.Frame.prototype, "depth")) {
   Debugger.Frame.prototype._depth = null;
   Object.defineProperty(Debugger.Frame.prototype, "depth", {
     get: function () {
       if (this._depth === null) {
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const { DebuggerServer, ActorPool } = require("devtools/server/main");
 const { EnvironmentActor, LongStringActor, ObjectActor, ThreadActor } = require("devtools/server/actors/script");
 const { update } = require("devtools/toolkit/DevToolsUtils");
+const Debugger = require("Debugger");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => {
   return require("devtools/toolkit/webconsole/network-monitor")
          .NetworkMonitor;
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -14,16 +14,17 @@ let { Ci, Cc, CC, Cu, Cr } = require("ch
 let Services = require("Services");
 let { ActorPool } = require("devtools/server/actors/common");
 let { DebuggerTransport, LocalDebuggerTransport, ChildDebuggerTransport } =
   require("devtools/toolkit/transport/transport");
 let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 let { dumpn, dumpv, dbg_assert } = DevToolsUtils;
 let Services = require("Services");
 let EventEmitter = require("devtools/toolkit/event-emitter");
+let Debugger = require("Debugger");
 
 // Until all Debugger server code is converted to SDK modules,
 // imports Components.* alias from chrome module.
 var { Ci, Cc, CC, Cu, Cr } = require("chrome");
 // On B2G, `this` != Global scope, so `Ci` won't be binded on `this`
 // (i.e. this.Ci is undefined) Then later, when using loadSubScript,
 // Ci,... won't be defined for sub scripts.
 this.Ci = Ci;
@@ -838,16 +839,25 @@ var DebuggerServer = {
 EventEmitter.decorate(DebuggerServer);
 
 if (this.exports) {
   exports.DebuggerServer = DebuggerServer;
 }
 // Needed on B2G (See header note)
 this.DebuggerServer = DebuggerServer;
 
+// When using DebuggerServer.addActors, some symbols are expected to be in
+// the scope of the added actor even before the corresponding modules are
+// loaded, so let's explicitly bind the expected symbols here.
+let includes = ["Components", "Ci", "Cu", "require", "Services", "DebuggerServer",
+                "ActorPool", "DevToolsUtils"];
+includes.forEach(name => {
+  DebuggerServer[name] = this[name];
+});
+
 // Export ActorPool for requirers of main.js
 if (this.exports) {
   exports.ActorPool = ActorPool;
 }
 
 /**
  * Creates a DebuggerServerConnection.
  *
--- a/toolkit/devtools/server/tests/unit/testactors.js
+++ b/toolkit/devtools/server/tests/unit/testactors.js
@@ -1,15 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/script");
 const { DebuggerServer } = require("devtools/server/main");
+const promise = require("promise");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(aGlobal) {
   gTestGlobals.push(aGlobal);
 };
 
 // A mock tab list, for use by tests. This simply presents each global in
 // gTestGlobals as a tab, and the list is fixed: it never calls its
--- a/toolkit/devtools/tests/unit/test_invisible_loader.js
+++ b/toolkit/devtools/tests/unit/test_invisible_loader.js
@@ -16,33 +16,33 @@ function run_test() {
 }
 
 function visible_loader() {
   let loader = new DevToolsLoader();
   loader.invisibleToDebugger = false;
   loader.require("devtools/css-color");
 
   let dbg = new Debugger();
-  let sandbox = loader._provider.loader.sandboxes[COLOR_URI];
+  let sandbox = loader._provider.loader.sharedGlobalSandbox;
 
   try {
     dbg.addDebuggee(sandbox);
     do_check_true(true);
   } catch(e) {
     do_throw("debugger could not add visible value");
   }
 }
 
 function invisible_loader() {
   let loader = new DevToolsLoader();
   loader.invisibleToDebugger = true;
   loader.require("devtools/css-color");
 
   let dbg = new Debugger();
-  let sandbox = loader._provider.loader.sandboxes[COLOR_URI];
+  let sandbox = loader._provider.loader.sharedGlobalSandbox;
 
   try {
     dbg.addDebuggee(sandbox);
     do_throw("debugger added invisible value");
   } catch(e) {
     do_check_true(true);
   }
 }
--- a/toolkit/devtools/transport/packets.js
+++ b/toolkit/devtools/transport/packets.js
@@ -23,17 +23,17 @@
  *   * destroy()
  *     Called to clean up at the end of use
  */
 
 const { Cc, Ci, Cu } = require("chrome");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dumpn, dumpv } = DevToolsUtils;
 const StreamUtils = require("devtools/toolkit/transport/stream-utils");
-const EventEmitter = require("devtools/toolkit/event-emitter");
+const promise = require("promise");
 
 DevToolsUtils.defineLazyGetter(this, "unicodeConverter", () => {
   const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                            .createInstance(Ci.nsIScriptableUnicodeConverter);
   unicodeConverter.charset = "UTF-8";
   return unicodeConverter;
 });
 
--- a/toolkit/devtools/transport/stream-utils.js
+++ b/toolkit/devtools/transport/stream-utils.js
@@ -4,16 +4,17 @@
 
 "use strict";
 
 const { Ci, Cc, Cu, Cr, CC } = require("chrome");
 const Services = require("Services");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dumpv } = DevToolsUtils;
 const EventEmitter = require("devtools/toolkit/event-emitter");
+const promise = require("promise");
 
 DevToolsUtils.defineLazyGetter(this, "IOUtil", () => {
   return Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
 });
 
 DevToolsUtils.defineLazyGetter(this, "ScriptableInputStream", () => {
   return CC("@mozilla.org/scriptableinputstream;1",
             "nsIScriptableInputStream", "init");
--- a/toolkit/devtools/transport/tests/unit/testactors.js
+++ b/toolkit/devtools/transport/tests/unit/testactors.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const { ActorPool, appendExtraActors, createExtraActors } =
   require("devtools/server/actors/common");
 const { RootActor } = require("devtools/server/actors/root");
 const { ThreadActor } = require("devtools/server/actors/script");
 const { DebuggerServer } = require("devtools/server/main");
+const promise = require("promise");
 
 var gTestGlobals = [];
 DebuggerServer.addTestGlobal = function(aGlobal) {
   gTestGlobals.push(aGlobal);
 };
 
 // A mock tab list, for use by tests. This simply presents each global in
 // gTestGlobals as a tab, and the list is fixed: it never calls its
--- a/toolkit/devtools/transport/transport.js
+++ b/toolkit/devtools/transport/transport.js
@@ -24,16 +24,17 @@
 
 const { Cc, Ci, Cr, Cu, CC } = require("chrome");
 const Services = require("Services");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dumpn, dumpv } = DevToolsUtils;
 const StreamUtils = require("devtools/toolkit/transport/stream-utils");
 const { Packet, JSONPacket, BulkPacket } =
   require("devtools/toolkit/transport/packets");
+const promise = require("promise");
 
 DevToolsUtils.defineLazyGetter(this, "Pipe", () => {
   return CC("@mozilla.org/pipe;1", "nsIPipe", "init");
 });
 
 DevToolsUtils.defineLazyGetter(this, "ScriptableInputStream", () => {
   return CC("@mozilla.org/scriptableinputstream;1",
             "nsIScriptableInputStream", "init");
--- a/toolkit/devtools/worker-loader.js
+++ b/toolkit/devtools/worker-loader.js
@@ -306,28 +306,26 @@ if (typeof Components === "object") {
   const Timer = Cu.import("resource://gre/modules/Timer.jsm", {});
   const xpcInspector = Cc["@mozilla.org/jsinspector;1"].
                        getService(Ci.nsIJSInspector);
 
   this.worker = new WorkerDebuggerLoader({
     createSandbox: createSandbox,
     globals: {
       "isWorker": true,
-      "Debugger": Debugger,
-      "setInterval": Timer.setInterval,
-      "setTimeout": Timer.setTimeout,
-      "clearInterval": Timer.clearInterval,
-      "clearTimeout": Timer.clearTimeout,
-      "xpcInspector": xpcInspector,
       "reportError": Cu.reportError,
     },
     loadInSandbox: loadInSandbox,
     modules: {
       "Services": {},
       "chrome": chrome,
+      "promise": Promise,
+      "Debugger": Debugger,
+      "xpcInspector": xpcInspector,
+      "Timer": Object.create(Timer)
     },
     paths: {
       "": "resource://gre/modules/commonjs/",
       "devtools": "resource:///modules/devtools",
       "devtools/server": "resource://gre/modules/devtools/server",
       "devtools/toolkit": "resource://gre/modules/devtools",
       "source-map": "resource://gre/modules/devtools/source-map",
       "xpcshell-test": "resource://test",