Bug 1190680: Part 1 - [webext] Factor common extension context logic into a shared base class. r=billm
authorKris Maglione <maglione.k@gmail.com>
Fri, 29 Jan 2016 18:39:29 -0800
changeset 282373 0573e9dfa2b8fbe1c01426768b1f0edfa2f1153d
parent 282372 843c860178d36432b2d021e433ce4a2c07097347
child 282374 32faeeebe2e0b4a1c667b16781ac4d0b6cde62e5
push id29957
push userphilringnalda@gmail.com
push dateSat, 30 Jan 2016 17:15:45 +0000
treeherdermozilla-central@8be8932a2e0f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1190680
milestone47.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 1190680: Part 1 - [webext] Factor common extension context logic into a shared base class. r=billm
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionContent.jsm
toolkit/components/extensions/ExtensionUtils.jsm
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -76,16 +76,17 @@ ExtensionManagement.registerSchema("chro
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/i18n.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/idle.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/runtime.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_navigation.json");
 ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_request.json");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
+  BaseContext,
   LocaleData,
   MessageBroker,
   Messenger,
   injectAPI,
   instanceOf,
   extend,
   flushJarCache,
 } = ExtensionUtils;
@@ -213,98 +214,72 @@ var gContextId = 0;
 // content loaded into browser tabs (type="tab").
 //
 // |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).
-ExtensionPage = function(extension, params) {
-  let {type, contentWindow, uri} = params;
-  this.extension = extension;
-  this.type = type;
-  this.contentWindow = contentWindow || null;
-  this.uri = uri || extension.baseURI;
-  this.incognito = params.incognito || false;
-  this.onClose = new Set();
-  this.contextId = gContextId++;
-  this.unloaded = false;
+ExtensionPage = class extends BaseContext {
+  constructor(extension, params) {
+    super();
+
+    let {type, contentWindow, uri} = params;
+    this.extension = extension;
+    this.type = type;
+    this.contentWindow = contentWindow || null;
+    this.uri = uri || extension.baseURI;
+    this.incognito = params.incognito || false;
+    this.contextId = gContextId++;
+    this.unloaded = false;
 
-  // This is the MessageSender property passed to extension.
-  // It can be augmented by the "page-open" hook.
-  let sender = {id: extension.uuid};
-  if (uri) {
-    sender.url = uri.spec;
+    // This is the MessageSender property passed to extension.
+    // It can be augmented by the "page-open" hook.
+    let sender = {id: extension.uuid};
+    if (uri) {
+      sender.url = uri.spec;
+    }
+    let delegate = {
+      getSender() {},
+    };
+    Management.emit("page-load", this, params, sender, delegate);
+
+    // Properties in |filter| must match those in the |recipient|
+    // parameter of sendMessage.
+    let filter = {extensionId: extension.id};
+    this.messenger = new Messenger(this, globalBroker, sender, filter, delegate);
+
+    this.extension.views.add(this);
   }
-  let delegate = {
-    getSender() {},
-  };
-  Management.emit("page-load", this, params, sender, delegate);
 
-  // Properties in |filter| must match those in the |recipient|
-  // parameter of sendMessage.
-  let filter = {extensionId: extension.id};
-  this.messenger = new Messenger(this, globalBroker, sender, filter, delegate);
-
-  this.extension.views.add(this);
-};
-
-ExtensionPage.prototype = {
   get cloneScope() {
     return this.contentWindow;
-  },
+  }
 
   get principal() {
     return this.contentWindow.document.nodePrincipal;
-  },
-
-  checkLoadURL(url, options = {}) {
-    let ssm = Services.scriptSecurityManager;
-
-    let flags = ssm.STANDARD;
-    if (!options.allowScript) {
-      flags |= ssm.DISALLOW_SCRIPT;
-    }
-    if (!options.allowInheritsPrincipal) {
-      flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
-    }
-
-    try {
-      ssm.checkLoadURIStrWithPrincipal(this.principal, url, flags);
-    } catch (e) {
-      return false;
-    }
-    return true;
-  },
+  }
 
   // A wrapper around MessageChannel.sendMessage which adds the extension ID
   // to the recipient object, and ensures replies are not processed after the
   // context has been unloaded.
   sendMessage(target, messageName, data, recipient = {}, sender = {}) {
     recipient.extensionId = this.extension.id;
     sender.extensionId = this.extension.id;
     sender.contextId = this.contextId;
 
     return MessageChannel.sendMessage(target, messageName, data, recipient, sender);
-  },
-
-  callOnClose(obj) {
-    this.onClose.add(obj);
-  },
-
-  forgetOnClose(obj) {
-    this.onClose.delete(obj);
-  },
+  }
 
   // Called when the extension shuts down.
   shutdown() {
     Management.emit("page-shutdown", this);
     this.unload();
-  },
+  }
 
   // This method is called when an extension page navigates away or
   // its tab is closed.
   unload() {
     // Note that without this guard, we end up running unload code
     // multiple times for tab pages closed by the "page-unload" handlers
     // triggered below.
     if (this.unloaded) {
@@ -317,20 +292,18 @@ ExtensionPage.prototype = {
       extensionId: this.extension.id,
       contextId: this.contextId,
     });
 
     Management.emit("page-unload", this);
 
     this.extension.views.delete(this);
 
-    for (let obj of this.onClose) {
-      obj.close();
-    }
-  },
+    super.unload();
+  }
 };
 
 // Responsible for loading extension APIs into the right globals.
 GlobalManager = {
   // Number of extensions currently enabled.
   count: 0,
 
   // Map[docShell -> {extension, context}] where context is an ExtensionPage.
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -32,16 +32,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   runSafeSyncWithoutClone,
+  BaseContext,
   LocaleData,
   MessageBroker,
   Messenger,
   injectAPI,
   flushJarCache,
 } = ExtensionUtils;
 
 function isWhenBeforeOrSame(when1, when2) {
@@ -222,132 +223,125 @@ function getWindowMessageManager(content
   }
 }
 
 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.
