Bug 1255041 Implement uninstall() on DOM Addon objects r?rhelmer draft
authorAndrew Swan <aswan@mozilla.com>
Thu, 21 Apr 2016 09:59:14 -0700
changeset 355008 6dc90b8c352478206be2e48b174dc515d7f04181
parent 354901 c277eb72ddb0eafcc8068d9dd38a341a45d0155b
child 519125 fe58b82d13cc2ccaa16c166156fc510af00a576c
push id16209
push useraswan@mozilla.com
push dateThu, 21 Apr 2016 23:16:34 +0000
reviewersrhelmer
bugs1255041
milestone48.0a1
Bug 1255041 Implement uninstall() on DOM Addon objects r?rhelmer MozReview-Commit-ID: Ad3r78Y9IKb
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/amWebAPI.js
toolkit/mozapps/extensions/test/browser/browser.ini
toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
toolkit/mozapps/extensions/test/browser/browser_webapi_uninstall.js
toolkit/mozapps/extensions/test/browser/head.js
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -309,20 +309,22 @@ function getLocale() {
 
 function webAPIForAddon(addon) {
   if (!addon) {
     return null;
   }
 
   let result = {};
 
-  // By default just pass through any plain property, the webidl will control
-  // access.
+  // By default just pass through any plain property, the webidl will
+  // control access.  Also filter out private properties, regular Addon
+  // objects are okay but MockAddon used in tests has non-serializable
+  // private properties.
   for (let prop in addon) {
-    if (typeof(addon[prop]) != "function") {
+    if (prop[0] != "_" && typeof(addon[prop]) != "function") {
       result[prop] = addon[prop];
     }
   }
 
   // A few properties are computed for a nicer API
   result.isEnabled = !addon.userDisabled;
 
   return result;
@@ -2854,16 +2856,34 @@ var AddonManagerInternal = {
           let result = {id};
           this.copyProps(install, result);
           resolve(result);
         };
         AddonManager.getInstallForURL(options.url, newInstall, "application/x-xpinstall");
       });
     },
 
+    addonUninstall(target, id) {
+      return new Promise(resolve => {
+        AddonManager.getAddonByID(id, addon => {
+          if (!addon) {
+            resolve(false);
+          }
+
+          try {
+            addon.uninstall();
+            resolve(true);
+          } catch (err) {
+            Cu.reportError(err);
+            resolve(false);
+          }
+        });
+      });
+    },
+
     addonInstallDoInstall(target, id) {
       let state = this.installs.get(id);
       if (!state) {
         return Promise.reject(`invalid id ${id}`);
       }
       return Promise.resolve(state.install.install());
     },
 
--- a/toolkit/mozapps/extensions/amWebAPI.js
+++ b/toolkit/mozapps/extensions/amWebAPI.js
@@ -71,26 +71,23 @@ const APIBroker = {
 
   sendCleanup: function(ids) {
     Services.cpmm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids });
   },
 };
 
 APIBroker.init();
 
