Bug 1454909 - Add support for system addon signing for web extension bundling. r=johannh, r=kmag, a=RyanVM
☠☠ backed out by 55bafed34718 ☠ ☠
authorJonathan Kingston <jkt@mozilla.com>
Thu, 07 Jun 2018 14:23:45 -0400
changeset 806032 d0564109d0e340caa7cdcf0e96aaa96b1feb0a09
parent 806031 00155725c3eae3e4c04b7c88e88f9f52670ee704
child 806033 c7187146116fe90f3e98a7d7657fe5501faad472
child 808797 1efb74b5ebe2dc9d9d6083246ce4862d031d46cc
push id112832
push userbballo@mozilla.com
push dateFri, 08 Jun 2018 21:11:22 +0000
reviewersjohannh, kmag, RyanVM
bugs1454909
milestone60.0.3
Bug 1454909 - Add support for system addon signing for web extension bundling. r=johannh, r=kmag, a=RyanVM
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionTestCommon.jsm
toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js
toolkit/mozapps/extensions/internal/XPIProvider.jsm
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -488,16 +488,20 @@ class ExtensionData {
   static comparePermissions(oldPermissions, newPermissions) {
     let oldMatcher = new MatchPatternSet(oldPermissions.origins);
     return {
       origins: newPermissions.origins.filter(perm => !oldMatcher.subsumes(new MatchPattern(perm))),
       permissions: newPermissions.permissions.filter(perm => !oldPermissions.permissions.includes(perm)),
     };
   }
 
+  canUseExperiment(manifest) {
+    return this.experimentsAllowed && manifest.experiment_apis;
+  }
+
   // eslint-disable-next-line complexity
   async parseManifest() {
     let [manifest] = await Promise.all([
       this.readJSON("manifest.json"),
       Management.lazyInit(),
     ]);
 
     this.manifest = manifest;
@@ -632,17 +636,17 @@ class ExtensionData {
         scopes: data.scopes,
       });
 
       let computeModuleInit = (scope, modules) => {
         let manager = new ExtensionCommon.SchemaAPIManager(scope);
         return manager.initModuleJSON([modules]);
       };
 
-      if (manifest.experiment_apis) {
+      if (this.canUseExperiment(manifest)) {
         let parentModules = {};
         let childModules = {};
 
         for (let [name, data] of Object.entries(manifest.experiment_apis)) {
           let schema = this.getURL(data.schema);
 
           if (!schemaPromises.has(schema)) {
             schemaPromises.set(schema, this.readJSON(data.schema).then(json => Schemas.processSchema(json)));
@@ -1366,16 +1370,18 @@ class Extension extends ExtensionData {
   }
 
   get manifestCacheKey() {
     return [this.id, this.version, Services.locale.getAppLocaleAsLangTag()];
   }
 
   get isPrivileged() {
     return (this.addonData.signedState === AddonManager.SIGNEDSTATE_PRIVILEGED ||
+            this.addonData.signedState === AddonManager.SIGNEDSTATE_SYSTEM ||
+            this.addonData.builtIn ||
             (AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS &&
              this.addonData.temporarilyInstalled));
   }
 
   get experimentsAllowed() {
     return (AddonSettings.ALLOW_LEGACY_EXTENSIONS ||
             this.isPrivileged);
   }
--- a/toolkit/components/extensions/ExtensionTestCommon.jsm
+++ b/toolkit/components/extensions/ExtensionTestCommon.jsm
@@ -369,18 +369,25 @@ var ExtensionTestCommon = class Extensio
       } else if (data.manifest.browser_specific_settings && data.manifest.browser_specific_settings.gecko) {
         id = data.manifest.browser_specific_settings.gecko.id;
       }
     }
     if (!id) {
       id = uuidGen.generateUUID().number;
     }
 
+    let signedState = AddonManager.SIGNEDSTATE_SIGNED;
+    if (data.isPrivileged) {
+      signedState = AddonManager.SIGNEDSTATE_PRIVILEGED;
+    }
+    if (data.isSystem) {
+      signedState = AddonManager.SIGNEDSTATE_SYSTEM;
+    }
+
     return new Extension({
       id,
       resourceURI: jarURI,
       cleanupFile: file,
-      signedState: data.isPrivileged ? AddonManager.SIGNEDSTATE_PRIVILEGED
-                                     : AddonManager.SIGNEDSTATE_SIGNED,
+      signedState,
       temporarilyInstalled: !!data.temporarilyInstalled,
     });
   }
 };
--- a/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
@@ -281,46 +281,71 @@ async function testFooExperiment() {
 
   browser.test.assertEq("child", browser.experiments.foo.child(),
                         "foo.child()");
 
   browser.test.assertEq("parent", await browser.experiments.foo.parent(),
                         "await foo.parent()");
 }
 
+async function testFooFailExperiment() {
+  browser.test.assertEq("object", typeof browser.experiments,
+                        "typeof browser.experiments");
+
+  browser.test.assertEq("undefined", typeof browser.experiments.foo,
+                        "typeof browser.experiments.foo");
+}
+
 add_task(async function test_bundled_experiments() {
-  async function background() {
-    await testFooExperiment();
+  let testCases = [
+    {isSystem: true, temporarilyInstalled: true, shouldHaveExperiments: true},
+    {isSystem: true, temporarilyInstalled: false, shouldHaveExperiments: true},
+    {isPrivileged: true, temporarilyInstalled: true, shouldHaveExperiments: true},
+    {isPrivileged: true, temporarilyInstalled: false, shouldHaveExperiments: true},
+    {isPrivileged: false, temporarilyInstalled: true, shouldHaveExperiments: true},
+    {isPrivileged: false, temporarilyInstalled: false, shouldHaveExperiments: false},
+  ];
+
+  async function background(shouldHaveExperiments) {
+    if (shouldHaveExperiments) {
+      await testFooExperiment();
+    } else {
+      await testFooFailExperiment();
+    }
 
     browser.test.notifyPass("background.experiments.foo");
   }
 
-  let extension = ExtensionTestUtils.loadExtension({
-    isPrivileged: true,
+  for (let testCase of testCases) {
+    let extension = ExtensionTestUtils.loadExtension({
+      isPrivileged: testCase.isPrivileged,
+      isSystem: testCase.isSystem,
+      temporarilyInstalled: testCase.temporarilyInstalled,
 
-    manifest: {
-      experiment_apis: fooExperimentAPIs,
-    },
+      manifest: {
+        experiment_apis: fooExperimentAPIs,
+      },
 
-    background: `
-      ${testFooExperiment}
-      (${background})();
-    `,
-
-    files: fooExperimentFiles,
-  });
+      background: `
+        ${testFooExperiment}
+        ${testFooFailExperiment}
+        (${background})(${testCase.shouldHaveExperiments});
+      `,
 
-  await extension.startup();
+      files: fooExperimentFiles,
+    });
 
-  await extension.awaitFinish("background.experiments.foo");
+    await extension.startup();
 
-  await extension.unload();
+    await extension.awaitFinish("background.experiments.foo");
+
+    await extension.unload();
+  }
 });
 
-
 add_task(async function test_unbundled_experiments() {
   async function background() {
     await testFooExperiment();
 
     browser.test.assertEq("object", typeof browser.experiments.crunk,
                           "typeof browser.experiments.crunk");
 
     browser.test.assertEq("function", typeof browser.experiments.crunk.child,
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js
@@ -54,16 +54,17 @@ function setHandlingUserInput(extension)
 
 // Test that the schema requireUserInput flag works correctly for
 // proxied api implementations.
 add_task(async function test_proxy() {
   let apiUrl = URL.createObjectURL(new Blob([API.toString()]));
   ExtensionAPIs.register("userinputtest", schemaUrl, apiUrl);
 
   let extension = ExtensionTestUtils.loadExtension({
+    isPrivileged: true,
     background() {
       browser.test.onMessage.addListener(async () => {
         try {
           await browser.userinputtest.test();
           browser.test.sendMessage("result", null);
         } catch (err) {
           browser.test.sendMessage("result", err.message);
         }
@@ -102,16 +103,17 @@ add_task(async function test_local() {
     userinputtest: {
       url: apiUrl,
       scopes: ["addon_child"],
       paths: [["userinputtest"]],
     },
   });
 
   let extension = ExtensionTestUtils.loadExtension({
+    isPrivileged: true,
     background() {
       browser.test.onMessage.addListener(async () => {
         try {
           await browser.userinputtest.test();
           browser.test.sendMessage("result", null);
         } catch (err) {
           browser.test.sendMessage("result", err.message);
         }
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -4393,16 +4393,17 @@ var XPIProvider = {
       let installLocation = aAddon._installLocation || null;
       let params = {
         id: aAddon.id,
         version: aAddon.version,
         installPath: aFile.clone(),
         resourceURI: getURIForResourceInFile(aFile, ""),
         signedState: aAddon.signedState,
         temporarilyInstalled: installLocation == TemporaryInstallLocation,
+        builtIn: installLocation instanceof BuiltInInstallLocation,
       };
 
       if (aMethod == "startup" && aAddon.startupData) {
         params.startupData = aAddon.startupData;
       }
 
       if (aExtraParams) {
         for (let key in aExtraParams) {