-function ExtensionContext(extensionId, contentWindow, contextOptions = {}) {
-  let { isExtensionPage } = contextOptions;
+class ExtensionContext extends BaseContext {
+  constructor(extensionId, contentWindow, contextOptions = {}) {
+    super();
+
+    let { isExtensionPage } = contextOptions;
+
+    this.isExtensionPage = isExtensionPage;
+    this.extension = ExtensionManager.get(extensionId);
+    this.extensionId = extensionId;
+    this.contentWindow = contentWindow;
+
+    let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsIDOMWindowUtils);
+    let outerWindowId = utils.outerWindowID;
+    let frameId = contentWindow == contentWindow.top ? 0 : outerWindowId;
+    this.frameId = frameId;
 
-  this.isExtensionPage = isExtensionPage;
-  this.extension = ExtensionManager.get(extensionId);
-  this.extensionId = extensionId;
-  this.contentWindow = contentWindow;
+    let mm = getWindowMessageManager(contentWindow);
+    this.messageManager = mm;
+
+    let prin;
+    let contentPrincipal = contentWindow.document.nodePrincipal;
+    let ssm = Services.scriptSecurityManager;
 
-  this.onClose = new Set();
+    let extensionPrincipal = ssm.createCodebasePrincipal(this.extension.baseURI, {addonId: extensionId});
+    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.
+      prin = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal);
+    } else {
+      prin = [contentPrincipal, extensionPrincipal];
+    }
 
-  let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindowUtils);
-  let outerWindowId = utils.outerWindowID;
-  let frameId = contentWindow == contentWindow.top ? 0 : outerWindowId;
-  this.frameId = frameId;
-
-  let mm = getWindowMessageManager(contentWindow);
-  this.messageManager = mm;
+    if (isExtensionPage) {
+      if (ExtensionManagement.getAddonIdForWindow(this.contentWindow) != 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,
+        wantXrays: false,
+        isWebExtensionContentScript: true,
+      });
+    } else {
+      this.sandbox = Cu.Sandbox(prin, {
+        sandboxPrototype: contentWindow,
+        wantXrays: true,
+        isWebExtensionContentScript: true,
+        wantGlobalProperties: ["XMLHttpRequest"],
+      });
+    }
 
