Bug 1282981 - Implement management.get and getAll, r?mixedpuppy draft
authorBob Silverberg <bsilverberg@mozilla.com>
Fri, 09 Jun 2017 11:04:46 -0400
changeset 593392 bb413d7ef12f2209fe8e7544da8df29bbc5d4b37
parent 592551 f9605772a0c9098ed1bcaa98089b2c944ed69e9b
child 633093 21d4378d502a275fddaf135af28ed828801dd417
push id63677
push userbmo:bob.silverberg@gmail.com
push dateTue, 13 Jun 2017 13:50:16 +0000
reviewersmixedpuppy
bugs1282981
milestone55.0a1
Bug 1282981 - Implement management.get and getAll, r?mixedpuppy This updates the existing implementation of browser.management.getAll to allow both themes and extensions to be returned. It also adds an API for browser.management.get. MozReview-Commit-ID: C1v2FqQqNux
browser/locales/en-US/chrome/browser/browser.properties
toolkit/components/extensions/ext-management.js
toolkit/components/extensions/schemas/management.json
toolkit/components/extensions/test/browser/browser_ext_management_themes.js
toolkit/components/extensions/test/xpcshell/test_ext_management.js
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -96,16 +96,17 @@ webextPerms.optionalPermsDeny.label=Deny
 webextPerms.optionalPermsDeny.accessKey=D
 
 webextPerms.description.bookmarks=Read and modify bookmarks
 webextPerms.description.clipboardRead=Get data from the clipboard
 webextPerms.description.clipboardWrite=Input data to the clipboard
 webextPerms.description.downloads=Download files and read and modify the browser’s download history
 webextPerms.description.geolocation=Access your location
 webextPerms.description.history=Access browsing history
+webextPerms.description.management=Monitor extension usage and manage themes
 # LOCALIZATION NOTE (webextPerms.description.nativeMessaging)
 # %S will be replaced with the name of the application
 webextPerms.description.nativeMessaging=Exchange messages with programs other than %S
 webextPerms.description.notifications=Display notifications to you
 webextPerms.description.privacy=Read and modify privacy settings
 webextPerms.description.sessions=Access recently closed tabs
 webextPerms.description.tabs=Access browser tabs
 webextPerms.description.topSites=Access browsing history
--- a/toolkit/components/extensions/ext-management.js
+++ b/toolkit/components/extensions/ext-management.js
@@ -84,56 +84,60 @@ function getExtensionInfoForAddon(extens
   if (addon.updateURL) {
     extInfo.updateUrl = addon.updateURL;
   }
   return extInfo;
 }
 
 const listenerMap = new WeakMap();
 // Some management APIs are intentionally limited.
