Bug 1190692: Load web extensions. r=billm
authorDave Townsend <dtownsend@oxymoronical.com>
Fri, 07 Aug 2015 15:53:46 -0700
changeset 288645 896476b65693c0ef789afa2d5cf92ba181beade5
parent 288644 b958b84f2fa0121763910a4257f19a548a65bcd5
child 288646 7ece2ae55806cd2c3e88690baab3d70979350a3a
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1190692
milestone42.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 1190692: Load web extensions. r=billm Also corrects a race condition where if an extension was disabled before it had finished loading its manifest it would have called GlobalManager.init but never call GlobalManager.uninit.
browser/components/extensions/bootstrap.js
browser/components/extensions/prepare.py
toolkit/components/extensions/Extension.jsm
toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/internal/moz.build
toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
deleted file mode 100644
--- a/browser/components/extensions/prepare.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/env python
-# 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/.
-
-import argparse
-import json
-import uuid
-import sys
-import os.path
-
-parser = argparse.ArgumentParser(description='Create install.rdf from manifest.json')
-parser.add_argument('--locale')
-parser.add_argument('--profile')
-parser.add_argument('--uuid')
-parser.add_argument('dir')
-args = parser.parse_args()
-
-manifestFile = os.path.join(args.dir, 'manifest.json')
-manifest = json.load(open(manifestFile))
-
-locale = args.locale
-if not locale:
-    locale = manifest.get('default_locale', 'en-US')
-
-def process_locale(s):
-    if s.startswith('__MSG_') and s.endswith('__'):
-        tag = s[6:-2]
-        path = os.path.join(args.dir, '_locales', locale, 'messages.json')
-        data = json.load(open(path))
-        return data[tag]['message']
-    else:
-        return s
-
-id = args.uuid
-if not id:
-    id = '{' + str(uuid.uuid4()) + '}'
-
-name = process_locale(manifest['name'])
-desc = process_locale(manifest['description'])
-version = manifest['version']
-
-installFile = open(os.path.join(args.dir, 'install.rdf'), 'w')
-print >>installFile, '<?xml version="1.0"?>'
-print >>installFile, '<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"'
-print >>installFile, '     xmlns:em="http://www.mozilla.org/2004/em-rdf#">'
-print >>installFile
-print >>installFile, '  <Description about="urn:mozilla:install-manifest">'
-print >>installFile, '    <em:id>{}</em:id>'.format(id)
-print >>installFile, '    <em:type>2</em:type>'
-print >>installFile, '    <em:name>{}</em:name>'.format(name)
-print >>installFile, '    <em:description>{}</em:description>'.format(desc)
-print >>installFile, '    <em:version>{}</em:version>'.format(version)
-print >>installFile, '    <em:bootstrap>true</em:bootstrap>'
-
-print >>installFile, '    <em:targetApplication>'
-print >>installFile, '      <Description>'
-print >>installFile, '        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>'
-print >>installFile, '        <em:minVersion>4.0</em:minVersion>'
-print >>installFile, '        <em:maxVersion>50.0</em:maxVersion>'
-print >>installFile, '      </Description>'
-print >>installFile, '    </em:targetApplication>'
-
-print >>installFile, '  </Description>'
-print >>installFile, '</RDF>'
-installFile.close()
-
-bootstrapPath = os.path.join(os.path.dirname(sys.argv[0]), 'bootstrap.js')
-data = open(bootstrapPath).read()
-boot = open(os.path.join(args.dir, 'bootstrap.js'), 'w')
-boot.write(data)
-boot.close()
-
-if args.profile:
-    os.system('mkdir -p {}/extensions'.format(args.profile))
-    output = open(args.profile + '/extensions/' + id, 'w')
-    print >>output, os.path.realpath(args.dir)
-    output.close()
-else:
-    dir = os.path.realpath(args.dir)
-    if dir[-1] == os.sep:
-        dir = dir[:-1]
-    os.system('cd "{}"; zip ../"{}".xpi -r *'.format(args.dir, os.path.basename(dir)))
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -136,16 +136,20 @@ let Management = {
     this.emitter.on(hook, callback);
   },
 
   // Ask to run all the callbacks that are registered for a given hook.
   emit(hook, ...args) {
     this.lazyInit();
     this.emitter.emit(hook, ...args);
   },
+
+  off(hook, callback) {
+    this.emitter.off(hook, callback);
+  }
 };
 
 // A MessageBroker that's used to send and receive messages for
 // extension pages (which run in the chrome process).
 let globalBroker = new MessageBroker([Services.mm, Services.ppmm]);
 
 // An extension page is an execution context for any extension content
 // that runs in the chrome process. It's used for background pages
