Bug 1295082 - Put Extension in BaseContext draft
authorRob Wu <rob@robwu.nl>
Mon, 15 Aug 2016 01:04:58 -0700
changeset 400742 bf1534604d9cf27e5fd5bc6240b118d101a06c5c
parent 400444 6e191a55c3d23e83e6a2e72e4e80c1dc21516493
child 400743 8be54c94eb3924584394bd0a029f9e8e7203def9
push id26267
push userbmo:rob@robwu.nl
push dateMon, 15 Aug 2016 17:18:48 +0000
bugs1295082
milestone51.0a1
Bug 1295082 - Put Extension in BaseContext ExtensionContext in Extension.jsm has |extension| as an instance member, so use it instead of passing |extension| to registerSchemaAPI's callback. And to make sure that this pattern also works in content processes, move the |extension| member to BaseContext. MozReview-Commit-ID: BgsGGCPQxJR
browser/components/extensions/ext-bookmarks.js
browser/components/extensions/ext-browserAction.js
browser/components/extensions/ext-commands.js
browser/components/extensions/ext-contextMenus.js
browser/components/extensions/ext-history.js
browser/components/extensions/ext-pageAction.js
browser/components/extensions/ext-tabs.js
browser/components/extensions/ext-windows.js
mobile/android/components/extensions/ext-pageAction.js
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionContent.jsm
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/ext-alarms.js
toolkit/components/extensions/ext-backgroundPage.js
toolkit/components/extensions/ext-cookies.js
toolkit/components/extensions/ext-downloads.js
toolkit/components/extensions/ext-extension.js
toolkit/components/extensions/ext-i18n.js
toolkit/components/extensions/ext-idle.js
toolkit/components/extensions/ext-management.js
toolkit/components/extensions/ext-notifications.js
toolkit/components/extensions/ext-runtime.js
toolkit/components/extensions/ext-storage.js
toolkit/components/extensions/ext-test.js
toolkit/components/extensions/ext-webNavigation.js
toolkit/components/extensions/ext-webRequest.js
toolkit/components/extensions/test/mochitest/test_ext_contentscript_context.html
toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
--- a/browser/components/extensions/ext-bookmarks.js
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -72,17 +72,17 @@ function convert(result) {
     node.url = result.url.href; // Output is always URL object.
   } else {
     node.dateGroupModified = result.lastModified.getTime();
   }
 
   return node;
 }
 
