Bug 1190995 - Support the new extension model in b2g r=billm
authorFabrice Desré <fabrice@mozilla.com>
Fri, 14 Aug 2015 16:55:09 -0700
changeset 257905 0450f02a2b3bfbbada04bc9b1549a4cfb626a127
parent 257903 2ddfc9180971a212127a370911ba2e8ef36a6605
child 257906 2a5669be7ee3caff9fdfbd2320bdd0d7abc11d1e
push id29236
push userryanvm@gmail.com
push dateMon, 17 Aug 2015 12:50:46 +0000
treeherdermozilla-central@028fa40d0ab8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1190995
milestone43.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 1190995 - Support the new extension model in b2g r=billm
b2g/app/b2g.js
b2g/components/ProcessGlobal.js
b2g/installer/package-manifest.in
dom/apps/UserCustomizations.jsm
dom/apps/Webapps.jsm
dom/apps/tests/addons/application.zip
dom/apps/tests/addons/index.html
dom/apps/tests/addons/manifest.json
dom/apps/tests/addons/manifest.webapp
dom/apps/tests/addons/script.js
dom/apps/tests/addons/script2.js
dom/browser-element/BrowserElementChildPreload.js
dom/browser-element/BrowserElementParent.js
dom/ipc/extensions.js
dom/ipc/jar.mn
dom/ipc/preload.js
netwerk/protocol/res/SubstitutingProtocolHandler.cpp
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionContent.jsm
toolkit/components/extensions/ExtensionManagement.jsm
toolkit/components/extensions/ExtensionStorage.jsm
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/ext-alarms.js
toolkit/components/extensions/ext-notifications.js
toolkit/components/extensions/ext-runtime.js
toolkit/components/extensions/ext-storage.js
toolkit/components/extensions/ext-webNavigation.js
toolkit/components/extensions/ext-webRequest.js
toolkit/modules/addons/MatchPattern.jsm
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1169,9 +1169,8 @@ pref("dom.audiochannel.mutedByDefault", 
 // requests.
 pref("dom.bluetooth.app-origin", "app://bluetooth.gaiamobile.org");
 
 // Default device name for Presentation API
 pref("dom.presentation.device.name", "Firefox OS");
 
 // Enable notification of performance timing
 pref("dom.performance.enable_notify_performance_timing", true);
-
--- a/b2g/components/ProcessGlobal.js
+++ b/b2g/components/ProcessGlobal.js
@@ -160,19 +160,17 @@ ProcessGlobal.prototype = {
   observe: function pg_observe(subject, topic, data) {
     switch (topic) {
     case 'app-startup': {
       Services.obs.addObserver(this, 'console-api-log-event', false);
       let inParent = Cc["@mozilla.org/xre/app-info;1"]
                        .getService(Ci.nsIXULRuntime)
                        .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
       if (inParent) {
-        let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
-                     .getService(Ci.nsIMessageListenerManager);
-        ppmm.addMessageListener("getProfD", function(message) {
+        Services.ppmm.addMessageListener("getProfD", function(message) {
           return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
         });
 
         this.cleanupAfterFactoryReset();
       }
       break;
     }
     case 'console-api-log-event': {
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -1001,8 +1001,11 @@ bin/libfreebl_32int64_3.so
 #ifdef MOZ_EME
 @RESPATH@/gmp-clearkey/0.1/@DLL_PREFIX@clearkey@DLL_SUFFIX@
 @RESPATH@/gmp-clearkey/0.1/clearkey.info
 #endif
 
 #ifdef PKG_LOCALE_MANIFEST
 #include @PKG_LOCALE_MANIFEST@
 #endif
+
+@RESPATH@/components/simpleServices.js
+@RESPATH@/components/utils.manifest
--- a/dom/apps/UserCustomizations.jsm
+++ b/dom/apps/UserCustomizations.jsm
@@ -7,369 +7,69 @@
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 this.EXPORTED_SYMBOLS = ["UserCustomizations"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/AppsUtils.jsm");
+Cu.import("resource://gre/modules/Extension.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
-                                   "@mozilla.org/parentprocessmessagemanager;1",
-                                   "nsIMessageBroadcaster");
-
-XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
-                                   "@mozilla.org/childprocessmessagemanager;1",
-                                   "nsIMessageSender");
 
 XPCOMUtils.defineLazyServiceGetter(this, "console",
                                    "@mozilla.org/consoleservice;1",
                                    "nsIConsoleService");
-/**
-  * Customization scripts and CSS stylesheets can be specified in an
-  * application manifest with the following syntax:
-  * "customizations": [
-  *  {
-  *    "filter": "http://youtube.com",
-  *    "css": ["file1.css", "file2.css"],
-  *    "scripts": ["script1.js", "script2.js"]
-  *  }
-  * ]
-  */
 
 function debug(aMsg) {
   if (!UserCustomizations._debug) {
     return;
   }
-  dump("-*-*- UserCustomizations (" +
-           (UserCustomizations._inParent ? "parent" : "child") +
-           "): " + aMsg + "\n");
+  dump("-*-*- UserCustomizations " + aMsg + "\n");
 }
 
 function log(aStr) {
   console.logStringMessage(aStr);
 }
 
 this.UserCustomizations = {
-  _debug: false,
-  _items: [],
-  _loaded : {},   // Keep track per manifestURL of css and scripts loaded.
-  _windows: null, // Set of currently opened windows.
-  _enabled: false,
-
-  _addItem: function(aItem) {
-    debug("_addItem: " + uneval(aItem));
-    this._items.push(aItem);
-    if (this._inParent) {
-      ppmm.broadcastAsyncMessage("UserCustomizations:Add", [aItem]);
-    }
-  },
+  extensions: new Map(), // id -> extension. Needed to disable extensions.
 
-  _removeItem: function(aHash) {
-    debug("_removeItem: " + aHash);
-    let index = -1;
-    this._items.forEach((script, pos) => {
-      if (script.hash == aHash ) {
-        index = pos;
-      }
-    });
-
-    if (index != -1) {
-      this._items.splice(index, 1);
-    }
-
-    if (this._inParent) {
-      ppmm.broadcastAsyncMessage("UserCustomizations:Remove", aHash);
-    }
-  },
-
-  register: function(aManifest, aApp) {
-    debug("Starting customization registration for " + aApp.manifestURL);
-
+  register: function(aApp) {
     if (!this._enabled || !aApp.enabled || aApp.role != "addon") {
       debug("Rejecting registration (global enabled=" + this._enabled +
             ") (app role=" + aApp.role +
             ", enabled=" + aApp.enabled + ")");
-      debug(uneval(aApp));
-      return;
-    }
-
-    let customizations = aManifest.customizations;
-    if (customizations === undefined || !Array.isArray(customizations)) {
       return;
     }
 
-    let base = Services.io.newURI(aApp.origin, null, null);
-
-    customizations.forEach(item => {
-      // The filter property is mandatory.
-      if (!item.filter || (typeof item.filter !== "string")) {
-        log("Mandatory filter property not found in this customization item: " +
-            uneval(item) + " in " + aApp.manifestURL);
-        return;
-      }
+    debug("Starting customization registration for " + aApp.manifestURL + "\n");
 
-      // Create a new object with resolved urls and a hash that we reuse to
-      // remove items.
-      let custom = {
-        filter: item.filter,
-        status: aApp.appStatus,
-        manifestURL: aApp.manifestURL,
-        css: [],
-        scripts: []
-      };
-      custom.hash = AppsUtils.computeObjectHash(item);
+    let extension = new Extension({
+      id: aApp.manifestURL,
+      resourceURI: Services.io.newURI(aApp.origin + "/", null, null)
+    });
 
-      if (item.css && Array.isArray(item.css)) {
-        item.css.forEach((css) => {
-          custom.css.push(base.resolve(css));
-        });
-      }
-
-      if (item.scripts && Array.isArray(item.scripts)) {
-        item.scripts.forEach((script) => {
-          custom.scripts.push(base.resolve(script));
-        });
-      }
-
-      this._addItem(custom);
-    });
-    this._updateAllWindows();
+    this.extensions.set(aApp.manifestURL, extension);
+    extension.startup();
   },
 
-  _updateAllWindows: function() {
-    debug("UpdateWindows");
-    if (this._inParent) {
-      ppmm.broadcastAsyncMessage("UserCustomizations:UpdateWindows", {});
-    }
-    // Inject in all currently opened windows.
-    this._windows.forEach(this._injectInWindow.bind(this));
-  },
-
-  unregister: function(aManifest, aApp) {
+  unregister: function(aApp) {
     if (!this._enabled) {
       return;
     }
 
     debug("Starting customization unregistration for " + aApp.manifestURL);
-    let customizations = aManifest.customizations;
-    if (customizations === undefined || !Array.isArray(customizations)) {
-      return;
-    }
-
-    customizations.forEach(item => {
-      this._removeItem(AppsUtils.computeObjectHash(item));
-    });
-    this._unloadForManifestURL(aApp.manifestURL);
-  },
-
-  _unloadForManifestURL: function(aManifestURL) {
-    debug("_unloadForManifestURL " + aManifestURL);
-
-    if (this._inParent) {
-      ppmm.broadcastAsyncMessage("UserCustomizations:Unload", aManifestURL);
-    }
-
-    if (!this._loaded[aManifestURL]) {
-      return;
-    }
-
-    if (this._loaded[aManifestURL].scripts &&
-        this._loaded[aManifestURL].scripts.length > 0) {
-      // We can't rollback script changes, so don't even try to unload in this
-      // situation.
-      return;
-    }
-
-    this._loaded[aManifestURL].css.forEach(aItem => {
-      try {
-        debug("unloading " + aItem.uri.spec);
-        let utils = aItem.window.QueryInterface(Ci.nsIInterfaceRequestor)
-                                .getInterface(Ci.nsIDOMWindowUtils);
-        utils.removeSheet(aItem.uri, Ci.nsIDOMWindowUtils.AUTHOR_SHEET);
-      } catch(e) {
-        log("Error unloading stylesheet " + aItem.uri.spec + " : " + e);
-      }
-    });
-
-    this._loaded[aManifestURL] = null;
-  },
-
-  _injectItem: function(aWindow, aItem, aInjected) {
-    debug("Injecting item " + uneval(aItem) + " in " + aWindow.location.href);
-    let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDOMWindowUtils);
-
-    let manifestURL = aItem.manifestURL;
-
-    // Load the stylesheets only in this window.
-    aItem.css.forEach(aCss => {
-      if (aInjected.indexOf(aCss) !== -1) {
-        debug("Skipping duplicated css: " + aCss);
-        return;
-      }
-
-      let uri = Services.io.newURI(aCss, null, null);
-      try {
-        utils.loadSheet(uri, Ci.nsIDOMWindowUtils.AUTHOR_SHEET);
-        if (!this._loaded[manifestURL]) {
-          this._loaded[manifestURL] = { css: [], scripts: [] };
-        }
-        this._loaded[manifestURL].css.push({ window: aWindow, uri: uri });
-        aInjected.push(aCss);
-      } catch(e) {
-        log("Error loading stylesheet " + aCss + " : " + e);
-      }
-    });
-
-    let sandbox;
-    if (aItem.scripts.length > 0) {
-      sandbox = Cu.Sandbox([aWindow],
-                           { wantComponents: false,
-                             sandboxPrototype: aWindow });
-    }
-
-    // Load the scripts using a sandbox.
-    aItem.scripts.forEach(aScript => {
-      debug("Sandboxing " + aScript);
-      if (aInjected.indexOf(aScript) !== -1) {
-        debug("Skipping duplicated script: " + aScript);
-        return;
-      }
-
-      try {
-        let options = {
-          target: sandbox,
-          charset: "UTF-8",
-          async: true
-        }
-        Services.scriptloader.loadSubScriptWithOptions(aScript, options);
-        if (!this._loaded[manifestURL]) {
-          this._loaded[manifestURL] = { css: [], scripts: [] };
-        }
-        this._loaded[manifestURL].scripts.push({ sandbox: sandbox, uri: aScript });
-        aInjected.push(aScript);
-      } catch(e) {
-        log("Error sandboxing " + aScript + " : " + e);
-      }
-    });
-
-    // Makes sure we get rid of the sandbox.
-    if (sandbox) {
-      aWindow.addEventListener("unload", () => {
-        Cu.nukeSandbox(sandbox);
-        sandbox = null;
-      });
-    }
-  },
-
-  _injectInWindow: function(aWindow) {
-    debug("_injectInWindow");
-
-    if (!aWindow || !aWindow.document) {
-      return;
-    }
-
-    let principal = aWindow.document.nodePrincipal;
-    debug("principal status: " + principal.appStatus);
-
-    let href = aWindow.location.href;
-
-    // The list of resources loaded in this window, used to filter out
-    // duplicates.
-    let injected = [];
-
-    this._items.forEach((aItem) => {
-      // We only allow customizations to apply to apps with an equal or lower
-      // privilege level.
-      if (principal.appStatus > aItem.status) {
-        return;
-      }
-
-      let regexp = new RegExp(aItem.filter, "g");
-      if (regexp.test(href)) {
-        this._injectItem(aWindow, aItem, injected);
-        debug("Currently injected: " + injected.toString());
-      }
-    });
-  },
-
-  observe: function(aSubject, aTopic, aData) {
-    if (aTopic === "content-document-global-created") {
-      let window = aSubject.QueryInterface(Ci.nsIDOMWindow);
-      let href = window.location.href;
-      if (!href || href == "about:blank") {
-        return;
-      }
-
-      let id = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                     .getInterface(Ci.nsIDOMWindowUtils)
-                     .currentInnerWindowID;
-      this._windows.set(id, window);
-
-      debug("document created: " + href);
-      this._injectInWindow(window);
-    } else if (aTopic === "inner-window-destroyed") {
-      let winId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
-      this._windows.delete(winId);
+    if (this.extensions.has(aApp.manifestURL)) {
+      this.extensions.get(aApp.manifestURL).shutdown();
+      this.extensions.delete(aApp.manifestURL);
     }
   },
 
   init: function() {
     this._enabled = false;
     try {
       this._enabled = Services.prefs.getBoolPref("dom.apps.customization.enabled");
     } catch(e) {}
-
-    if (!this._enabled) {
-      return;
-    }
-
-    this._windows = new Map(); // Can't be a WeakMap because we need to enumerate.
-    this._inParent = Cc["@mozilla.org/xre/runtime;1"]
-                       .getService(Ci.nsIXULRuntime)
-                       .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-
-    debug("init");
-
-    Services.obs.addObserver(this, "content-document-global-created",
-                             /* ownsWeak */ false);
-    Services.obs.addObserver(this, "inner-window-destroyed",
-                             /* ownsWeak */ false);
-
-    if (this._inParent) {
-      ppmm.addMessageListener("UserCustomizations:List", this);
-    } else {
-      cpmm.addMessageListener("UserCustomizations:Add", this);
-      cpmm.addMessageListener("UserCustomizations:Remove", this);
-      cpmm.addMessageListener("UserCustomizations:Unload", this);
-      cpmm.addMessageListener("UserCustomizations:UpdateWindows", this);
-      cpmm.sendAsyncMessage("UserCustomizations:List", {});
-    }
   },
-
-  receiveMessage: function(aMessage) {
-    let name = aMessage.name;
-    let data = aMessage.data;
-
-    switch(name) {
-      case "UserCustomizations:List":
-        aMessage.target.sendAsyncMessage("UserCustomizations:Add", this._items);
-        break;
-      case "UserCustomizations:Add":
-        data.forEach(this._addItem, this);
-        break;
-      case "UserCustomizations:Remove":
-        this._removeItem(data);
-        break;
-      case "UserCustomizations:Unload":
-        this._unloadForManifestURL(data);
-        break;
-      case "UserCustomizations:UpdateWindows":
-        this._updateAllWindows();
-        break;
-    }
-  }
 }
 
 UserCustomizations.init();
--- a/dom/apps/Webapps.jsm
+++ b/dom/apps/Webapps.jsm
@@ -434,17 +434,17 @@ this.DOMApplicationRegistry = {
 
         let localeManifest = new ManifestHelper(aResult.manifest, app.origin, app.manifestURL);
         this._saveWidgetsFullPath(localeManifest, app);
 
         if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
           app.redirects = this.sanitizeRedirects(aResult.redirects);
         }
         app.kind = this.appKind(app, aResult.manifest);
-        UserCustomizations.register(aResult.manifest, app);
+        UserCustomizations.register(app);
         Langpacks.register(app, aResult.manifest);
       });
 
       // Nothing else to do but notifying we're ready.
       this.notifyAppsRegistryReady();
     }
   }),
 
@@ -1159,17 +1159,17 @@ this.DOMApplicationRegistry = {
 
         if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
           app.redirects = this.sanitizeRedirects(manifest.redirects);
         }
         app.kind = this.appKind(app, aResult.manifest);
         this._registerSystemMessages(manifest, app);
         this._registerInterAppConnections(manifest, app);
         appsToRegister.push({ manifest: manifest, app: app });
-        UserCustomizations.register(manifest, app);
+        UserCustomizations.register(app);
         Langpacks.register(app, manifest);
       });
       this._safeToClone.resolve();
       this._registerActivitiesForApps(appsToRegister, aRunUpdate);
     });
   },
 
   observe: function(aSubject, aTopic, aData) {
@@ -2008,20 +2008,20 @@ this.DOMApplicationRegistry = {
       this._registerInterAppConnections(aNewManifest, aApp);
     } else {
       // Nothing else to do but notifying we're ready.
       this.notifyAppsRegistryReady();
     }
 
     // Update user customizations and langpacks.
     if (aOldManifest) {
-      UserCustomizations.unregister(aOldManifest, aApp);
+      UserCustomizations.unregister(aApp);
       Langpacks.unregister(aApp, aOldManifest);
     }
-    UserCustomizations.register(aNewManifest, aApp);
+    UserCustomizations.register(aApp);
     Langpacks.register(aApp, aNewManifest);
   },
 
   checkForUpdate: function(aData, aMm) {
     debug("checkForUpdate for " + aData.manifestURL);
 
     let sendError = (aError) => {
       debug("checkForUpdate error " + aError);
@@ -4120,17 +4120,17 @@ this.DOMApplicationRegistry = {
     this._clearPrivateData(aApp.localId, false);
 
     // Then notify observers.
     Services.obs.notifyObservers(null, "webapps-uninstall", JSON.stringify(aApp));
 
     if (supportSystemMessages()) {
       this._unregisterActivities(aApp.manifest, aApp);
     }
-    UserCustomizations.unregister(aApp.manifest, aApp);
+    UserCustomizations.unregister(aApp);
     Langpacks.unregister(aApp, aApp.manifest);
 
     let dir = this._getAppDir(id);
     try {
       dir.remove(true);
     } catch (e) {}
 
     delete this.webapps[id];
@@ -4540,20 +4540,21 @@ this.DOMApplicationRegistry = {
       MessageBroadcaster.broadcastMessage("Webapps:UpdateState", {
         app: app,
         id: app.id
       });
       MessageBroadcaster.broadcastMessage("Webapps:SetEnabled:Return", app);
     });
 
     // Update customization.
-    this.getManifestFor(app.manifestURL).then((aManifest) => {
-      app.enabled ? UserCustomizations.register(aManifest, app)
-                  : UserCustomizations.unregister(aManifest, app);
-    });
+    if (app.enabled) {
+      UserCustomizations.register(app);
+    } else {
+      UserCustomizations.unregister(app);
+    }
   },
 
   getManifestFor: function(aManifestURL, aEntryPoint) {
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
     if (!id || (app.installState == "pending" && !app.retryingDownload)) {
       return Promise.resolve(null);
     }
index 543587c8869ba38506c5889fa66d28755389274b..2d2ece845300a67ebd38ee5da44e12fa4acd408f
GIT binary patch
literal 1967
zc$^FHW@Zs#U|`^2@atr8Uw`xVcSlAB1`j3%1}+8}hRnQ_)C#?flH8oo5Kac>;*f)(
zAY59(&A`a=f|-E<Ow0{B=y%wFXYYG$AOE>5(yynk6Y5b^RGe|d;n5A#ds3R6tFI)A
zY^hiGj6T>XJpD-Fz0cJfC(plYw{~0pY||@iLJd#;=2cj~QAF{BqIXzCPWDZwU^#)l
zAk7<!8A3(+fd|$xuM_lA)I0Iyv)ujS+ow*ob)UR9vrAPn<6=j4>$Ka}oBCA@OzUPI
zntpt`qWpQ|WBhRw6HRRPehPoTw6#klGNP(h`Gtk`0{0ik47<|bT?x=wVm?hOer2`p
z)Was7dsL>q6iqrb-+!`v{I!`|$_=OFPuw1)Z0YBIfuX@(PH{!it=YVvvRf7RynN?6
ztHo|nc>kyW32Dq<OZcW=EXsLZ^Cr9Fc;HiOA=wFmAzi6L@>6({SBNXr<{1RrOmOAh
zlsWg?m!lk~>KZpKsW6<HJ#)*SFUISBC00~iTg_(pKk?be<2B3y@JKN2W^w<beD(4L
zATJ9T5xl@i$W6@4OiL{;(aS2%&jZH;Tg1Uo5JroK(Efv2%!WKJ?|+MSoj;iOeMa>A
zzs(z1u7^doZ_No)+S2l;`t8+A0`ffhZf88cGai_5KEp4;E=h-FUs&;*PrTuNt-C(w
z-48iE?~?hZn0e23NQz#cv%~w&md%3ykEY#P(;;&$B=!6+m&Tq6FS;BZbU%nF8pv-^
z6%kU|xnd`)ndF0ezf{kx5Z&xyYdPI}#^kBnro5Kl9kD6f{X^fr_@jD(N93)&<4V?k
zI}_Tx<(Qe(A->0coq2VhACJENAb)Y%HJ+cb7s_50%O2}p<}N*_?f3_FM6g8Ev$*qp
z6aME1<i!IqKXR~?rzRy96o3O}h4aBs5Jn3a)gD8>1_d71a=(vn-*Dc!v(v@dI$?=G
zg7Lk(A=kctx+lzFr02!{LS!XdUT@j)Map0Aq^)~q_R1wNWw(j14y&1?RC2WI`tSBh
z@_`kHk(_1R#p14Y&}mmGkk<;toIqz4Cl_TFfZ_`5AjPnQp&*Rzpze)aOojriziW;f
z_uhJ9_h5ryi}0494RMp63Z2O9OA-B3t$%~}%o&3R6Q`LP&sv`unxWF#uVQRIp}|;R
zXIK6Q*AnIQGwoVR!3Q`O{wV*Rm$vuM<<H-x__kHA`(`oAPp`9J?!51}zuo98)~lF0
z>+re0V}BTsQW?<Q`R}WuDuKLS21r(ixZ4Qka8;nARWTiI+T+M|$UwmLdvTYdbm`=#
z+mGA?MM{>6OH5kGX)<||PwTfYW*xiP-|xR)`lBmTN^-S!VbB$aF1-mnF%wxTpB!1V
zyGyS-FNM)A#K54^X5sm~ynB3WuHXN%=g!}ICg-1K@Y$Md32Zt2_HRwj#GPkCi@UUc
z|6l!*@c}o;@mvfb5LxuXtt?}%k|dBV2@CX+%A8ca<YI{1cTJufdQlR??aCRci7BZ?
z3e{W+3d#97`9)R=MX4#)T(ziXzqHZImj<$>VF3d&+lWZRjgU=G$|+3+8_&oj$Be6x
zl>l1|<T7k&1hJ5dTvkYti)IzFd6?w|vU#mQ3a#{Dg_It6jKs`q$VSd#V!+IKtdN|C
z$570if^4WUP&HD%VTI%yeBQ=Lz{tkEh8t%DGY?BrhM0#dO(C1Nf`tJ&g|R{s0RiJs
elM%9UEUXMDi3*o_tZX1X>_BJ+4D@7H5Dx&-9+$fS
--- a/dom/apps/tests/addons/index.html
+++ b/dom/apps/tests/addons/index.html
@@ -5,18 +5,22 @@
     <script>
 function sendAlertsForNode(node) {
   alert(node.textContent);
   var color = window.getComputedStyle(node).getPropertyValue("color");
   alert(color);
 }
 
 function run() {
-  sendAlertsForNode(document.getElementById("header"));
-  sendAlertsForNode(document.getElementById("header2"));
+  // We need to wait for the next tick because add-ons are injected in the
+  // onload event too.
+  window.setTimeout(function() {
+    sendAlertsForNode(document.getElementById("header"));
+    sendAlertsForNode(document.getElementById("header2"));
+  }, 0);
 }
     </script>
   </head>
   <body onload="run()">
     <h1 id="header">Lorem ipsum</h1>
     <h2 id="header2">Uncustomized content</h2>
   </body>
 </html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/addons/manifest.json
@@ -0,0 +1,12 @@
+{
+  "name": "Addon app",
+  "version": "1.0",
+  "manifest_version": 2,
+  "permissions": ["tabs"],
+  "description": "Let me inject script and css!",
+  "content_scripts": [
+    {"matches": ["http://mochi.test/tests/dom/apps/tests/addons/index.html"],
+     "js": ["script.js", "script2.js", "invalid.js", "script.js"],
+     "css": ["style.css", "style2.css"]}
+  ]
+}
--- a/dom/apps/tests/addons/manifest.webapp
+++ b/dom/apps/tests/addons/manifest.webapp
@@ -1,12 +1,5 @@
 {
   "name": "Addon app",
   "description": "Let me inject script and css!",
-  "customizations" : [
-    {
-      "filter": "http://mochi.test:8888/tests/dom/apps/tests/addons",
-      "css": ["style.css", "style2.css", "invalid.css", "style.css"],
-      "scripts": ["script.js", "script2.js", "invalid.js", "script.js"]
-    }
-  ],
   "role": "addon"
 }
--- a/dom/apps/tests/addons/script.js
+++ b/dom/apps/tests/addons/script.js
@@ -1,4 +1,5 @@
-document.addEventListener("DOMContentLoaded", function() {
-  var head = document.getElementById("header");
-  head.innerHTML = "Hello World!";
-}, false);
+// Simple script that changes an element's content.
+
+var head = document.getElementById("header");
+head.innerHTML = "Hello World!";
+
--- a/dom/apps/tests/addons/script2.js
+++ b/dom/apps/tests/addons/script2.js
@@ -1,4 +1,6 @@
-document.addEventListener("DOMContentLoaded", function() {
-  var head = document.getElementById("header2");
-  head.innerHTML = "Customized content";
-}, false);
+// Simple script that changes an element's content.
+
+var head = document.getElementById("header2");
+head.innerHTML = "Customized content";
+
+
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -1727,9 +1727,8 @@ BrowserElementChild.prototype = {
   },
 
   get messageManager() {
     return this._messageManagerPublic;
   }
 };
 
 var api = new BrowserElementChild();
-
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -229,16 +229,18 @@ BrowserElementParent.prototype = {
 
       if (aMsg.data.msg_name in mmCalls) {
         return mmCalls[aMsg.data.msg_name].apply(self, arguments);
       } else if (!isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
         return mmSecuritySensitiveCalls[aMsg.data.msg_name]
                  .apply(self, arguments);
       }
     });
+
+    this._mm.loadFrameScript("chrome://global/content/extensions.js", true);
   },
 
   /**
    * You shouldn't touch this._frameElement or this._window if _isAlive is
    * false.  (You'll likely get an exception if you do.)
    */
   _isAlive: function() {
     return !Cu.isDeadWrapper(this._frameElement) &&
new file mode 100644
--- /dev/null
+++ b/dom/ipc/extensions.js
@@ -0,0 +1,15 @@
+/* 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";
+
+dump("######################## extensions.js loaded\n");
+
+Components.utils.import("resource://gre/modules/ExtensionContent.jsm");
+
+ExtensionContent.init(this);
+
+addEventListener("unload", () => {
+  ExtensionContent.uninit(this);
+});
--- a/dom/ipc/jar.mn
+++ b/dom/ipc/jar.mn
@@ -5,10 +5,11 @@
 toolkit.jar:
         content/global/test-ipc.xul (test.xul)
         content/global/remote-test-ipc.js (remote-test.js)
         content/global/BrowserElementChild.js (../browser-element/BrowserElementChild.js)
         content/global/BrowserElementChildPreload.js (../browser-element/BrowserElementChildPreload.js)
         content/global/BrowserElementCopyPaste.js (../browser-element/BrowserElementCopyPaste.js)
         content/global/BrowserElementPanning.js (../browser-element/BrowserElementPanning.js)
 *       content/global/BrowserElementPanningAPZDisabled.js (../browser-element/BrowserElementPanningAPZDisabled.js)
+        content/global/extensions.js (extensions.js)
         content/global/manifestMessages.js (manifestMessages.js)
         content/global/preload.js (preload.js)
--- a/dom/ipc/preload.js
+++ b/dom/ipc/preload.js
@@ -22,21 +22,16 @@ const BrowserElementIsPreloaded = true;
   Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
   Cu.import("resource://gre/modules/FileUtils.jsm");
   Cu.import("resource://gre/modules/Geometry.jsm");
   Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
   Cu.import("resource://gre/modules/NetUtil.jsm");
   Cu.import("resource://gre/modules/Services.jsm");
   Cu.import("resource://gre/modules/SettingsDB.jsm");
   Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-  try {
-    if (Services.prefs.getBoolPref("dom.apps.customization.enabled")) {
-      Cu.import("resource://gre/modules/UserCustomizations.jsm");
-    }
-  } catch(e) {}
 
   Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci["nsIAppShellService"]);
   Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci["nsIWindowMediator"]);
   Cc["@mozilla.org/AppsService;1"].getService(Ci["nsIAppsService"]);
   Cc["@mozilla.org/base/telemetry;1"].getService(Ci["nsITelemetry"]);
   Cc["@mozilla.org/categorymanager;1"].getService(Ci["nsICategoryManager"]);
   Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci["nsIMessageSender"]);
   Cc["@mozilla.org/consoleservice;1"].getService(Ci["nsIConsoleService"]);
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -303,17 +303,18 @@ SubstitutingProtocolHandler::SetSubstitu
     return NS_OK;
   }
 
   // If baseURI isn't a same-scheme URI, we can set the substitution immediately.
   nsAutoCString scheme;
   nsresult rv = baseURI->GetScheme(scheme);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!scheme.Equals(mScheme)) {
-    if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")) {
+    if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")
+        && !scheme.EqualsLiteral("app")) {
       NS_WARNING("Refusing to create substituting URI to non-file:// target");
       return NS_ERROR_INVALID_ARG;
     }
 
     mSubstitutions.Put(root, baseURI);
     SendSubstitution(root, baseURI);
     return NS_OK;
   }
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -1,15 +1,15 @@
 /* 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 EXPORTED_SYMBOLS = ["Extension"];
+this.EXPORTED_SYMBOLS = ["Extension"];
 
 /*
  * This file is the main entry point for extensions. When an extension
  * loads, its bootstrap.js file creates a Extension instance
  * and calls .startup() on it. It calls .shutdown() when the extension
  * unloads. Extension manages any extension-specific state in
  * the chrome process.
  */
@@ -301,17 +301,17 @@ let GlobalManager = {
       context.unload();
     };
     eventHandler.addEventListener("unload", listener, true);
   },
 };
 
 // We create one instance of this class per extension. |addonData|
 // comes directly from bootstrap.js when initializing.
