Bug 1308261 Add hash to options for mozAddonManager.createInstall() r?kmag draft
authorAndrew Swan <aswan@mozilla.com>
Thu, 06 Oct 2016 17:50:55 -0700
changeset 422572 e92a34df1bcc53d35d9c90f0a255d47a0b5c11ae
parent 420680 42c95d88aaaa7c2eca1d278399421d437441ac4d
child 533310 3601d67b4ea74fa40837e665002940e1a523da3d
push id31745
push useraswan@mozilla.com
push dateFri, 07 Oct 2016 21:20:21 +0000
reviewerskmag
bugs1308261
milestone52.0a1
Bug 1308261 Add hash to options for mozAddonManager.createInstall() r?kmag MozReview-Commit-ID: Dx4Giy5k26c
dom/webidl/AddonManager.webidl
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
toolkit/mozapps/extensions/test/browser/webapi_checkavailable.html
--- a/dom/webidl/AddonManager.webidl
+++ b/dom/webidl/AddonManager.webidl
@@ -43,16 +43,20 @@ interface AddonInstall : EventTarget {
   readonly attribute long long maxProgress;
 
   Promise<void> install();
   Promise<void> cancel();
 };
 
 dictionary addonInstallOptions {
   required DOMString url;
+  // If a non-empty string is passed for "hash", it is used to verify the
+  // checksum of the downloaded XPI before installing.  If is omitted or if
+  // it is null or empty string, no checksum verification is performed.
+  DOMString? hash = null;
 };
 
 [HeaderFile="mozilla/AddonManagerWebAPI.h",
  Func="mozilla::AddonManagerWebAPI::IsAPIEnabled",
  NavigatorProperty="mozAddonManager",
  JSImplementation="@mozilla.org/addon-web-api/manager;1"]
 interface AddonManager : EventTarget {
   /**
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -2935,17 +2935,17 @@ var AddonManagerInternal = {
           install.addListener(listener);
 
           this.installs.set(id, {install, target, listener});
 
           let result = {id};
           this.copyProps(install, result);
           resolve(result);
         };
-        AddonManager.getInstallForURL(options.url, newInstall, "application/x-xpinstall");
+        AddonManager.getInstallForURL(options.url, newInstall, "application/x-xpinstall", options.hash);
       });
     },
 
     addonUninstall(target, id) {
       return new Promise(resolve => {
         AddonManager.getAddonByID(id, addon => {
           if (!addon) {
             resolve(false);
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
@@ -1,10 +1,11 @@
 const TESTPAGE = `${SECURE_TESTROOT}webapi_checkavailable.html`;
 const XPI_URL = `${SECURE_TESTROOT}addons/browser_webapi_install.xpi`;
+const XPI_SHA = "sha256:d4bab17ff9ba5f635e97c84021f4c527c502250d62ab7f6e6c9e8ee28822f772";
 
 const ID = "webapi_install@tests.mozilla.org";
 // eh, would be good to just stat the real file instead of this...
 const XPI_LEN = 4782;
 
 function waitForClear() {
   const MSG = "WebAPICleanup";
   return new Promise(resolve => {
@@ -31,20 +32,20 @@ add_task(function* setup() {
 
 // Wrapper around a common task to run in the content process to test
 // the mozAddonManager API.  Takes a URL for the XPI to install and an
 // array of steps, each of which can either be an action to take
 // (i.e., start or cancel the install) or an install event to wait for.
 // Steps that look for a specific event may also include a "props" property
 // with properties that the AddonInstall object is expected to have when
 // that event is triggered.
-function* testInstall(browser, url, steps, description) {
-  let success = yield ContentTask.spawn(browser, {url, steps}, function* (opts) {
-    let { url, steps } = opts;
-    let install = yield content.navigator.mozAddonManager.createInstall({url});
+function* testInstall(browser, args, steps, description) {
+  let success = yield ContentTask.spawn(browser, {args, steps}, function* (opts) {
+    let { args, steps } = opts;
+    let install = yield content.navigator.mozAddonManager.createInstall(args);
     if (!install) {
       yield Promise.reject("createInstall() did not return an install object");
     }
 
     // Check that the initial state of the AddonInstall is sane.
     if (install.state != "STATE_AVAILABLE") {
       yield Promise.reject("new install should be in STATE_AVAILABLE");
     }
@@ -152,77 +153,83 @@ function makeInstallTest(task) {
 
     yield BrowserTestUtils.withNewTab(TESTPAGE, task);
 
     yield clearPromise;
     is(AddonManager.webAPI.installs.size, 0, "AddonInstall was cleaned up");
   };
 }
 
-// Check the happy path for installing an add-on using the mozAddonManager API.
-add_task(makeInstallTest(function* (browser) {
-  let steps = [
-    {action: "install"},
-    {
-      event: "onDownloadStarted",
-      props: {state: "STATE_DOWNLOADING"},
-    },
-    {
-      event: "onDownloadProgress",
-      props: {maxProgress: XPI_LEN},
-    },
-    {
-      event: "onDownloadEnded",
-      props: {
-        state: "STATE_DOWNLOADED",
-        progress: XPI_LEN,
-        maxProgress: XPI_LEN,
+function makeRegularTest(options, what) {
+  return makeInstallTest(function* (browser) {
+    let steps = [
+      {action: "install"},
+      {
+        event: "onDownloadStarted",
+        props: {state: "STATE_DOWNLOADING"},
+      },
+      {
+        event: "onDownloadProgress",
+        props: {maxProgress: XPI_LEN},
+      },
+      {
+        event: "onDownloadEnded",
+        props: {
+          state: "STATE_DOWNLOADED",
+          progress: XPI_LEN,
+          maxProgress: XPI_LEN,
+        },
+      },
+      {
+        event: "onInstallStarted",
+        props: {state: "STATE_INSTALLING"},
       },
-    },
-    {
-      event: "onInstallStarted",
-      props: {state: "STATE_INSTALLING"},
-    },
-    {
-      event: "onInstallEnded",
-      props: {state: "STATE_INSTALLED"},
-    },
-  ];
+      {
+        event: "onInstallEnded",
+        props: {state: "STATE_INSTALLED"},
+      },
+    ];
 
-  yield testInstall(browser, XPI_URL, steps, "a basic install works");
+    yield testInstall(browser, options, steps, what);
+
+    let version = Services.prefs.getIntPref("webapitest.active_version");
+    is(version, 1, "the install really did work");
+
+    // Sanity check to ensure that the test in makeInstallTest() that
+    // installs.size == 0 means we actually did clean up.
+    ok(AddonManager.webAPI.installs.size > 0, "webAPI is tracking the AddonInstall");
 
-  let version = Services.prefs.getIntPref("webapitest.active_version");
-  is(version, 1, "the install really did work");
+    let addons = yield promiseAddonsByIDs([ID]);
+    isnot(addons[0], null, "Found the addon");
 
-  // Sanity check to ensure that the test in makeInstallTest() that
-  // installs.size == 0 means we actually did clean up.
-  ok(AddonManager.webAPI.installs.size > 0, "webAPI is tracking the AddonInstall");
+    yield addons[0].uninstall();
 
-  let addons = yield promiseAddonsByIDs([ID]);
-  isnot(addons[0], null, "Found the addon");
+    addons = yield promiseAddonsByIDs([ID]);
+    is(addons[0], null, "Addon was uninstalled");
+  });
+}
 
-  yield addons[0].uninstall();
-
-  addons = yield promiseAddonsByIDs([ID]);
-  is(addons[0], null, "Addon was uninstalled");
-}));
+add_task(makeRegularTest({url: XPI_URL}, "a basic install works"));
+add_task(makeRegularTest({url: XPI_URL, hash: null}, "install with hash=null works"));
+add_task(makeRegularTest({url: XPI_URL, hash: ""}, "install with empty string for hash works"));
+add_task(makeRegularTest({url: XPI_URL, hash: XPI_SHA}, "install with hash works"));
 
 add_task(makeInstallTest(function* (browser) {
   let steps = [
     {action: "cancel"},
     {
       event: "onDownloadCancelled",
       props: {
         state: "STATE_CANCELLED",
         error: null,
       },
     }
   ];
 
-  yield testInstall(browser, XPI_URL, steps, "canceling an install works");
+  yield testInstall(browser, {url: XPI_URL}, steps, "canceling an install works");
 
   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");
 }));
 
 add_task(makeInstallTest(function* (browser) {
@@ -237,17 +244,42 @@ add_task(makeInstallTest(function* (brow
       event: "onDownloadFailed",
       props: {
         state: "STATE_DOWNLOAD_FAILED",
         error: "ERROR_NETWORK_FAILURE",
       },
     }
   ];
 
-  yield testInstall(browser, XPI_URL + "bogus", steps, "install of a bad url fails");
+  yield testInstall(browser, {url: 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");
+}));
+
+add_task(makeInstallTest(function* (browser) {
+  let steps = [
+    {action: "install"},
+    {
+      event: "onDownloadStarted",
+      props: {state: "STATE_DOWNLOADING"},
+    },
+    {event: "onDownloadProgress"},
+    {
+      event: "onDownloadFailed",
+      props: {
+        state: "STATE_DOWNLOAD_FAILED",
+        error: "ERROR_INCORRECT_HASH",
+      },
+    }
+  ];
+
+  yield testInstall(browser, {url: XPI_URL, hash: "sha256:bogus"}, steps, "install with bad hash 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");
 }));
 
 add_task(function* test_permissions() {
--- a/toolkit/mozapps/extensions/test/browser/webapi_checkavailable.html
+++ b/toolkit/mozapps/extensions/test/browser/webapi_checkavailable.html
@@ -1,10 +1,13 @@
 <!DOCTYPE html>
 
 <html>
+<head>
+  <meta charset="utf-8">
+</head>
 <body>
 <p id="result"></p>
 <script type="text/javascript">
 document.getElementById("result").textContent = ("mozAddonManager" in window.navigator);
 </script>
 </body>
 </html>