-  let prin;
-  let contentPrincipal = contentWindow.document.nodePrincipal;
-  let ssm = Services.scriptSecurityManager;
-  if (ssm.isSystemPrincipal(contentPrincipal)) {
-    // Make sure we don't hand out the system principal by accident.
-    prin = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal);
-  } else {
-    let extensionPrincipal = ssm.createCodebasePrincipal(this.extension.baseURI, {addonId: extensionId});
-    prin = [contentPrincipal, extensionPrincipal];
+    let delegate = {
+      getSender(context, target, sender) {
+        // Nothing to do here.
+      },
+    };
+
+    let url = contentWindow.location.href;
+    let broker = ExtensionContent.getBroker(mm);
+    // 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};
+    this.messenger = new Messenger(this, broker, 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;
+
+    injectAPI(api(this), this.chromeObj);
+
+    // This is an iframe with content script API enabled. (See Bug 1214658 for rationale)
+    if (isExtensionPage) {
+      Cu.waiveXrays(this.contentWindow).chrome = this.chromeObj;
+      Cu.waiveXrays(this.contentWindow).browser = this.chromeObj;
+    }
   }
 
-  if (isExtensionPage) {
-    if (ExtensionManagement.getAddonIdForWindow(this.contentWindow) != 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,
-      wantXrays: false,
-      isWebExtensionContentScript: true,
-    });
-  } else {
-    this.sandbox = Cu.Sandbox(prin, {
-      sandboxPrototype: contentWindow,
-      wantXrays: true,
-      isWebExtensionContentScript: true,
-      wantGlobalProperties: ["XMLHttpRequest"],
-    });
-  }
-
-  let delegate = {
-    getSender(context, target, sender) {
-      // Nothing to do here.
-    },
-  };
-
-  let url = contentWindow.location.href;
-  let broker = ExtensionContent.getBroker(mm);
-  // 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};
-  this.messenger = new Messenger(this, broker, 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;
-
-  injectAPI(api(this), this.chromeObj);
-
-  // This is an iframe with content script API enabled. (See Bug 1214658 for rationale)
-  if (isExtensionPage) {
-    Cu.waiveXrays(this.contentWindow).chrome = this.chromeObj;
-    Cu.waiveXrays(this.contentWindow).browser = this.chromeObj;
-  }
-}
-
-ExtensionContext.prototype = {
   get cloneScope() {
     return this.sandbox;
-  },
+  }
 
   execute(script, shouldRun) {
     script.tryInject(this.extension, this.contentWindow, this.sandbox, shouldRun);
-  },
-
-  callOnClose(obj) {
-    this.onClose.add(obj);
-  },
-
-  forgetOnClose(obj) {
-    this.onClose.delete(obj);
-  },
+  }
 
   close() {
-    for (let obj of this.onClose) {
-      obj.close();
-    }
+    super.unload();
 
     // Overwrite the content script APIs with an empty object if the APIs objects are still
     // defined in the content window (See Bug 1214658 for rationale).
     if (this.isExtensionPage && !Cu.isDeadWrapper(this.contentWindow) &&
         Cu.waiveXrays(this.contentWindow).browser === this.chromeObj) {
       Cu.createObjectIn(this.contentWindow, { defineAs: "browser" });
       Cu.createObjectIn(this.contentWindow, { defineAs: "chrome" });
     }
-
     Cu.nukeSandbox(this.sandbox);
     this.sandbox = null;
-  },
-};
+  }
+}
 
 function windowId(window) {
   return window.QueryInterface(Ci.nsIInterfaceRequestor)
                .getInterface(Ci.nsIDOMWindowUtils)
                .currentInnerWindowID;
 }
 
 // Responsible for creating ExtensionContexts and injecting content
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -107,16 +107,63 @@ DefaultWeakMap.prototype = {
     if (key) {
       this.weakmap.set(key, value);
     } else {
       this.defaultValue = value;
     }
   },
 };
 
+class BaseContext {
+  constructor() {
+    this.onClose = new Set();
+  }
+
+  get cloneScope() {
+    throw new Error("Not implemented");
+  }
+
+  get principal() {
+    throw new Error("Not implemented");
+  }
+
+  checkLoadURL(url, options = {}) {
+    let ssm = Services.scriptSecurityManager;
+
+    let flags = ssm.STANDARD;
+    if (!options.allowScript) {
+      flags |= ssm.DISALLOW_SCRIPT;
+    }
+    if (!options.allowInheritsPrincipal) {
+      flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
+    }
+
+    try {
+      ssm.checkLoadURIStrWithPrincipal(this.principal, url, flags);
+    } catch (e) {
+      return false;
+    }
+    return true;
+  }
+
+  callOnClose(obj) {
+    this.onClose.add(obj);
+  }
+
+  forgetOnClose(obj) {
+    this.onClose.delete(obj);
+  }
+
+  unload() {
+    for (let obj of this.onClose) {
+      obj.close();
+    }
+  }
+}
+
 function LocaleData(data) {
   this.defaultLocale = data.defaultLocale;
   this.selectedLocale = data.selectedLocale;
   this.locales = data.locales || new Map();
 
   // Map(locale-name -> Map(message-key -> localized-string))
   //
   // Contains a key for each loaded locale, each of which is a
@@ -781,16 +828,17 @@ function flushJarCache(jarFile) {
   Services.obs.notifyObservers(jarFile, "flush-cache-entry", null);
 }
 
 this.ExtensionUtils = {
   runSafeWithoutClone,
   runSafeSyncWithoutClone,
   runSafe,
   runSafeSync,
+  BaseContext,
   DefaultWeakMap,
   EventManager,
   LocaleData,
   SingletonEventManager,
   ignoreEvent,
   injectAPI,
   MessageBroker,
   Messenger,