Bug 1255041 - Implement uninstall() on DOM Addon objects. r=rhelmer
authorAndrew Swan <aswan@mozilla.com>
Thu, 21 Apr 2016 09:59:14 -0700
changeset 332398 c21153b1155fa7f162245fb546cfaef75f2bc8ec
parent 332397 5655a04459af6ab8a11a030beb36496198fb00bc
child 332399 752d9eb3ebea2993d4fb32b3513db9f9b6b766e3
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrhelmer
bugs1255041
milestone48.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 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() {