@@ -524,23 +528,23 @@ Extension.prototype = {
     this.onShutdown.add(obj);
   },
 
   forgetOnClose(obj) {
     this.onShutdown.delete(obj);
   },
 
   startup() {
-    GlobalManager.init(this);
-
     return Promise.all([this.readManifest(), this.readLocaleMessages()]).then(([manifest, messages]) => {
       if (this.hasShutdown) {
         return;
       }
 
+      GlobalManager.init(this);
+
       this.manifest = manifest;
       this.localeMessages = messages;
 
       Management.emit("startup", this);
 
       this.runManifest(manifest);
     }).catch(e => {
       dump(`Extension error: ${e} ${e.fileName}:${e.lineNumber}\n`);
rename from browser/components/extensions/bootstrap.js
rename to toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js
--- a/browser/components/extensions/bootstrap.js
+++ b/toolkit/mozapps/extensions/internal/WebExtensionBootstrap.js
@@ -3,18 +3,26 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 Components.utils.import("resource://gre/modules/Extension.jsm");
 
 let extension;
 
+function install(data, reason)
+{
+}
+
 function startup(data, reason)
 {
   extension = new Extension(data);
   extension.startup();
 }
 
 function shutdown(data, reason)
 {
   extension.shutdown();
 }
+
+function uninstall(data, reason)
+{
+}
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -4623,16 +4623,18 @@ this.XPIProvider = {
                                     metadata: { addonID: aId } });
       logger.error("Attempted to load bootstrap scope from missing directory " + aFile.path);
       return;
     }
 
     let uri = getURIForResourceInFile(aFile, "bootstrap.js").spec;
     if (aType == "dictionary")
       uri = "resource://gre/modules/addons/SpellCheckDictionaryBootstrap.js"
+    else if (aType == "webextension")
+      uri = "resource://gre/modules/addons/WebExtensionBootstrap.js"
 
     this.bootstrapScopes[aId] =
       new Cu.Sandbox(principal, { sandboxName: uri,
                                   wantGlobalProperties: ["indexedDB"],
                                   addonId: aId,
                                   metadata: { addonID: aId, URI: uri } });
 
     let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
--- a/toolkit/mozapps/extensions/internal/moz.build
+++ b/toolkit/mozapps/extensions/internal/moz.build
@@ -8,16 +8,17 @@ EXTRA_JS_MODULES.addons += [
     'AddonLogging.jsm',
     'AddonRepository.jsm',
     'AddonRepository_SQLiteMigrator.jsm',
     'AddonUpdateChecker.jsm',
     'Content.js',
     'GMPProvider.jsm',
     'LightweightThemeImageOptimizer.jsm',
     'SpellCheckDictionaryBootstrap.js',
+    'WebExtensionBootstrap.js',
 ]
 
 # Don't ship unused providers on Android
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     EXTRA_JS_MODULES.addons += [
         'PluginProvider.jsm',
     ]
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
@@ -5,47 +5,92 @@
 const ID = "webextension1@tests.mozilla.org";
 
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 startupManager();
 
+const { GlobalManager, Management } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
+
+function promiseAddonStartup() {
+  return new Promise(resolve => {
+    let listener = (extension) => {
+      Management.off("startup", listener);
+      resolve(extension);
+    }
+
+    Management.on("startup", listener);
+  });
+}
+
 add_task(function*() {
-  yield promiseInstallAllFiles([do_get_addon("webextension_1")], true);
+  do_check_eq(GlobalManager.count, 0);
+  do_check_false(GlobalManager.extensionMap.has(ID));
+
+  yield Promise.all([
+    promiseInstallAllFiles([do_get_addon("webextension_1")], true),
+    promiseAddonStartup()
+  ]);
+
+  do_check_eq(GlobalManager.count, 1);
+  do_check_true(GlobalManager.extensionMap.has(ID));
 
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_eq(addon.version, "1.0");
   do_check_eq(addon.name, "Web Extension Name");
   do_check_true(addon.isCompatible);
   do_check_false(addon.appDisabled);
   do_check_true(addon.isActive);
   do_check_eq(addon.type, "extension");
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
 
   // Should persist through a restart
-  yield promiseRestartManager();
+  yield promiseShutdownManager();
+
+  do_check_eq(GlobalManager.count, 0);
+  do_check_false(GlobalManager.extensionMap.has(ID));
+
+  startupManager();
+  yield promiseAddonStartup();
+
+  do_check_eq(GlobalManager.count, 1);
+  do_check_true(GlobalManager.extensionMap.has(ID));
 
   addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
   do_check_eq(addon.version, "1.0");
   do_check_eq(addon.name, "Web Extension Name");
   do_check_true(addon.isCompatible);
   do_check_false(addon.appDisabled);
   do_check_true(addon.isActive);
   do_check_eq(addon.type, "extension");
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
 
   let file = getFileForAddon(profileDir, ID);
   do_check_true(file.exists());
 
+  addon.userDisabled = true;
+
+  do_check_eq(GlobalManager.count, 0);
+  do_check_false(GlobalManager.extensionMap.has(ID));
+
+  addon.userDisabled = false;
+  yield promiseAddonStartup();
+
+  do_check_eq(GlobalManager.count, 1);
+  do_check_true(GlobalManager.extensionMap.has(ID));
+
   addon.uninstall();
 
+  do_check_eq(GlobalManager.count, 0);
+  do_check_false(GlobalManager.extensionMap.has(ID));
+
   yield promiseShutdownManager();
 });
 
 // Writing the manifest direct to the profile should work
 add_task(function*() {
   writeWebManifestForExtension({
     name: "Web Extension Name",
     version: "1.0",