-extensions.registerSchemaAPI("bookmarks", (extension, context) => {
+extensions.registerSchemaAPI("bookmarks", context => {
   return {
     bookmarks: {
       get: function(idOrIdList) {
         let list = Array.isArray(idOrIdList) ? idOrIdList : [idOrIdList];
 
         return Task.spawn(function* () {
           let bookmarks = [];
           for (let id of list) {
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -254,17 +254,18 @@ extensions.on("manifest_browser_action",
 extensions.on("shutdown", (type, extension) => {
   if (browserActionMap.has(extension)) {
     browserActionMap.get(extension).shutdown();
     browserActionMap.delete(extension);
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
-extensions.registerSchemaAPI("browserAction", (extension, context) => {
+extensions.registerSchemaAPI("browserAction", context => {
+  let {extension} = context;
   return {
     browserAction: {
       onClicked: new EventManager(context, "browserAction.onClicked", fire => {
         let listener = () => {
           let tab = TabManager.activeTab;
           fire(TabManager.convert(extension, tab));
         };
         BrowserAction.for(extension).on("click", listener);
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -223,17 +223,18 @@ extensions.on("shutdown", (type, extensi
   let commandsList = commandsMap.get(extension);
   if (commandsList) {
     commandsList.unregister();
     commandsMap.delete(extension);
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
-extensions.registerSchemaAPI("commands", (extension, context) => {
+extensions.registerSchemaAPI("commands", context => {
+  let {extension} = context;
   return {
     commands: {
       getAll() {
         let commands = commandsMap.get(extension).commands;
         return Promise.resolve(Array.from(commands, ([name, command]) => {
           return ({
             name,
             description: command.description,
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -480,17 +480,18 @@ extensions.on("shutdown", (type, extensi
   gRootItems.delete(extension);
   if (--gExtensionCount == 0) {
     Services.obs.removeObserver(contextMenuObserver,
                                 "on-build-contextmenu");
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
-extensions.registerSchemaAPI("contextMenus", (extension, context) => {
+extensions.registerSchemaAPI("contextMenus", context => {
+  let {extension} = context;
   return {
     contextMenus: {
       create: function(createProperties, callback) {
         let menuItem = new MenuItem(extension, context, createProperties);
         gContextMenuMap.get(extension).set(menuItem.id, menuItem);
         if (callback) {
           runSafe(context, callback);
         }
--- a/browser/components/extensions/ext-history.js
+++ b/browser/components/extensions/ext-history.js
@@ -125,17 +125,17 @@ function getObserver() {
       },
     };
     EventEmitter.decorate(_observer);
     PlacesUtils.history.addObserver(_observer, false);
   }
   return _observer;
 }
 
-extensions.registerSchemaAPI("history", (extension, context) => {
+extensions.registerSchemaAPI("history", context => {
   return {
     history: {
       addUrl: function(details) {
         let transition, date;
         try {
           transition = getTransitionType(details.transition);
         } catch (error) {
           return Promise.reject({message: error.message});
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -212,17 +212,18 @@ extensions.on("shutdown", (type, extensi
 /* eslint-enable mozilla/balanced-listeners */
 
 PageAction.for = extension => {
   return pageActionMap.get(extension);
 };
 
 global.pageActionFor = PageAction.for;
 
-extensions.registerSchemaAPI("pageAction", (extension, context) => {
+extensions.registerSchemaAPI("pageAction", context => {
+  let {extension} = context;
   return {
     pageAction: {
       onClicked: new EventManager(context, "pageAction.onClicked", fire => {
         let listener = (evt, tab) => {
           fire(TabManager.convert(extension, tab));
         };
         let pageAction = PageAction.for(extension);
 
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -258,17 +258,18 @@ let tabListener = {
 
   awaitTabReady(tab) {
     return new Promise((resolve, reject) => {
       this.tabReadyPromises.set(tab, {resolve, reject});
     });
   },
 };
 
-extensions.registerSchemaAPI("tabs", (extension, context) => {
+extensions.registerSchemaAPI("tabs", context => {
+  let {extension} = context;
   let self = {
     tabs: {
       onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
         let tabId = TabManager.getId(tab);
         let windowId = WindowManager.getId(tab.ownerGlobal);
         fire({tabId, windowId});
       }).api(),
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -10,17 +10,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
 } = ExtensionUtils;
 
-extensions.registerSchemaAPI("windows", (extension, context) => {
+extensions.registerSchemaAPI("windows", context => {
+  let {extension} = context;
   return {
     windows: {
       onCreated:
       new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
         fire(WindowManager.convert(extension, window));
       }).api(),
 
       onRemoved:
--- a/mobile/android/components/extensions/ext-pageAction.js
+++ b/mobile/android/components/extensions/ext-pageAction.js
@@ -112,17 +112,18 @@ extensions.on("manifest_page_action", (t
 extensions.on("shutdown", (type, extension) => {
   if (pageActionMap.has(extension)) {
     pageActionMap.get(extension).shutdown();
     pageActionMap.delete(extension);
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
-extensions.registerSchemaAPI("pageAction", (extension, context) => {
+extensions.registerSchemaAPI("pageAction", context => {
+  let {extension} = context;
   return {
     pageAction: {
       onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => {
         let listener = (event) => {
           fire();
         };
         pageActionMap.get(extension).on("click", listener);
         return () => {
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -182,17 +182,17 @@ var Management = {
   },
 
   registerSchemaAPI(namespace, api) {
     this.schemaApis.push({namespace, api});
   },
 
   // Mash together into a single object all the APIs registered by the
   // functions above. Return the merged object.
-  generateAPIs(extension, context, apis, namespaces = null) {
+  generateAPIs(context, apis, namespaces = null) {
     let obj = {};
 
     // Recursively copy properties from source to dest.
     function copy(dest, source) {
       for (let prop in source) {
         let desc = Object.getOwnPropertyDescriptor(source, prop);
         if (typeof(desc.value) == "object") {
           if (!(prop in dest)) {
@@ -205,26 +205,26 @@ var Management = {
       }
     }
 
     for (let api of apis) {
       if (namespaces && !namespaces.includes(api.namespace)) {
         continue;
       }
       if (api.permission) {
-        if (!extension.hasPermission(api.permission)) {
+        if (!context.extension.hasPermission(api.permission)) {
           continue;
         }
       }
 
-      api = api.api(extension, context);
+      api = api.api(context);
       copy(obj, api);
     }
 
-    for (let api of extension.apis) {
+    for (let api of context.extension.apis) {
       copy(obj, api.getAPI(context));
     }
 
     return obj;
   },
 
   // The ext-*.js scripts can ask to be notified for certain hooks.
   on(hook, callback) {
@@ -249,20 +249,19 @@ var Management = {
 // |params| is an object with the following properties:
 // |type| is one of "background", "popup", or "tab".
 // |contentWindow| is the DOM window the content runs in.
 // |uri| is the URI of the content (optional).
 // |docShell| is the docshell the content runs in (optional).
 // |incognito| is the content running in a private context (default: false).
 ExtensionContext = class extends BaseContext {
   constructor(extension, params) {
-    super(extension.id);
+    super(extension);
 
     let {type, uri} = params;
-    this.extension = extension;
     this.type = type;
     this.uri = uri || extension.baseURI;
     this.incognito = params.incognito || false;
 
     if (params.contentWindow) {
       this.setContentWindow(params.contentWindow);
     }
 
@@ -330,17 +329,17 @@ class ProxyContext extends ExtensionCont
     params.contentWindow = null;
     params.uri = NetUtil.newURI(params.url);
 
     super(extension, params);
     this.messageManager = messageManager;
     this.principal_ = principal;
 
     this.apiObj = {};
-    GlobalManager.injectInObject(extension, this, null, this.apiObj, ["storage", "test"]);
+    GlobalManager.injectInObject(this, null, this.apiObj, ["storage", "test"]);
 
     this.listenerProxies = new Map();
 
     this.sandbox = Cu.Sandbox(principal, {});
   }
 
   get principal() {
     return this.principal_;
@@ -615,37 +614,37 @@ GlobalManager = {
       this.initialized = false;
     }
   },
 
   getExtension(extensionId) {
     return this.extensionMap.get(extensionId);
   },
 
-  injectInObject(extension, context, defaultCallback, dest, namespaces = null) {
-    let api = Management.generateAPIs(extension, context, Management.apis, namespaces);
+  injectInObject(context, defaultCallback, dest, namespaces = null) {
+    let api = Management.generateAPIs(context, Management.apis, namespaces);
     injectAPI(api, dest);
 
-    let schemaApi = Management.generateAPIs(extension, context, Management.schemaApis, namespaces);
+    let schemaApi = Management.generateAPIs(context, Management.schemaApis, namespaces);
 
     // Add in any extra API namespaces which do not have implementations
     // outside of their schema file.
     schemaApi.extensionTypes = {};
 
     let schemaWrapper = {
       get principal() {
         return context.principal;
       },
 
       get cloneScope() {
         return context.cloneScope;
       },
 
       hasPermission(permission) {
-        return extension.hasPermission(permission);
+        return context.extension.hasPermission(permission);
       },
 
       callFunction(path, name, args) {
         return findPathInObject(schemaApi, path)[name](...args);
       },
 
       callFunctionNoReturn(path, name, args) {
         return findPathInObject(schemaApi, path)[name](...args);
@@ -693,24 +692,24 @@ GlobalManager = {
       hasListener(path, name, listener) {
         return findPathInObject(schemaApi, path)[name].hasListener.call(null, listener);
       },
     };
     Schemas.inject(dest, schemaWrapper);
   },
 
   observe(contentWindow, topic, data) {
-    let inject = (extension, context) => {
+    let inject = context => {
       // We create two separate sets of bindings, one for the `chrome`
       // global, and one for the `browser` global. The latter returns
       // Promise objects if a callback is not passed, while the former
       // does not.
       let injectObject = (name, defaultCallback) => {
         let browserObj = Cu.createObjectIn(contentWindow, {defineAs: name});
-        this.injectInObject(extension, context, defaultCallback, browserObj);
+        this.injectInObject(context, defaultCallback, browserObj);
       };
 
       injectObject("browser", null);
       injectObject("chrome", () => {});
     };
 
     let id = ExtensionManagement.getAddonIdForWindow(contentWindow);
 
@@ -751,17 +750,17 @@ GlobalManager = {
     }
 
 
     let extension = this.extensionMap.get(id);
     let uri = contentWindow.document.documentURIObject;
     let incognito = PrivateBrowsingUtils.isContentWindowPrivate(contentWindow);
 
     let context = new ExtensionContext(extension, {type, contentWindow, uri, docShell, incognito});
-    inject(extension, context);
+    inject(context);
     if (type == "background") {
       this._initializeBackgroundPage(contentWindow);
     }
 
     let eventHandler = docShell.chromeEventHandler;
     let listener = event => {
       if (event.target != docShell.contentViewer.DOMDocument) {
         return;
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -307,24 +307,22 @@ function getWindowMessageManager(content
 
 var DocumentManager;
 var ExtensionManager;
 
 // Scope in which extension content script code can run. It uses
 // Cu.Sandbox to run the code. There is a separate scope for each
 // frame.
 class ExtensionContext extends BaseContext {
-  constructor(extensionId, contentWindow, contextOptions = {}) {
-    super(extensionId);
+  constructor(extension, contentWindow, contextOptions = {}) {
+    super(extension);
 
     let {isExtensionPage} = contextOptions;
 
     this.isExtensionPage = isExtensionPage;
-    this.extension = ExtensionManager.get(extensionId);
-    this.extensionId = extensionId;
 
     this.setContentWindow(contentWindow);
 
     let frameId = WebNavigationFrames.getFrameId(contentWindow);
     this.frameId = frameId;
 
     this.scripts = [];
 
@@ -333,31 +331,31 @@ class ExtensionContext extends BaseConte
 
     let prin;
     let contentPrincipal = contentWindow.document.nodePrincipal;
     let ssm = Services.scriptSecurityManager;
 
     // copy origin attributes from the content window origin attributes to
     // preserve the user context id. overwrite the addonId.
     let attrs = contentPrincipal.originAttributes;
-    attrs.addonId = extensionId;
+    attrs.addonId = this.extensionId;
     let extensionPrincipal = ssm.createCodebasePrincipal(this.extension.baseURI, attrs);
     Object.defineProperty(this, "principal",
                           {value: extensionPrincipal, enumerable: true, configurable: true});
 
     if (ssm.isSystemPrincipal(contentPrincipal)) {
       // Make sure we don't hand out the system principal by accident.
       // also make sure that the null principal has the right origin attributes
       prin = ssm.createNullPrincipal(attrs);
     } else {
       prin = [contentPrincipal, extensionPrincipal];
     }
 
     if (isExtensionPage) {
-      if (ExtensionManagement.getAddonIdForWindow(this.contentWindow) != extensionId) {
+      if (ExtensionManagement.getAddonIdForWindow(this.contentWindow) != this.extensionId) {
         throw new Error("Invalid target window for this extension context");
       }
       // This is an iframe with content script API enabled and its principal should be the
       // contentWindow itself. (we create a sandbox with the contentWindow as principal and with X-rays disabled
       // because it enables us to create the APIs object in this sandbox object and then copying it
       // into the iframe's window, see Bug 1214658 for rationale)
       this.sandbox = Cu.Sandbox(contentWindow, {
         sandboxPrototype: contentWindow,
@@ -395,17 +393,17 @@ class ExtensionContext extends BaseConte
       },
     };
 
     let url = contentWindow.location.href;
     // The |sender| parameter is passed directly to the extension.
     let sender = {id: this.extension.uuid, frameId, url};
     // Properties in |filter| must match those in the |recipient|
     // parameter of sendMessage.
-    let filter = {extensionId, frameId};
+    let filter = {extensionId: this.extensionId, frameId};
     this.messenger = new Messenger(this, [mm], sender, filter, delegate);
 
     this.chromeObj = Cu.createObjectIn(this.sandbox, {defineAs: "browser"});
 
     // Sandboxes don't get Xrays for some weird compatibility
     // reason. However, we waive here anyway in case that changes.
     Cu.waiveXrays(this.sandbox).chrome = this.chromeObj;
 
@@ -527,19 +525,21 @@ DocumentManager = {
         return;
       }
 
       // Enable the content script APIs should be available in subframes' window
       // if it is recognized as a valid addon id (see Bug 1214658 for rationale).
       const {CONTENTSCRIPT_PRIVILEGES} = ExtensionManagement.API_LEVELS;
       let extensionId = ExtensionManagement.getAddonIdForWindow(window);
 
-      if (ExtensionManagement.getAPILevelForWindow(window, extensionId) == CONTENTSCRIPT_PRIVILEGES &&
-          ExtensionManager.get(extensionId)) {
-        DocumentManager.getExtensionPageContext(extensionId, window);
+      if (ExtensionManagement.getAPILevelForWindow(window, extensionId) == CONTENTSCRIPT_PRIVILEGES) {
+        let extension = ExtensionManager.get(extensionId);
+        if (extension) {
+          DocumentManager.getExtensionPageContext(extension, window);
+        }
       }
 
       this.trigger("document_start", window);
       /* eslint-disable mozilla/balanced-listeners */
       window.addEventListener("DOMContentLoaded", this, true);
       window.addEventListener("load", this, true);
       /* eslint-enable mozilla/balanced-listeners */
     } else if (topic == "inner-window-destroyed") {
@@ -589,17 +589,17 @@ DocumentManager = {
   executeScript(global, extensionId, options) {
     let extension = ExtensionManager.get(extensionId);
 
     let executeInWin = (window) => {
       let deferred = PromiseUtils.defer();
       let script = new Script(extension, options, deferred);
 
       if (script.matches(window)) {
-        let context = this.getContentScriptContext(extensionId, window);
+        let context = this.getContentScriptContext(extension, window);
         context.addScript(script);
         return deferred.promise;
       }
       return null;
     };
 
     let promises = Array.from(this.enumerateWindows(global.docShell), executeInWin)
                         .filter(promise => promise);
@@ -640,37 +640,37 @@ DocumentManager = {
 
     if (extensions) {
       return Array.from(extensions.values(), ctx => ctx.sandbox);
     }
 
     return [];
   },
 
-  getContentScriptContext(extensionId, window) {
+  getContentScriptContext(extension, window) {
     let winId = getInnerWindowID(window);
     if (!this.contentScriptWindows.has(winId)) {
       this.contentScriptWindows.set(winId, new Map());
     }
 
     let extensions = this.contentScriptWindows.get(winId);
-    if (!extensions.has(extensionId)) {
-      let context = new ExtensionContext(extensionId, window);
-      extensions.set(extensionId, context);
+    if (!extensions.has(extension.id)) {
+      let context = new ExtensionContext(extension, window);
+      extensions.set(extension.id, context);
     }
 
-    return extensions.get(extensionId);
+    return extensions.get(extension.id);
   },
 
-  getExtensionPageContext(extensionId, window) {
+  getExtensionPageContext(extension, window) {
     let winId = getInnerWindowID(window);
 
     let context = this.extensionPageWindows.get(winId);
     if (!context) {
-      let context = new ExtensionContext(extensionId, window, {isExtensionPage: true});
+      let context = new ExtensionContext(extension, window, {isExtensionPage: true});
       this.extensionPageWindows.set(winId, context);
     }
 
     return context;
   },
 
   startupExtension(extensionId) {
     if (this.extensionCount == 0) {
@@ -681,17 +681,17 @@ DocumentManager = {
     let extension = ExtensionManager.get(extensionId);
     for (let global of ExtensionContent.globals.keys()) {
       // Note that we miss windows in the bfcache here. In theory we
       // could execute content scripts on a pageshow event for that
       // window, but that seems extreme.
       for (let window of this.enumerateWindows(global.docShell)) {
         for (let script of extension.scripts) {
           if (script.matches(window)) {
-            let context = this.getContentScriptContext(extensionId, window);
+            let context = this.getContentScriptContext(extension, window);
             context.addScript(script);
           }
         }
       }
     }
   },
 
   shutdownExtension(extensionId) {
@@ -719,20 +719,20 @@ DocumentManager = {
       this.uninit();
     }
   },
 
   trigger(when, window) {
     let state = this.getWindowState(window);
 
     if (state == "document_start") {
-      for (let [extensionId, extension] of ExtensionManager.extensions) {
+      for (let extension of ExtensionManager.extensions.values()) {
         for (let script of extension.scripts) {
           if (script.matches(window)) {
-            let context = this.getContentScriptContext(extensionId, window);
+            let context = this.getContentScriptContext(extension, window);
             context.addScript(script);
           }
         }
       }
     } else {
       let contexts = this.contentScriptWindows.get(getInnerWindowID(window)) || new Map();
       for (let context of contexts.values()) {
         context.triggerScripts(state);
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -157,23 +157,24 @@ class SpreadArgs extends Array {
     super();
     this.push(...args);
   }
 }
 
 let gContextId = 0;
 
 class BaseContext {
-  constructor(extensionId) {
+  constructor(extension) {
     this.onClose = new Set();
     this.checkedLastError = false;
     this._lastError = null;
     this.contextId = ++gContextId;
     this.unloaded = false;
-    this.extensionId = extensionId;
+    this.extension = extension;
+    this.extensionId = extension.id;
     this.jsonSandbox = null;
     this.active = true;
 
     this.docShell = null;
     this.contentWindow = null;
     this.innerWindowID = 0;
   }
 
--- a/toolkit/components/extensions/ext-alarms.js
+++ b/toolkit/components/extensions/ext-alarms.js
@@ -88,17 +88,18 @@ extensions.on("shutdown", (type, extensi
       alarm.clear();
     }
     alarmsMap.delete(extension);
     alarmCallbacksMap.delete(extension);
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
-extensions.registerSchemaAPI("alarms", (extension, context) => {
+extensions.registerSchemaAPI("alarms", context => {
+  let {extension} = context;
   return {
     alarms: {
       create: function(name, alarmInfo) {
         name = name || "";
         let alarms = alarmsMap.get(extension);
         if (alarms.has(name)) {
           alarms.get(name).clear();
         }
--- a/toolkit/components/extensions/ext-backgroundPage.js
+++ b/toolkit/components/extensions/ext-backgroundPage.js
@@ -138,17 +138,18 @@ extensions.on("manifest_background", (ty
 extensions.on("shutdown", (type, extension) => {
   if (backgroundPagesMap.has(extension)) {
     backgroundPagesMap.get(extension).shutdown();
     backgroundPagesMap.delete(extension);
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
-extensions.registerSchemaAPI("extension", (extension, context) => {
+extensions.registerSchemaAPI("extension", context => {
+  let {extension} = context;
   return {
     extension: {
       getBackgroundPage: function() {
         return backgroundPagesMap.get(extension).contentWindow;
       },
     },
 
     runtime: {
--- a/toolkit/components/extensions/ext-cookies.js
+++ b/toolkit/components/extensions/ext-cookies.js
@@ -233,17 +233,18 @@ function* query(detailsIn, props, extens
   while (enumerator.hasMoreElements()) {
     let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
     if (matches(cookie)) {
       yield cookie;
     }
   }
 }
 
-extensions.registerSchemaAPI("cookies", (extension, context) => {
+extensions.registerSchemaAPI("cookies", context => {
+  let {extension} = context;
   let self = {
     cookies: {
       get: function(details) {
         // FIXME: We don't sort by length of path and creation time.
         for (let cookie of query(details, ["url", "name", "storeId"], extension)) {
           return Promise.resolve(convert(cookie));
         }
 
--- a/toolkit/components/extensions/ext-downloads.js
+++ b/toolkit/components/extensions/ext-downloads.js
@@ -381,17 +381,18 @@ function queryHelper(query) {
       if (matchFn(download)) {
         results.push(download);
       }
     }
     return results;
   });
 }
 
-extensions.registerSchemaAPI("downloads", (extension, context) => {
+extensions.registerSchemaAPI("downloads", context => {
+  let {extension} = context;
   return {
     downloads: {
       download(options) {
         if (options.filename != null) {
           if (options.filename.length == 0) {
             return Promise.reject({message: "filename must not be empty"});
           }
 
--- a/toolkit/components/extensions/ext-extension.js
+++ b/toolkit/components/extensions/ext-extension.js
@@ -1,11 +1,12 @@
 "use strict";
 
-extensions.registerSchemaAPI("extension", (extension, context) => {
+extensions.registerSchemaAPI("extension", context => {
+  let {extension} = context;
   return {
     extension: {
       getURL: function(url) {
         return extension.baseURI.resolve(url);
       },
 
       getViews: function(fetchProperties) {
         let result = Cu.cloneInto([], context.cloneScope);
--- a/toolkit/components/extensions/ext-i18n.js
+++ b/toolkit/components/extensions/ext-i18n.js
@@ -2,17 +2,18 @@
 
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   detectLanguage,
 } = ExtensionUtils;
 
-extensions.registerSchemaAPI("i18n", (extension, context) => {
+extensions.registerSchemaAPI("i18n", context => {
+  let {extension} = context;
   return {
     i18n: {
       getMessage: function(messageName, substitutions) {
         return extension.localizeMessage(messageName, substitutions, {cloneScope: context.cloneScope});
       },
 
       getAcceptLanguages: function() {
         let result = extension.localeData.acceptLanguages;
--- a/toolkit/components/extensions/ext-idle.js
+++ b/toolkit/components/extensions/ext-idle.js
@@ -1,11 +1,11 @@
 "use strict";
 
-extensions.registerSchemaAPI("idle", (extension, context) => {
+extensions.registerSchemaAPI("idle", context => {
   return {
     idle: {
       queryState: function(detectionIntervalInSeconds) {
         return Promise.resolve("active");
       },
     },
   };
 });
--- a/toolkit/components/extensions/ext-management.js
+++ b/toolkit/components/extensions/ext-management.js
@@ -1,9 +1,9 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-extensions.registerSchemaAPI("management", (extension, context) => {
+extensions.registerSchemaAPI("management", context => {
   return {
     management: {},
   };
 });
--- a/toolkit/components/extensions/ext-notifications.js
+++ b/toolkit/components/extensions/ext-notifications.js
@@ -87,17 +87,18 @@ extensions.on("shutdown", (type, extensi
     }
     notificationsMap.delete(extension);
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 var nextId = 0;
 
-extensions.registerSchemaAPI("notifications", (extension, context) => {
+extensions.registerSchemaAPI("notifications", context => {
+  let {extension} = context;
   return {
     notifications: {
       create: function(notificationId, options) {
         if (!notificationId) {
           notificationId = String(nextId++);
         }
 
         let notifications = notificationsMap.get(extension);
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -8,17 +8,18 @@ Cu.import("resource://gre/modules/Extens
 var {
   EventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
                                   "resource://gre/modules/NativeMessaging.jsm");
 
-extensions.registerSchemaAPI("runtime", (extension, context) => {
+extensions.registerSchemaAPI("runtime", context => {
+  let {extension} = context;
   return {
     runtime: {
       onStartup: new EventManager(context, "runtime.onStartup", fire => {
         extension.onStartup = fire;
         return () => {
           extension.onStartup = null;
         };
       }).api(),
--- a/toolkit/components/extensions/ext-storage.js
+++ b/toolkit/components/extensions/ext-storage.js
@@ -5,17 +5,18 @@ var {classes: Cc, interfaces: Ci, utils:
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
                                   "resource://gre/modules/ExtensionStorage.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
 } = ExtensionUtils;
 
-extensions.registerSchemaAPI("storage", (extension, context) => {
+extensions.registerSchemaAPI("storage", context => {
+  let {extension} = context;
   return {
     storage: {
       local: {
         get: function(keys) {
           return ExtensionStorage.get(extension.id, keys);
         },
         set: function(items) {
           return ExtensionStorage.set(extension.id, items, context);
--- a/toolkit/components/extensions/ext-test.js
+++ b/toolkit/components/extensions/ext-test.js
@@ -20,17 +20,18 @@ extensions.on("shutdown", (type, extensi
 extensions.on("test-message", (type, extension, ...args) => {
   let handlers = messageHandlers.get(extension);
   for (let handler of handlers) {
     handler(...args);
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
-extensions.registerSchemaAPI("test", (extension, context) => {
+extensions.registerSchemaAPI("test", context => {
+  let {extension} = context;
   return {
     test: {
       sendMessage: function(...args) {
         extension.emit("test-message", ...args);
       },
 
       notifyPass: function(msg) {
         extension.emit("test-done", true, msg);
--- a/toolkit/components/extensions/ext-webNavigation.js
+++ b/toolkit/components/extensions/ext-webNavigation.js
@@ -153,17 +153,17 @@ function convertGetFrameResult(tabId, da
     errorOccurred: data.errorOccurred,
     url: data.url,
     tabId,
     frameId: ExtensionManagement.getFrameId(data.windowId),
     parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
   };
 }
 
-extensions.registerSchemaAPI("webNavigation", (extension, context) => {
+extensions.registerSchemaAPI("webNavigation", context => {
   return {
     webNavigation: {
       onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(),
       onCommitted: new WebNavigationEventManager(context, "onCommitted").api(),
       onDOMContentLoaded: new WebNavigationEventManager(context, "onDOMContentLoaded").api(),
       onCompleted: new WebNavigationEventManager(context, "onCompleted").api(),
       onErrorOccurred: new WebNavigationEventManager(context, "onErrorOccurred").api(),
       onReferenceFragmentUpdated: new WebNavigationEventManager(context, "onReferenceFragmentUpdated").api(),
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -94,17 +94,17 @@ function WebRequestEventManager(context,
     };
   };
 
   return SingletonEventManager.call(this, context, name, register);
 }
 
 WebRequestEventManager.prototype = Object.create(SingletonEventManager.prototype);
 
-extensions.registerSchemaAPI("webRequest", (extension, context) => {
+extensions.registerSchemaAPI("webRequest", context => {
   return {
     webRequest: {
       onBeforeRequest: new WebRequestEventManager(context, "onBeforeRequest").api(),
       onBeforeSendHeaders: new WebRequestEventManager(context, "onBeforeSendHeaders").api(),
       onSendHeaders: new WebRequestEventManager(context, "onSendHeaders").api(),
       onHeadersReceived: new WebRequestEventManager(context, "onHeadersReceived").api(),
       onBeforeRedirect: new WebRequestEventManager(context, "onBeforeRedirect").api(),
       onResponseStarted: new WebRequestEventManager(context, "onResponseStarted").api(),
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_context.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_context.html
@@ -44,17 +44,17 @@ add_task(function* test_contentscript_co
 
   let win = window.open("http://example.com/");
   yield extension.awaitMessage("content-script-ready");
   yield extension.awaitMessage("content-script-show");
 
   // Get the content script context and check that it points to the correct window.
 
   let {DocumentManager} = SpecialPowers.Cu.import("resource://gre/modules/ExtensionContent.jsm", {});
-  let context = DocumentManager.getContentScriptContext(extension.id, win);
+  let context = DocumentManager.getContentScriptContext(extension, win);
   ok(context != null, "Got content script context");
 
   is(SpecialPowers.unwrap(context.contentWindow), win, "Context's contentWindow property is correct");
 
   // Navigate so that the content page is hidden in the bfcache.
 
   win.location = "http://example.org/";
   yield extension.awaitMessage("content-script-hide");
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
@@ -8,26 +8,23 @@ Cu.import("resource://gre/modules/Extens
 var {
   BaseContext,
   EventManager,
   SingletonEventManager,
 } = ExtensionUtils;
 
 class StubContext extends BaseContext {
   constructor() {
-    super();
+    let fakeExtension = {id: "test@web.extension"};
+    super("test_env", fakeExtension);
     this.sandbox = Cu.Sandbox(global);
   }
 
   get cloneScope() {
-    return this. sandbox;
-  }
-
-  get extension() {
-    return {id: "test@web.extension"};
+    return this.sandbox;
   }
 }
 
 
 add_task(function* test_post_unload_promises() {
   let context = new StubContext();
 
   let fail = result => {
@@ -125,23 +122,23 @@ add_task(function* test_post_unload_list
   // The `setTimeout` ensures that we return to the event loop after
   // promise resolution, which means we're guaranteed to return after
   // any micro-tasks that get enqueued by the resolution handlers above.
   yield new Promise(resolve => setTimeout(resolve, 0));
 });
 
 class Context extends BaseContext {
   constructor(principal) {
-    super();
+    let fakeExtension = {id: "test@web.extension"};
+    super("test_env", fakeExtension);
     Object.defineProperty(this, "principal", {
       value: principal,
       configurable: true,
     });
     this.sandbox = Cu.Sandbox(principal, {wantXrays: false});
-    this.extension = {id: "test@web.extension"};
   }
 
   get cloneScope() {
     return this.sandbox;
   }
 }
 
 let ssm = Services.scriptSecurityManager;