-function Addon(win, properties) {
+function Addon(window, properties) {
+  this.window = window;
+
   // We trust the webidl binding to broker access to our properties.
   for (let key of Object.keys(properties)) {
     this[key] = properties[key];
   }
-
-  this.uninstall = function() {
-    let err = new win.Error("not yet implemented");
-    return win.Promise.reject(err);
-  };
 }
 
 function AddonInstall(window, properties) {
   let id = properties.id;
   APIBroker._installMap.set(id, this);
 
   this.window = window;
   this.handlers = new Map();
@@ -123,16 +120,22 @@ function WebAPITask(generator) {
 
     return new win.Promise((resolve, reject) => {
       task(...args).then(wrapForContent)
                    .then(resolve, reject);
     });
   }
 }
 
+Addon.prototype = {
+  uninstall: WebAPITask(function*() {
+    return yield APIBroker.sendRequest("addonUninstall", this.id);
+  }),
+};
+
 const INSTALL_EVENTS = [
   "onDownloadStarted",
   "onDownloadProgress",
   "onDownloadEnded",
   "onDownloadCancelled",
   "onDownloadFailed",
   "onInstallStarted",
   "onInstallEnded",
--- a/toolkit/mozapps/extensions/test/browser/browser.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser.ini
@@ -62,10 +62,11 @@ skip-if = require_signing
 [browser_newaddon.js]
 [browser_updatessl.js]
 [browser_task_next_test.js]
 [browser_discovery_install.js]
 [browser_update.js]
 [browser_webapi.js]
 [browser_webapi_access.js]
 [browser_webapi_install.js]
+[browser_webapi_uninstall.js]
 
 [include:browser-common.ini]
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
@@ -227,16 +227,16 @@ add_task(makeInstallTest(function* (brow
       event: "onDownloadFailed",
       props: {
         state: "STATE_DOWNLOAD_FAILED",
         error: "ERROR_NETWORK_FAILURE",
       },
     }
   ];
 
-  yield testInstall(browser, XPI_URL + "bogus", steps, "a basic install works");
+  yield testInstall(browser, XPI_URL + "bogus", steps, "install of a bad url fails");
 
   let addons = yield promiseAddonsByIDs([ID]);
   is(addons[0], null, "The addon was not installed");
 
   ok(AddonManager.webAPI.installs.size > 0, "webAPI is tracking the AddonInstall");
 }));
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_uninstall.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TESTPAGE = `${SECURE_TESTROOT}webapi_checkavailable.html`;
+
+Services.prefs.setBoolPref("extensions.webapi.testing", true);
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("extensions.webapi.testing");
+});
+
+function testWithAPI(task) {
+  return function*() {
+    yield BrowserTestUtils.withNewTab(TESTPAGE, task);
+  }
+}
+
+function API_uninstallByID(browser, id) {
+  return ContentTask.spawn(browser, id, function*(id) {
+    let addon = yield content.navigator.mozAddonManager.getAddonByID(id);
+
+    let result = yield addon.uninstall();
+    return result;
+  });
+}
+
+add_task(testWithAPI(function*(browser) {
+  const ID1 = "addon1@tests.mozilla.org";
+  const ID2 = "addon2@tests.mozilla.org";
+
+  let provider = new MockProvider();
+
+  provider.addAddon(new MockAddon(ID1, "Test add-on 1", "extension", 0));
+  provider.addAddon(new MockAddon(ID2, "Test add-on 2", "extension", 0));
+
+  let [a1, a2] = yield promiseAddonsByIDs([ID1, ID2]);
+  isnot(a1, null, "addon1 is installed");
+  isnot(a2, null, "addon2 is installed");
+
+  let result = yield API_uninstallByID(browser, ID1);
+  is(result, true, "uninstall of addon1 succeeded");
+
+  [a1, a2] = yield promiseAddonsByIDs([ID1, ID2]);
+  is(a1, null, "addon1 is uninstalled");
+  isnot(a2, null, "addon2 is still installed");
+
+  result = yield API_uninstallByID(browser, ID2);
+  is(result, true, "uninstall of addon2 succeeded");
+  [a2] = yield promiseAddonsByIDs([ID2]);
+  is(a2, null, "addon2 is uninstalled");
+}));
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -1124,17 +1124,18 @@ function MockAddon(aId, aName, aType, aO
   this.scope = AddonManager.SCOPE_PROFILE;
   this.isActive = true;
   this.creator = "";
   this.pendingOperations = 0;
   this._permissions = AddonManager.PERM_CAN_UNINSTALL |
                       AddonManager.PERM_CAN_ENABLE |
                       AddonManager.PERM_CAN_DISABLE |
                       AddonManager.PERM_CAN_UPGRADE;
-  this.operationsRequiringRestart = aOperationsRequiringRestart ||
+  this.operationsRequiringRestart = (aOperationsRequiringRestart != undefined) ?
+    aOperationsRequiringRestart :
     (AddonManager.OP_NEEDS_RESTART_INSTALL |
      AddonManager.OP_NEEDS_RESTART_UNINSTALL |
      AddonManager.OP_NEEDS_RESTART_ENABLE |
      AddonManager.OP_NEEDS_RESTART_DISABLE);
 }
 
 MockAddon.prototype = {
   get shouldBeActive() {