Bug 1190680: Part 1 - Factor common extension context logic into a shared base class. r?billm draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 22 Jan 2016 17:49:17 -0800
changeset 324493 4b051e065f262155a8dec79c819b40c872a78b19
parent 324492 be005c69fbb61cfb10e544c0a6a0b9d5e6193a81
child 324494 76aa11d2c8e89cc99674357428214fd749601d4f
push id9927
push usermaglione.k@gmail.com
push dateSat, 23 Jan 2016 05:20:35 +0000
reviewersbillm
bugs1190680
milestone46.0a1
Bug 1190680: Part 1 - 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
@@ -74,16 +74,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;
@@ -209,97 +210,69 @@ var globalBroker = new MessageBroker([Se
 // 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();
+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 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;
-  },
-
-  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() {
     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
@@ -28,16 +28,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   runSafeSyncWithoutClone,
+  BaseContext,
   LocaleData,
   MessageBroker,
   Messenger,
   injectAPI,
   flushJarCache,
 } = ExtensionUtils;
 
 function isWhenBeforeOrSame(when1, when2) {
@@ -211,97 +212,90 @@ 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) {
-  this.extension = ExtensionManager.get(extensionId);
-  this.extensionId = extensionId;
-  this.contentWindow = contentWindow;
+class ExtensionContext extends BaseContext {
+  constructor(extensionId, contentWindow) {
+    super();
+
+    this.extension = ExtensionManager.get(extensionId);
+    this.extensionId = extensionId;
+    this.contentWindow = contentWindow;
 
-  this.onClose = new Set();
+    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;
 
-  let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindowUtils);
-  let outerWindowId = utils.outerWindowID;
-  let frameId = contentWindow == contentWindow.top ? 0 : outerWindowId;
-  this.frameId = frameId;
+    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 mm = getWindowMessageManager(contentWindow);
-  this.messageManager = mm;
+      Object.defineProperty(this, "principal",
+                            {value: extensionPrincipal, enumerable: true, configurable: true});
+    }
+
+    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);
+
+    let 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 = Cu.waiveXrays(this.sandbox).browser;
+    injectAPI(api(this), chromeObj);
   }
 
-  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);
-
-  let 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 = Cu.waiveXrays(this.sandbox).browser;
-  injectAPI(api(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();
     Cu.nukeSandbox(this.sandbox);
-  },
-};
+  }
+}
 
 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,