-const allowedTypes = ["theme"];
+const allowedTypes = ["theme", "extension"];
 
 class AddonListener {
   constructor() {
     AddonManager.addAddonListener(this);
     EventEmitter.decorate(this);
   }
 
   release() {
     AddonManager.removeAddonListener(this);
   }
 
   getExtensionInfo(addon) {
     let ext = addon.isWebExtension && GlobalManager.extensionMap.get(addon.id);
     return getExtensionInfoForAddon(ext, addon);
   }
 
+  checkAllowed(addon) {
+    return !addon.isSystem && allowedTypes.includes(addon.type);
+  }
+
   onEnabled(addon) {
-    if (!allowedTypes.includes(addon.type)) {
+    if (!this.checkAllowed(addon)) {
       return;
     }
     this.emit("onEnabled", this.getExtensionInfo(addon));
   }
 
   onDisabled(addon) {
-    if (!allowedTypes.includes(addon.type)) {
+    if (!this.checkAllowed(addon)) {
       return;
     }
     this.emit("onDisabled", this.getExtensionInfo(addon));
   }
 
   onInstalled(addon) {
-    if (!allowedTypes.includes(addon.type)) {
+    if (!this.checkAllowed(addon)) {
       return;
     }
     this.emit("onInstalled", this.getExtensionInfo(addon));
   }
 
   onUninstalled(addon) {
-    if (!allowedTypes.includes(addon.type)) {
+    if (!this.checkAllowed(addon)) {
       return;
     }
     this.emit("onUninstalled", this.getExtensionInfo(addon));
   }
 }
 
 let addonListener;
 
@@ -156,19 +160,26 @@ function getListener(extension, context)
   return addonListener;
 }
 
 this.management = class extends ExtensionAPI {
   getAPI(context) {
     let {extension} = context;
     return {
       management: {
+        async get(id) {
+          let addon = await AddonManager.getAddonByID(id);
+          if (!addon.isSystem) {
+            return getExtensionInfoForAddon(extension, addon);
+          }
+        },
+
         async getAll() {
           let addons = await AddonManager.getAddonsByTypes(allowedTypes);
-          return addons.map(addon => {
+          return addons.filter(addon => !addon.isSystem).map(addon => {
             // If the extension is enabled get it and use it for more data.
             let ext = addon.isWebExtension && GlobalManager.extensionMap.get(addon.id);
             return getExtensionInfoForAddon(ext, addon);
           });
         },
 
         async getSelf() {
           let addon = await AddonManager.getAddonByID(extension.id);
@@ -199,19 +210,22 @@ this.management = class extends Extensio
           addon.uninstall();
         },
 
         async setEnabled(id, enabled) {
           let addon = await AddonManager.getAddonByID(id);
           if (!addon) {
             throw new ExtensionError(`No such addon ${id}`);
           }
-          if (!allowedTypes.includes(addon.type)) {
+          if (addon.type !== "theme") {
             throw new ExtensionError("setEnabled applies only to theme addons");
           }
+          if (addon.isSystem) {
+            throw new ExtensionError("setEnabled cannot be used with a system addon");
+          }
           addon.userDisabled = !enabled;
         },
 
         onDisabled: new SingletonEventManager(context, "management.onDisabled", fire => {
           let listener = (event, data) => {
             fire.async(data);
           };
 
--- a/toolkit/components/extensions/schemas/management.json
+++ b/toolkit/components/extensions/schemas/management.json
@@ -170,17 +170,16 @@
             ]
           }
         ]
       },
       {
         "name": "get",
         "type": "function",
         "permissions": ["management"],
-        "unsupported": true,
         "description": "Returns information about the installed extension that has the given ID.",
         "async": "callback",
         "parameters": [
           {
             "name": "id",
             "$ref": "manifest.ExtensionID",
             "description": "The ID from an item of $(ref:management.ExtensionInfo)."
           },
--- a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
@@ -6,16 +6,18 @@ const {LightweightThemeManager} = Cu.imp
 
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({
     set: [["extensions.webextensions.themes.enabled", true]],
   });
 });
 
 add_task(async function test_management_themes() {
+  const TEST_ID = "test_management_themes@tests.mozilla.com";
+
   let theme = ExtensionTestUtils.loadExtension({
     manifest: {
       "name": "Simple theme test",
       "version": "1.0",
       "description": "test theme",
       "theme": {
         "images": {
           "headerURL": "image1.png",
@@ -23,17 +25,17 @@ add_task(async function test_management_
       },
     },
     files: {
       "image1.png": BACKGROUND,
     },
     useAddonManager: "temporary",
   });
 
-  async function background() {
+  async function background(TEST_ID) {
     browser.management.onInstalled.addListener(info => {
       browser.test.log(`${info.name} was installed`);
       browser.test.assertEq(info.type, "theme", "addon is theme");
       browser.test.sendMessage("onInstalled", info.name);
     });
     browser.management.onDisabled.addListener(info => {
       browser.test.log(`${info.name} was disabled`);
       browser.test.assertEq(info.type, "theme", "addon is theme");
@@ -47,20 +49,25 @@ add_task(async function test_management_
     browser.management.onUninstalled.addListener(info => {
       browser.test.log(`${info.name} was uninstalled`);
       browser.test.assertEq(info.type, "theme", "addon is theme");
       browser.test.sendMessage("onUninstalled", info.name);
     });
 
     async function getAddon(type) {
       let addons = await browser.management.getAll();
+      let themes = addons.filter(addon => addon.type === "theme");
       // We get the 3 built-in themes plus the lwt and our addon.
-      browser.test.assertEq(5, addons.length, "got expected addons");
+      browser.test.assertEq(5, themes.length, "got expected addons");
+      // We should also get our test extension.
+      let testExtension = addons.find(addon => { return addon.id === TEST_ID; });
+      browser.test.assertTrue(!!testExtension,
+                              `The extension with id ${TEST_ID} was returned by getAll.`);
       let found;
-      for (let addon of addons) {
+      for (let addon of themes) {
         browser.test.assertEq(addon.type, "theme", "addon is theme");
         if (type == "theme" && addon.id.includes("temporary-addon")) {
           found = addon;
         } else if (type == "enabled" && addon.enabled) {
           found = addon;
         }
       }
       return found;
@@ -84,19 +91,26 @@ add_task(async function test_management_
       browser.test.assertEq(theme.id, addon.id, "theme is enabled");
 
       browser.test.sendMessage("done");
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
+      applications: {
+        gecko: {
+          id: TEST_ID,
+        },
+      },
+      name: TEST_ID,
       permissions: ["management"],
     },
-    background,
+    background: `(${background})("${TEST_ID}")`,
+    useAddonManager: "temporary",
   });
   await extension.startup();
 
   // Test LWT
   LightweightThemeManager.currentTheme = {
     id: "lwt@personas.mozilla.org",
     version: "1",
     name: "Bling",
--- a/toolkit/components/extensions/test/xpcshell/test_ext_management.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_management.js
@@ -4,27 +4,63 @@
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 
 add_task(async function setup() {
   await ExtensionTestUtils.startAddonManager();
 });
 
-add_task(async function test_management_schema() {
+add_task(async function test_management_getAll() {
+  const id1 = "get_all_test1@tests.mozilla.com";
+  const id2 = "get_all_test2@tests.mozilla.com";
+
+  function getManifest(id) {
+    return {
+      applications: {
+        gecko: {
+          id,
+        },
+      },
+      name: id,
+      version: "1.0",
+      permissions: ["management"],
+    };
+  }
+
   async function background() {
-    browser.test.assertTrue(browser.management, "browser.management API exists");
-    let self = await browser.management.getSelf();
-    browser.test.assertEq(browser.runtime.id, self.id, "got self");
-    browser.test.notifyPass("management-schema");
+    browser.test.onMessage.addListener(async (msg, id) => {
+      let addon = await browser.management.get(id);
+      browser.test.sendMessage("addon", addon);
+    });
+
+    let addons = await browser.management.getAll();
+    browser.test.assertEq(addons.length, 2, "management.getAll returned two extensions.");
+    browser.test.sendMessage("addons", addons);
   }
 
-  let extension = ExtensionTestUtils.loadExtension({
-    manifest: {
-      permissions: ["management"],
-    },
-    background: `(${background})()`,
+  let extension1 = ExtensionTestUtils.loadExtension({
+    manifest: getManifest(id1),
+    useAddonManager: "temporary",
+  });
+
+  let extension2 = ExtensionTestUtils.loadExtension({
+    manifest: getManifest(id2),
+    background,
     useAddonManager: "temporary",
   });
-  await extension.startup();
-  await extension.awaitFinish("management-schema");
-  await extension.unload();
+
+  await extension1.startup();
+  await extension2.startup();
+
+  let addons = await extension2.awaitMessage("addons");
+  for (let id of [id1, id2]) {
+    let addon = addons.find(a => { return a.id === id; });
+    equal(addon.name, id, `The extension with id ${id} was returned by getAll.`);
+  }
+
+  extension2.sendMessage("getAddon", id1);
+  let addon = await extension2.awaitMessage("addon");
+  equal(addon.name, id1, `The extension with id ${id1} was returned by get.`);
+
+  await extension2.unload();
+  await extension1.unload();
 });