-function Extension(addonData)
+this.Extension = function(addonData)
 {
   let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
   let uuid = uuidGenerator.generateUUID().number;
   uuid = uuid.substring(1, uuid.length - 1); // Strip of { and } off the UUID.
   this.uuid = uuid;
 
   this.addonData = addonData;
   this.id = addonData.id;
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -1,29 +1,30 @@
 /* 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 EXPORTED_SYMBOLS = ["ExtensionContent"];
+this.EXPORTED_SYMBOLS = ["ExtensionContent"];
 
 /*
  * This file handles the content process side of extensions. It mainly
  * takes care of content script injection, content script APIs, and
  * messaging.
  */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
@@ -142,18 +143,30 @@ Script.prototype = {
         let url = "data:text/css;charset=utf-8," + encodeURIComponent(this.options.cssCode);
         runSafeWithoutClone(winUtils.loadSheetUsingURIString, url, winUtils.AUTHOR_SHEET);
       }
     }
 
     let scheduled = this.run_at || "document_idle";
     if (shouldRun(scheduled)) {
       for (let url of this.js) {
+        // On gonk we need to load the resources asynchronously because the
+        // app: channels only support asyncOpen. This is safe only in the
+        // `document_idle` state.
+        if (AppConstants.platform == "gonk" && scheduled != "document_idle") {
+          Cu.reportError(`Script injection: ignoring ${url} at ${scheduled}`);
+        }
         url = extension.baseURI.resolve(url);
-        Services.scriptloader.loadSubScript(url, sandbox);
+
+        let options = {
+          target: sandbox,
+          charset: "UTF-8",
+          async: AppConstants.platform == "gonk"
+        }
+        Services.scriptloader.loadSubScriptWithOptions(url, options);
       }
 
       if (this.options.jsCode) {
         Cu.evalInSandbox(this.options.jsCode, sandbox, "latest");
       }
     }
   },
 };
