Bug 1273184 - Don't allow reloading of unsupported add-ons. r=ochameau draft
authorKumar McMillan <kumar.mcmillan@gmail.com>
Mon, 16 May 2016 16:08:46 -0500
changeset 367950 4320a40023e805f741ac2ca6a34b838c7088c911
parent 366965 df68e02c25c4fc1fc75c2399bc4a4a48bdcb2ee1
child 521142 d877104f8b01bb0635960491a07ab1961557e2b8
push id18390
push userbmo:kumar.mcmillan@gmail.com
push dateTue, 17 May 2016 19:10:59 +0000
reviewersochameau
bugs1273184
milestone49.0a1
Bug 1273184 - Don't allow reloading of unsupported add-ons. r=ochameau MozReview-Commit-ID: ERSpt8X9MD8
devtools/client/aboutdebugging/components/addons/panel.js
devtools/client/aboutdebugging/components/addons/target.js
devtools/client/aboutdebugging/test/addons/bug1273184.xpi
devtools/client/aboutdebugging/test/browser.ini
devtools/client/aboutdebugging/test/browser_addons_reload.js
devtools/client/locales/en-US/aboutdebugging.properties
devtools/server/actors/addon.js
--- a/devtools/client/aboutdebugging/components/addons/panel.js
+++ b/devtools/client/aboutdebugging/components/addons/panel.js
@@ -62,17 +62,18 @@ module.exports = createClass({
   updateAddonsList() {
     this.props.client.listAddons()
       .then(({addons}) => {
         let extensions = addons.filter(addon => addon.debuggable).map(addon => {
           return {
             name: addon.name,
             icon: addon.iconURL || ExtensionIcon,
             addonID: addon.id,
-            addonActor: addon.actor
+            addonActor: addon.actor,
+            temporarilyInstalled: addon.temporarilyInstalled
           };
         });
 
         this.setState({ extensions });
       }, error => {
         throw new Error("Client error while listing addons: " + error);
       });
   },
--- a/devtools/client/aboutdebugging/components/addons/target.js
+++ b/devtools/client/aboutdebugging/components/addons/target.js
@@ -35,16 +35,18 @@ module.exports = createClass({
     }).then(() => {}, error => {
       throw new Error(
         "Error reloading addon " + target.addonID + ": " + error);
     });
   },
 
   render() {
     let { target, debugDisabled } = this.props;
+    // Only temporarily installed add-ons can be reloaded.
+    const canBeReloaded = target.temporarilyInstalled;
 
     return dom.li({ className: "target-container" },
       dom.img({
         className: "target-icon",
         role: "presentation",
         src: target.icon
       }),
       dom.div({ className: "target" },
@@ -52,13 +54,16 @@ module.exports = createClass({
       ),
       dom.button({
         className: "debug-button",
         onClick: this.debug,
         disabled: debugDisabled,
       }, Strings.GetStringFromName("debug")),
       dom.button({
         className: "reload-button",
-        onClick: this.reload
+        onClick: this.reload,
+        disabled: !canBeReloaded,
+        title: !canBeReloaded ?
+          Strings.GetStringFromName("reloadDisabledTooltip") : ""
       }, Strings.GetStringFromName("reload"))
     );
   }
 });
new file mode 100644
index 0000000000000000000000000000000000000000..946422b6f188b1c597d7d59236b076c3949706ac
GIT binary patch
literal 280
zc$^FHW@Zs#U|`^2m{GC8!!T$2#0(&B8W8g`$S~w4=4Ga(7MJK{73b%LhHx@4-&eN^
z{|CgS72FJrEMFNJ7{J<BKMM*7IOTihw2!y0UZ=jduGYCT=Yux|8C+uYe4_en8Iz@#
z=hBy4ZEnwwwTY>DC2+PlsXE&-cSIyPI{Kv*C2<Dqk<bX_6=CjLwp=a!`Qe=ojb}tw
zbOtcXdNr7dYd0><-s!Mo8aG3LHzSiAGcE^9Fn|EiO$<vKK`b=qvqGGY=JWt>RyK$>
MMuuP@y&h}`09TPrj{pDw
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   addons/unpacked/bootstrap.js
   addons/unpacked/install.rdf
   addons/bad/manifest.json
+  addons/bug1273184.xpi
   service-workers/empty-sw.html
   service-workers/empty-sw.js
   service-workers/push-sw.html
   service-workers/push-sw.js
 
 [browser_addons_debug_bootstrapped.js]
 [browser_addons_debugging_initial_state.js]
 [browser_addons_install.js]
--- a/devtools/client/aboutdebugging/test/browser_addons_reload.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_reload.js
@@ -17,26 +17,58 @@ function promiseAddonEvent(event) {
         resolve(args);
       }
     };
 
     AddonManager.addAddonListener(listener);
   });
 }
 
+function* tearDownAddon(addon) {
+  const onUninstalled = promiseAddonEvent("onUninstalled");
+  addon.uninstall();
+  const [uninstalledAddon] = yield onUninstalled;
+  is(uninstalledAddon.id, addon.id,
+     `Add-on was uninstalled: ${uninstalledAddon.id}`);
+}
+
 function getReloadButton(document, addonName) {
   const names = [...document.querySelectorAll("#addons .target-name")];
   const name = names.filter(element => element.textContent === addonName)[0];
   ok(name, `Found ${addonName} add-on in the list`);
   const targetElement = name.parentNode.parentNode;
   const reloadButton = targetElement.querySelector(".reload-button");
   info(`Found reload button for ${addonName}`);
   return reloadButton;
 }
 
+function installAddonWithManager(filePath) {
+  return new Promise((resolve, reject) => {
+    AddonManager.getInstallForFile(filePath, install => {
+      if (!install) {
+        throw new Error(`An install was not created for ${filePath}`);
+      }
+      install.addListener({
+        onDownloadFailed: reject,
+        onDownloadCancelled: reject,
+        onInstallFailed: reject,
+        onInstallCancelled: reject,
+        onInstallEnded: resolve
+      });
+      install.install();
+    });
+  });
+}
+
+function getAddonByID(addonId) {
+  return new Promise(resolve => {
+    AddonManager.getAddonByID(addonId, addon => resolve(addon));
+  });
+}
+
 /**
  * Creates a web extension from scratch in a temporary location.
  * The object must be removed when you're finished working with it.
  */
 class TempWebExt {
   constructor(addonId) {
     this.addonId = addonId;
     this.tmpDir = FileUtils.getDir("TmpD", ["browser_addons_reload"]);
@@ -76,16 +108,18 @@ class TempWebExt {
 
 add_task(function* reloadButtonReloadsAddon() {
   const { tab, document } = yield openAboutDebugging("addons");
   yield waitForInitialAddonList(document);
   yield installAddon(document, "addons/unpacked/install.rdf",
                      ADDON_NAME, ADDON_NAME);
 
   const reloadButton = getReloadButton(document, ADDON_NAME);
+  is(reloadButton.disabled, false, "Reload button should not be disabled");
+  is(reloadButton.title, "", "Reload button should not have a tooltip");
   const onInstalled = promiseAddonEvent("onInstalled");
 
   const onBootstrapInstallCalled = new Promise(done => {
     Services.obs.addObserver(function listener() {
       Services.obs.removeObserver(listener, ADDON_NAME, false);
       info("Add-on was re-installed: " + ADDON_NAME);
       done();
     }, ADDON_NAME, false);
@@ -93,24 +127,17 @@ add_task(function* reloadButtonReloadsAd
 
   reloadButton.click();
 
   const [reloadedAddon] = yield onInstalled;
   is(reloadedAddon.name, ADDON_NAME,
      "Add-on was reloaded: " + reloadedAddon.name);
 
   yield onBootstrapInstallCalled;
-
-  info("Uninstall addon installed earlier.");
-  const onUninstalled = promiseAddonEvent("onUninstalled");
-  reloadedAddon.uninstall();
-  const [uninstalledAddon] = yield onUninstalled;
-  is(uninstalledAddon.id, ADDON_ID,
-     "Add-on was uninstalled: " + uninstalledAddon.id);
-
+  yield tearDownAddon(reloadedAddon);
   yield closeAboutDebugging(tab);
 });
 
 add_task(function* reloadButtonRefreshesMetadata() {
   const { tab, document } = yield openAboutDebugging("addons");
   yield waitForInitialAddonList(document);
 
   const manifestBase = {
@@ -148,15 +175,30 @@ add_task(function* reloadButtonRefreshes
   yield onAddonReloaded;
   const [reloadedAddon] = yield onReInstall;
   // Make sure the name was updated correctly.
   const allAddons = [...document.querySelectorAll("#addons .target-name")]
     .map(element => element.textContent);
   const nameWasUpdated = allAddons.some(name => name === newName);
   ok(nameWasUpdated, `New name appeared in reloaded add-ons: ${allAddons}`);
 
-  const onUninstalled = promiseAddonEvent("onUninstalled");
-  reloadedAddon.uninstall();
-  yield onUninstalled;
-
+  yield tearDownAddon(reloadedAddon);
   tempExt.remove();
   yield closeAboutDebugging(tab);
 });
+
+add_task(function* onlyTempInstalledAddonsCanBeReloaded() {
+  const { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
+  const onAddonListUpdated = waitForMutation(getAddonList(document),
+                                             { childList: true });
+  yield installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
+  yield onAddonListUpdated;
+  const addon = yield getAddonByID("bug1273184@tests");
+
+  const reloadButton = getReloadButton(document, addon.name);
+  ok(reloadButton, "Reload button exists");
+  is(reloadButton.disabled, true, "Reload button should be disabled");
+  ok(reloadButton.title, "Disabled reload button should have a tooltip");
+
+  yield tearDownAddon(addon);
+  yield closeAboutDebugging(tab);
+});
--- a/devtools/client/locales/en-US/aboutdebugging.properties
+++ b/devtools/client/locales/en-US/aboutdebugging.properties
@@ -12,16 +12,17 @@ unregister = unregister
 addons = Add-ons
 addonDebugging.label = Enable add-on debugging
 addonDebugging.tooltip = Turning this on will allow you to debug add-ons and various other parts of the browser chrome
 addonDebugging.moreInfo = more info
 loadTemporaryAddon = Load Temporary Add-on
 extensions = Extensions
 selectAddonFromFile2 = Select Manifest File or Package (.xpi)
 reload = Reload
+reloadDisabledTooltip = Only temporarily installed add-ons can be reloaded
 
 workers = Workers
 serviceWorkers = Service Workers
 sharedWorkers = Shared Workers
 otherWorkers = Other Workers
 
 tabs = Tabs
 
--- a/devtools/server/actors/addon.js
+++ b/devtools/server/actors/addon.js
@@ -80,16 +80,17 @@ BrowserAddonActor.prototype = {
 
     return {
       actor: this.actorID,
       id: this.id,
       name: this._addon.name,
       url: this.url,
       iconURL: this._addon.iconURL,
       debuggable: this._addon.isDebuggable,
+      temporarilyInstalled: this._addon.temporarilyInstalled,
       consoleActor: this._consoleActor.actorID,
 
       traits: {
         highlightable: false,
         networkMonitor: false,
       },
     };
   },