@@ -220,16 +233,17 @@ ExtensionContext.prototype = {
   },
 
   execute(script, shouldRun) {
     script.tryInject(this.extension, this.contentWindow, this.sandbox, shouldRun);
   },
 
   callOnClose(obj) {
     this.onClose.add(obj);
+    Cu.nukeSandbox(this.sandbox);
   },
 
   forgetOnClose(obj) {
     this.onClose.delete(obj);
   },
 
   close() {
     for (let obj of this.onClose) {
@@ -471,17 +485,17 @@ let ExtensionManager = {
       extension.shutdown();
       DocumentManager.shutdownExtension(data.id);
       this.extensions.delete(data.id);
       break;
     }
   }
 };
 
-let ExtensionContent = {
+this.ExtensionContent = {
   globals: new Map(),
 
   init(global) {
     let broker = new MessageBroker([global]);
     this.globals.set(global, broker);
 
     global.addMessageListener("Extension:Execute", this);
 
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -1,15 +1,15 @@
 /* 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 EXPORTED_SYMBOLS = ["ExtensionManagement"];
+this.EXPORTED_SYMBOLS = ["ExtensionManagement"];
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
@@ -183,19 +183,18 @@ let Service = {
     }
 
     let uuid = uri.host;
     let extension = this.uuidMap.get(uuid);
     return extension ? extension.id : undefined;
   },
 };
 
-let ExtensionManagement = {
+this.ExtensionManagement = {
   startupExtension: Service.startupExtension.bind(Service),
   shutdownExtension: Service.shutdownExtension.bind(Service),
 
   registerScript: Scripts.register.bind(Scripts),
   getScripts: Scripts.getScripts.bind(Scripts),
 
   getFrameId: Frames.getId.bind(Frames),
   getParentFrameId: Frames.getParentId.bind(Frames),
 };
-
--- a/toolkit/components/extensions/ExtensionStorage.jsm
+++ b/toolkit/components/extensions/ExtensionStorage.jsm
@@ -1,30 +1,30 @@
 /* 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 EXPORTED_SYMBOLS = ["ExtensionStorage"];
+this.EXPORTED_SYMBOLS = ["ExtensionStorage"];
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/osfile.jsm")
 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
 
 let Path = OS.Path;
 let profileDir = OS.Constants.Path.profileDir;
 
-let ExtensionStorage = {
+this.ExtensionStorage = {
   cache: new Map(),
   listeners: new Map(),
 
   extensionDir: Path.join(profileDir, "browser-extension-data"),
 
   getExtensionDir(extensionId) {
     return Path.join(this.extensionDir, extensionId);
   },
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -1,15 +1,15 @@
 /* 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 EXPORTED_SYMBOLS = ["ExtensionUtils"];
+this.EXPORTED_SYMBOLS = ["ExtensionUtils"];
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
@@ -522,17 +522,18 @@ Messenger.prototype = {
       this.broker.addListener("connect", listener, this.filter);
       return () => {
         this.broker.removeListener("connect", listener);
       };
     }).api();
   },
 };
 
-let ExtensionUtils = {
+this.ExtensionUtils = {
+  runSafeWithoutClone,
   runSafe,
   DefaultWeakMap,
   EventManager,
   SingletonEventManager,
   ignoreEvent,
   injectAPI,
   MessageBroker,
   Messenger,
--- a/toolkit/components/extensions/ext-alarms.js
+++ b/toolkit/components/extensions/ext-alarms.js
@@ -1,8 +1,10 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 let {
   EventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Set[Alarm]]
 let alarmsMap = new WeakMap();
--- a/toolkit/components/extensions/ext-notifications.js
+++ b/toolkit/components/extensions/ext-notifications.js
@@ -1,8 +1,10 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 let {
   EventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Set[Notification]]
 let notificationsMap = new WeakMap();
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -1,8 +1,10 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 let {
   EventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 extensions.registerAPI((extension, context) => {
   return {
--- a/toolkit/components/extensions/ext-storage.js
+++ b/toolkit/components/extensions/ext-storage.js
@@ -1,8 +1,10 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
                                   "resource://gre/modules/ExtensionStorage.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 let {
   EventManager,
   ignoreEvent,
   runSafe,
--- a/toolkit/components/extensions/ext-webNavigation.js
+++ b/toolkit/components/extensions/ext-webNavigation.js
@@ -1,8 +1,12 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebNavigation",
                                   "resource://gre/modules/WebNavigation.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -1,8 +1,12 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",
                                   "resource://gre/modules/WebRequest.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 let {
   SingletonEventManager,
--- a/toolkit/modules/addons/MatchPattern.jsm
+++ b/toolkit/modules/addons/MatchPattern.jsm
@@ -1,19 +1,19 @@
 /* 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 = Components.utils;
 
-const EXPORTED_SYMBOLS = ["MatchPattern"];
+this.EXPORTED_SYMBOLS = ["MatchPattern"];
 
-const PERMITTED_SCHEMES = ["http", "https", "file", "ftp"];
+const PERMITTED_SCHEMES = ["http", "https", "file", "ftp", "app"];
 
 // This function converts a glob pattern (containing * and possibly ?
 // as wildcards) to a regular expression.
 function globToRegexp(pat, allowQuestion)
 {
   // Escape everything except ? and *.
   pat = pat.replace(/[.+^${}()|[\]\\]/g, "\\$&");
 
@@ -32,17 +32,17 @@ function SingleMatchPattern(pat)
 {
   if (pat == "<all_urls>") {
     this.scheme = PERMITTED_SCHEMES;
     this.host = "*";
     this.path = new RegExp('.*');
   } else if (!pat) {
     this.scheme = [];
   } else {
-    let re = new RegExp("^(http|https|file|ftp|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+|)(/.*)$");
+    let re = new RegExp("^(http|https|file|ftp|app|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+|)(/.*)$");
     let match = re.exec(pat);
     if (!match) {
       Cu.reportError(`Invalid match pattern: '${pat}'`);
       this.scheme = [];
       return;
     }
 
     if (match[1] == '*') {
@@ -86,17 +86,17 @@ SingleMatchPattern.prototype = {
     if (!ignorePath && !this.path.test(uri.path)) {
       return false;
     }
 
     return true;
   }
 };
 
-function MatchPattern(pat)
+this.MatchPattern = function(pat)
 {
   this.pat = pat;
   if (!pat) {
     this.matchers = [];
   } else if (pat instanceof String || typeof(pat) == "string") {
     this.matchers = [new SingleMatchPattern(pat)];
   } else {
     this.matchers = [for (p of pat) new SingleMatchPattern(p)];