Bug 1323129 part 1: Remove support for installing multiple xpis in InstallTrigger r=rhelmer
authorAndrew Swan <aswan@mozilla.com>
Wed, 04 Jan 2017 10:12:53 -0800
changeset 328093 d97b2eeffc46de2ef3f9312dd60e79cce68f5208
parent 328092 b14520ebf7cc599f73adba54b3d74e8b61ebf213
child 328094 308b6c4b0e8ff09723cc87229faddd3a0ed35441
push id85390
push userkwierso@gmail.com
push dateFri, 06 Jan 2017 01:32:55 +0000
treeherdermozilla-inbound@5ae3e55675da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrhelmer
bugs1323129
milestone53.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 1323129 part 1: Remove support for installing multiple xpis in InstallTrigger r=rhelmer MozReview-Commit-ID: 567cHYjbjO4
browser/base/content/test/general/browser_bug553455.js
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/addonManager.js
toolkit/mozapps/extensions/amContentHandler.js
toolkit/mozapps/extensions/amIWebInstaller.idl
toolkit/mozapps/extensions/amInstallTrigger.js
toolkit/mozapps/extensions/moz.build
toolkit/mozapps/extensions/test/xpinstall/amosigned2.xpi
toolkit/mozapps/extensions/test/xpinstall/browser.ini
toolkit/mozapps/extensions/test/xpinstall/browser_cancel.js
toolkit/mozapps/extensions/test/xpinstall/browser_httphash6.js
toolkit/mozapps/extensions/test/xpinstall/browser_signed_multiple.js
toolkit/mozapps/extensions/test/xpinstall/browser_signed_naming.js
toolkit/mozapps/extensions/test/xpinstall/browser_signed_no_cn.js
toolkit/mozapps/extensions/test/xpinstall/browser_signed_no_o.js
toolkit/mozapps/extensions/test/xpinstall/signed2.xpi
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -465,56 +465,16 @@ function test_restartless() {
     Services.perms.remove(makeURI("http://example.com/"), "install");
 
     let closePromise = waitForNotificationClose();
     gBrowser.removeTab(gBrowser.selectedTab);
     yield closePromise;
   });
 },
 
-function test_multiple() {
-  return Task.spawn(function* () {
-    let pm = Services.perms;
-    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-    let progressPromise = waitForProgressNotification();
-    let dialogPromise = waitForInstallDialog();
-    let triggers = encodeURIComponent(JSON.stringify({
-      "Unsigned XPI": "amosigned.xpi",
-      "Restartless XPI": "restartless.xpi"
-    }));
-    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
-    let panel = yield progressPromise;
-    let installDialog = yield dialogPromise;
-
-    let notificationPromise = waitForNotification("addon-install-restart");
-    acceptInstallDialog(installDialog);
-    yield notificationPromise;
-
-    let notification = panel.childNodes[0];
-    is(notification.button.label, "Restart Now", "Should have seen the right button");
-    is(notification.getAttribute("label"),
-       "2 add-ons will be installed after you restart " + gApp + ".",
-       "Should have seen the right message");
-
-    let installs = yield getInstalls();
-    is(installs.length, 1, "Should be one pending install");
-    installs[0].cancel();
-
-    let addon = yield new Promise(resolve => {
-      AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(result) {
-        resolve(result);
-      });
-    });
-    addon.uninstall();
-    Services.perms.remove(makeURI("http://example.com/"), "install");
-    yield removeTab();
-  });
-},
-
 function test_sequential() {
   return Task.spawn(function* () {
     // This test is only relevant if using the new doorhanger UI
     // TODO: this subtest is disabled until multiple notification prompts are
     // reworked in bug 1188152
     if (true || !Preferences.get("xpinstall.customConfirmationUI", false)) {
       return;
     }
@@ -581,74 +541,16 @@ function test_sequential() {
     Services.perms.remove(makeURI("http://example.com"), "install");
     let closePromise = waitForNotificationClose();
     cancelInstallDialog(installDialog);
     yield closePromise;
     yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   });
 },
 
-function test_someUnverified() {
-  return Task.spawn(function* () {
-    // This test is only relevant if using the new doorhanger UI and allowing
-    // unsigned add-ons
-    if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
-        Preferences.get("xpinstall.signatures.required", true) ||
-        REQUIRE_SIGNING) {
-      return;
-    }
-    let pm = Services.perms;
-    pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-    let progressPromise = waitForProgressNotification();
-    let dialogPromise = waitForInstallDialog();
-    let triggers = encodeURIComponent(JSON.stringify({
-      "Extension XPI": "restartless-unsigned.xpi",
-      "Theme XPI": "theme.xpi"
-    }));
-    BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
-    yield progressPromise;
-    let installDialog = yield dialogPromise;
-
-    let notification = document.getElementById("addon-install-confirmation-notification");
-    let message = notification.getAttribute("label");
-    is(message, "Caution: This site would like to install 2 add-ons in " + gApp +
-       ", some of which are unverified. Proceed at your own risk.",
-       "Should see the right message");
-
-    let container = document.getElementById("addon-install-confirmation-content");
-    is(container.childNodes.length, 2, "Should be two items listed");
-    is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
-    is(container.childNodes[0].lastChild.getAttribute("class"),
-       "addon-install-confirmation-unsigned", "Should have the unverified marker");
-    is(container.childNodes[1].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
-    is(container.childNodes[1].childNodes.length, 1, "Shouldn't have the unverified marker");
-
-    let notificationPromise = waitForNotification("addon-install-restart");
-    acceptInstallDialog(installDialog);
-    yield notificationPromise;
-
-    let [addon, theme] = yield new Promise(resolve => {
-      AddonManager.getAddonsByIDs(["restartless-xpi@tests.mozilla.org",
-                                  "theme-xpi@tests.mozilla.org"],
-                                  function(addons) {
-        resolve(addons);
-      });
-    });
-    addon.uninstall();
-    // Installing a new theme tries to switch to it, switch back to the
-    // default theme.
-    theme.userDisabled = true;
-    theme.uninstall();
-
-    Services.perms.remove(makeURI("http://example.com/"), "install");
-    yield removeTab();
-  });
-},
-
 function test_allUnverified() {
   return Task.spawn(function* () {
     // This test is only relevant if using the new doorhanger UI and allowing
     // unsigned add-ons
     if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
         Preferences.get("xpinstall.signatures.required", true) ||
         REQUIRE_SIGNING) {
       return;
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -361,98 +361,87 @@ function webAPIForAddon(addon) {
 
   return result;
 }
 
 /**
  * Listens for a browser changing origin and cancels the installs that were
  * started by it.
  */
-function BrowserListener(aBrowser, aInstallingPrincipal, aInstalls) {
+function BrowserListener(aBrowser, aInstallingPrincipal, aInstall) {
   this.browser = aBrowser;
   this.principal = aInstallingPrincipal;
-  this.installs = aInstalls;
-  this.installCount = aInstalls.length;
+  this.install = aInstall;
 
   aBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
   Services.obs.addObserver(this, "message-manager-close", true);
 
-  for (let install of this.installs)
-    install.addListener(this);
+  aInstall.addListener(this);
 
   this.registered = true;
 }
 
 BrowserListener.prototype = {
   browser: null,
-  installs: null,
-  installCount: null,
+  install: null,
   registered: false,
 
   unregister() {
     if (!this.registered)
       return;
     this.registered = false;
 
     Services.obs.removeObserver(this, "message-manager-close");
     // The browser may have already been detached
     if (this.browser.removeProgressListener)
       this.browser.removeProgressListener(this);
 
-    for (let install of this.installs)
-      install.removeListener(this);
-    this.installs = null;
+    this.install.removeListener(this);
+    this.install = null;
   },
 
-  cancelInstalls() {
-    for (let install of this.installs) {
-      try {
-        install.cancel();
-      } catch (e) {
-        // Some installs may have already failed or been cancelled, ignore these
-      }
+  cancelInstall() {
+    try {
+      this.install.cancel();
+    } catch (e) {
+      // install may have already failed or been cancelled, ignore these
     }
   },
 
   observe(subject, topic, data) {
     if (subject != this.browser.messageManager)
       return;
 
     // The browser's message manager has closed and so the browser is
-    // going away, cancel all installs
-    this.cancelInstalls();
+    // going away, cancel the install
+    this.cancelInstall();
   },
 
   onLocationChange(webProgress, request, location) {
     if (this.browser.contentPrincipal && this.principal.subsumes(this.browser.contentPrincipal))
       return;
 
-    // The browser has navigated to a new origin so cancel all installs
-    this.cancelInstalls();
+    // The browser has navigated to a new origin so cancel the install
+    this.cancelInstall();
   },
 
   onDownloadCancelled(install) {
-    // Don't need to hear more events from this install
-    install.removeListener(this);
-
-    // Once all installs have ended unregister everything
-    if (--this.installCount == 0)
-      this.unregister();
+    this.unregister();
   },
 
   onDownloadFailed(install) {
-    this.onDownloadCancelled(install);
+    this.unregister();
   },
 
   onInstallFailed(install) {
-    this.onDownloadCancelled(install);
+    this.unregister();
   },
 
   onInstallEnded(install) {
-    this.onDownloadCancelled(install);
+    this.unregister();
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
                                          Ci.nsIWebProgressListener,
                                          Ci.nsIObserver])
 };
 
 /**
@@ -2021,54 +2010,49 @@ var AddonManagerInternal = {
       if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
           callProvider(provider, "isInstallAllowed", null, aInstallingPrincipal))
         return true;
     }
     return false;
   },
 
   /**
-   * Starts installation of an array of AddonInstalls notifying the registered
+   * Starts installation of an AddonInstall notifying the registered
    * web install listener of blocked or started installs.
    *
    * @param  aMimetype
    *         The mimetype of add-ons being installed
    * @param  aBrowser
    *         The optional browser element that started the installs
    * @param  aInstallingPrincipal
    *         The nsIPrincipal that initiated the install
-   * @param  aInstalls
-   *         The array of AddonInstalls to be installed
+   * @param  aInstall
+   *         The AddonInstall to be installed
    */
-  installAddonsFromWebpage(aMimetype, aBrowser,
-                                     aInstallingPrincipal, aInstalls) {
+  installAddonFromWebpage(aMimetype, aBrowser,
+                                    aInstallingPrincipal, aInstall) {
     if (!gStarted)
       throw Components.Exception("AddonManager is not initialized",
                                  Cr.NS_ERROR_NOT_INITIALIZED);
 
     if (!aMimetype || typeof aMimetype != "string")
       throw Components.Exception("aMimetype must be a non-empty string",
                                  Cr.NS_ERROR_INVALID_ARG);
 
     if (aBrowser && !(aBrowser instanceof Ci.nsIDOMElement))
       throw Components.Exception("aSource must be a nsIDOMElement, or null",
                                  Cr.NS_ERROR_INVALID_ARG);
 
     if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal))
       throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
                                  Cr.NS_ERROR_INVALID_ARG);
 
-    if (!Array.isArray(aInstalls))
-      throw Components.Exception("aInstalls must be an array",
-                                 Cr.NS_ERROR_INVALID_ARG);
-
     if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) {
-      logger.warn("No web installer available, cancelling all installs");
-      for (let install of aInstalls)
-        install.cancel();
+      logger.warn("No web installer available, cancelling install");
+      aInstall.cancel();
       return;
     }
 
     // When a chrome in-content UI has loaded a <browser> inside to host a
     // website we want to do our security checks on the inner-browser but
     // notify front-end that install events came from the outer-browser (the
     // main tab's browser). Check this by seeing if the browser we've been
     // passed is in a content type docshell and if so get the outer-browser.
@@ -2080,56 +2064,51 @@ var AddonManagerInternal = {
     if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent)
       topBrowser = docShell.chromeEventHandler;
 
     try {
       let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
                         getService(Ci.amIWebInstallListener);
 
       if (!this.isInstallEnabled(aMimetype)) {
-        for (let install of aInstalls)
-          install.cancel();
+        aInstall.cancel();
 
         weblistener.onWebInstallDisabled(topBrowser, aInstallingPrincipal.URI,
-                                         aInstalls, aInstalls.length);
+                                         [aInstall], 1);
         return;
       } else if (!aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) {
-        for (let install of aInstalls)
-          install.cancel();
+        aInstall.cancel();
 
         if (weblistener instanceof Ci.amIWebInstallListener2) {
           weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI,
-                                                aInstalls, aInstalls.length);
+                                                [aInstall], 1);
         }
         return;
       }
 
-      // The installs may start now depending on the web install listener,
+      // The install may start now depending on the web install listener,
       // listen for the browser navigating to a new origin and cancel the
-      // installs in that case.
-      new BrowserListener(aBrowser, aInstallingPrincipal, aInstalls);
+      // install in that case.
+      new BrowserListener(aBrowser, aInstallingPrincipal, aInstall);
 
       if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
         if (weblistener.onWebInstallBlocked(topBrowser, aInstallingPrincipal.URI,
-                                            aInstalls, aInstalls.length)) {
-          for (let install of aInstalls)
-            install.install();
+                                            [aInstall], 1)) {
+          aInstall.install();
         }
       } else if (weblistener.onWebInstallRequested(topBrowser, aInstallingPrincipal.URI,
-                                                 aInstalls, aInstalls.length)) {
-        for (let install of aInstalls)
-          install.install();
+                                                 [aInstall], 1)) {
+        aInstall.install();
       }
     } catch (e) {
       // In the event that the weblistener throws during instantiation or when
-      // calling onWebInstallBlocked or onWebInstallRequested all of the
-      // installs should get cancelled.
+      // calling onWebInstallBlocked or onWebInstallRequested the
+      // install should get cancelled.
       logger.warn("Failure calling web installer", e);
-      for (let install of aInstalls)
-        install.cancel();
+      aInstall.cancel();
     }
   },
 
   /**
    * Adds a new InstallListener if the listener is not already registered.
    *
    * @param  aListener
    *         The InstallListener to add
@@ -3419,21 +3398,20 @@ this.AddonManager = {
   isInstallEnabled(aType) {
     return AddonManagerInternal.isInstallEnabled(aType);
   },
 
   isInstallAllowed(aType, aInstallingPrincipal) {
     return AddonManagerInternal.isInstallAllowed(aType, aInstallingPrincipal);
   },
 
-  installAddonsFromWebpage(aType, aBrowser, aInstallingPrincipal,
-                                     aInstalls) {
-    AddonManagerInternal.installAddonsFromWebpage(aType, aBrowser,
-                                                  aInstallingPrincipal,
-                                                  aInstalls);
+  installAddonFromWebpage(aType, aBrowser, aInstallingPrincipal, aInstall) {
+    AddonManagerInternal.installAddonFromWebpage(aType, aBrowser,
+                                                 aInstallingPrincipal,
+                                                 aInstall);
   },
 
   installTemporaryAddon(aDirectory) {
     return AddonManagerInternal.installTemporaryAddon(aDirectory);
   },
 
   installAddonFromSources(aDirectory) {
     return AddonManagerInternal.installAddonFromSources(aDirectory);
--- a/toolkit/mozapps/extensions/addonManager.js
+++ b/toolkit/mozapps/extensions/addonManager.js
@@ -16,17 +16,17 @@ const { classes: Cc, interfaces: Ci, uti
 const EXECUTION_ERROR   = -203;
 const CANT_READ_ARCHIVE = -207;
 const USER_CANCELLED    = -210;
 const DOWNLOAD_ERROR    = -228;
 const UNSUPPORTED_TYPE  = -244;
 const SUCCESS           = 0;
 
 const MSG_INSTALL_ENABLED  = "WebInstallerIsInstallEnabled";
-const MSG_INSTALL_ADDONS   = "WebInstallerInstallAddonsFromWebpage";
+const MSG_INSTALL_ADDON    = "WebInstallerInstallAddonFromWebpage";
 const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
 
 const MSG_PROMISE_REQUEST  = "WebAPIPromiseRequest";
 const MSG_PROMISE_RESULT   = "WebAPIPromiseResult";
 const MSG_INSTALL_EVENT    = "WebAPIInstallEvent";
 const MSG_INSTALL_CLEANUP  = "WebAPICleanup";
 const MSG_ADDON_EVENT_REQ  = "WebAPIAddonEventRequest";
 const MSG_ADDON_EVENT      = "WebAPIAddonEvent";
@@ -39,17 +39,17 @@ Cu.import("resource://gre/modules/Servic
 var gSingleton = null;
 
 function amManager() {
   Cu.import("resource://gre/modules/AddonManager.jsm");
   /* globals AddonManagerPrivate*/
 
   Services.mm.loadFrameScript(CHILD_SCRIPT, true);
   Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this);
-  Services.mm.addMessageListener(MSG_INSTALL_ADDONS, this);
+  Services.mm.addMessageListener(MSG_INSTALL_ADDON, this);
   Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this);
   Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this);
   Services.mm.addMessageListener(MSG_ADDON_EVENT_REQ, this);
 
   Services.obs.addObserver(this, "message-manager-close", false);
   Services.obs.addObserver(this, "message-manager-disconnect", false);
 
   AddonManager.webAPI.setEventHandler(this.sendEvent);
@@ -75,84 +75,63 @@ amManager.prototype = {
   /**
    * @see amIAddonManager.idl
    */
   mapURIToAddonID(uri, id) {
     id.value = AddonManager.mapURIToAddonID(uri);
     return !!id.value;
   },
 
-  /**
-   * @see amIWebInstaller.idl
-   */
-  isInstallEnabled(aMimetype, aReferer) {
-    return AddonManager.isInstallEnabled(aMimetype);
-  },
-
-  /**
-   * @see amIWebInstaller.idl
-   */
-  installAddonsFromWebpage(aMimetype, aBrowser, aInstallingPrincipal,
-                                     aUris, aHashes, aNames, aIcons, aCallback) {
-    if (aUris.length == 0)
-      return false;
-
+  installAddonFromWebpage(aMimetype, aBrowser, aInstallingPrincipal,
+                                    aUri, aHash, aName, aIcon, aCallback) {
     let retval = true;
     if (!AddonManager.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
       aCallback = null;
       retval = false;
     }
 
-    let installs = [];
-    function buildNextInstall() {
-      if (aUris.length == 0) {
-        AddonManager.installAddonsFromWebpage(aMimetype, aBrowser, aInstallingPrincipal, installs);
+    AddonManager.getInstallForURL(aUri, function(aInstall) {
+      function callCallback(uri, status) {
+        try {
+          aCallback.onInstallEnded(uri, status);
+        } catch (e) {
+          Components.utils.reportError(e);
+        }
+      }
+
+      if (!aInstall) {
+        aCallback.onInstallEnded(aUri, UNSUPPORTED_TYPE);
         return;
       }
-      let uri = aUris.shift();
-      AddonManager.getInstallForURL(uri, function(aInstall) {
-        function callCallback(aUri, aStatus) {
-          try {
-            aCallback.onInstallEnded(aUri, aStatus);
-          } catch (e) {
-            Components.utils.reportError(e);
-          }
-        }
 
-        if (aInstall) {
-          installs.push(aInstall);
-          if (aCallback) {
-            aInstall.addListener({
-              onDownloadCancelled(aInstall) {
-                callCallback(uri, USER_CANCELLED);
-              },
+      if (aCallback) {
+        aInstall.addListener({
+          onDownloadCancelled(aInstall) {
+            callCallback(aUri, USER_CANCELLED);
+          },
 
-              onDownloadFailed(aInstall) {
-                if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE)
-                  callCallback(uri, CANT_READ_ARCHIVE);
-                else
-                  callCallback(uri, DOWNLOAD_ERROR);
-              },
-
-              onInstallFailed(aInstall) {
-                callCallback(uri, EXECUTION_ERROR);
-              },
+          onDownloadFailed(aInstall) {
+            if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE)
+              callCallback(aUri, CANT_READ_ARCHIVE);
+            else
+              callCallback(aUri, DOWNLOAD_ERROR);
+          },
 
-              onInstallEnded(aInstall, aStatus) {
-                callCallback(uri, SUCCESS);
-              }
-            });
+          onInstallFailed(aInstall) {
+            callCallback(aUri, EXECUTION_ERROR);
+          },
+
+          onInstallEnded(aInstall, aStatus) {
+            callCallback(aUri, SUCCESS);
           }
-        } else if (aCallback) {
-          aCallback.onInstallEnded(uri, UNSUPPORTED_TYPE);
-        }
-        buildNextInstall();
-      }, aMimetype, aHashes.shift(), aNames.shift(), aIcons.shift(), null, aBrowser);
-    }
-    buildNextInstall();
+        });
+      }
+
+      AddonManager.installAddonFromWebpage(aMimetype, aBrowser, aInstallingPrincipal, aInstall);
+    }, aMimetype, aHash, aName, aIcon, null, aBrowser);
 
     return retval;
   },
 
   notify(aTimer) {
     AddonManagerPrivate.backgroundUpdateTimerHandler();
   },
 
@@ -196,34 +175,34 @@ amManager.prototype = {
    */
   receiveMessage(aMessage) {
     let payload = aMessage.data;
 
     switch (aMessage.name) {
       case MSG_INSTALL_ENABLED:
         return AddonManager.isInstallEnabled(payload.mimetype);
 
-      case MSG_INSTALL_ADDONS: {
+      case MSG_INSTALL_ADDON: {
         let callback = null;
         if (payload.callbackID != -1) {
           let mm = aMessage.target.messageManager;
           callback = {
             onInstallEnded(url, status) {
               mm.sendAsyncMessage(MSG_INSTALL_CALLBACK, {
                 callbackID: payload.callbackID,
                 url,
                 status
               });
             },
           };
         }
 
-        return this.installAddonsFromWebpage(payload.mimetype,
-          aMessage.target, payload.principalToInherit, payload.uris,
-          payload.hashes, payload.names, payload.icons, callback);
+        return this.installAddonFromWebpage(payload.mimetype,
+          aMessage.target, payload.principalToInherit, payload.uri,
+          payload.hash, payload.name, payload.icon, callback);
       }
 
       case MSG_PROMISE_REQUEST: {
         let mm = aMessage.target.messageManager;
         let resolve = (value) => {
           mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
             callbackID: payload.callbackID,
             resolve: value
@@ -279,15 +258,14 @@ amManager.prototype = {
                                    Cr.NS_ERROR_NO_AGGREGATION);
 
       if (!gSingleton)
         gSingleton = new amManager();
       return gSingleton.QueryInterface(aIid);
     }
   },
   QueryInterface: XPCOMUtils.generateQI([Ci.amIAddonManager,
-                                         Ci.amIWebInstaller,
                                          Ci.nsITimerCallback,
                                          Ci.nsIObserver,
                                          Ci.nsIMessageListener])
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amManager]);
--- a/toolkit/mozapps/extensions/amContentHandler.js
+++ b/toolkit/mozapps/extensions/amContentHandler.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 const XPI_CONTENT_TYPE = "application/x-xpinstall";
-const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
+const MSG_INSTALL_ADDON = "WebInstallerInstallAddonFromWebpage";
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function amContentHandler() {
 }
 
 amContentHandler.prototype = {
@@ -46,21 +46,21 @@ amContentHandler.prototype = {
 
     aRequest.cancel(Cr.NS_BINDING_ABORTED);
 
     let principalToInherit = aRequest.loadInfo.principalToInherit;
     if (!principalToInherit) {
       principalToInherit = aRequest.loadInfo.triggeringPrincipal;
     }
 
-    let installs = {
-      uris: [uri.spec],
-      hashes: [null],
-      names: [null],
-      icons: [null],
+    let install = {
+      uri: uri.spec,
+      hash: null,
+      name: null,
+      icon: null,
       mimetype: XPI_CONTENT_TYPE,
       principalToInherit,
       callbackID: -1
     };
 
     if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
       // When running in the main process this might be a frame inside an
       // in-content UI page, walk up to find the first frame element in a chrome
@@ -69,31 +69,31 @@ amContentHandler.prototype = {
       let ssm = Services.scriptSecurityManager;
       while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal))
         element = element.ownerDocument.defaultView.frameElement;
 
       if (element) {
         let listener = Cc["@mozilla.org/addons/integration;1"].
                        getService(Ci.nsIMessageListener);
         listener.wrappedJSObject.receiveMessage({
-          name: MSG_INSTALL_ADDONS,
+          name: MSG_INSTALL_ADDON,
           target: element,
-          data: installs,
+          data: install
         });
         return;
       }
     }
 
     // Fall back to sending through the message manager
     let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIDocShell)
                                .QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIContentFrameMessageManager);
 
-    messageManager.sendAsyncMessage(MSG_INSTALL_ADDONS, installs);
+    messageManager.sendAsyncMessage(MSG_INSTALL_ADDON, install);
   },
 
   classID: Components.ID("{7beb3ba8-6ec3-41b4-b67c-da89b8518922}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler]),
 
   log(aMsg) {
     let msg = "amContentHandler.js: " + (aMsg.join ? aMsg.join("") : aMsg);
     Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
deleted file mode 100644
--- a/toolkit/mozapps/extensions/amIWebInstaller.idl
+++ /dev/null
@@ -1,82 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-interface nsIDOMElement;
-interface nsIVariant;
-interface nsIURI;
-
-/**
- * A callback function used to notify webpages when a requested install has
- * ended.
- *
- * NOTE: This is *not* the same as InstallListener.
- */
-[scriptable, function, uuid(bb22f5c0-3ca1-48f6-873c-54e87987700f)]
-interface amIInstallCallback : nsISupports
-{
-  /**
-   * Called when an install completes or fails.
-   *
-   * @param  aUrl
-   *         The url of the add-on being installed
-   * @param  aStatus
-   *         0 if the install was successful or negative if not
-   */
-  void onInstallEnded(in AString aUrl, in int32_t aStatus);
-};
-
-
-/**
- * This interface is used to allow webpages to start installing add-ons.
- */
-[scriptable, uuid(658d6c09-15e0-4688-bee8-8551030472a9)]
-interface amIWebInstaller : nsISupports
-{
-  /**
-   * Checks if installation is enabled for a webpage.
-   *
-   * @param  aMimetype
-   *         The mimetype for the add-on to be installed
-   * @param  referer
-   *         The URL of the webpage trying to install an add-on
-   * @return true if installation is enabled
-   */
-  boolean isInstallEnabled(in AString aMimetype, in nsIURI aReferer);
-
-  /**
-   * Installs an array of add-ons at the request of a webpage
-   *
-   * @param  aMimetype
-   *         The mimetype for the add-ons
-   * @param  aBrowser
-   *         The browser installing the add-ons.
-   * @param  aReferer
-   *         The URI for the webpage installing the add-ons
-   * @param  aUris
-   *         The URIs of add-ons to be installed
-   * @param  aHashes
-   *         The hashes for the add-ons to be installed
-   * @param  aNames
-   *         The names for the add-ons to be installed
-   * @param  aIcons
-   *         The icons for the add-ons to be installed
-   * @param  aCallback
-   *         An optional callback to notify about installation success and
-   *         failure
-   * @param  aInstallCount
-   *         An optional argument including the number of add-ons to install
-   * @return true if the installation was successfully started
-   */
-  boolean installAddonsFromWebpage(in AString aMimetype,
-                                   in nsIDOMElement aBrowser,
-                                   in nsIURI aReferer,
-                                   [array, size_is(aInstallCount)] in wstring aUris,
-                                   [array, size_is(aInstallCount)] in wstring aHashes,
-                                   [array, size_is(aInstallCount)] in wstring aNames,
-                                   [array, size_is(aInstallCount)] in wstring aIcons,
-                                   [optional] in amIInstallCallback aCallback,
-                                   [optional] in uint32_t aInstallCount);
-};
--- a/toolkit/mozapps/extensions/amInstallTrigger.js
+++ b/toolkit/mozapps/extensions/amInstallTrigger.js
@@ -9,37 +9,34 @@ const {classes: Cc, interfaces: Ci, util
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 
 const XPINSTALL_MIMETYPE   = "application/x-xpinstall";
 
 const MSG_INSTALL_ENABLED  = "WebInstallerIsInstallEnabled";
-const MSG_INSTALL_ADDONS   = "WebInstallerInstallAddonsFromWebpage";
+const MSG_INSTALL_ADDON    = "WebInstallerInstallAddonFromWebpage";
 const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
 
 
 var log = Log.repository.getLogger("AddonManager.InstallTrigger");
 log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"];
 
-function CallbackObject(id, callback, urls, mediator) {
+function CallbackObject(id, callback, mediator) {
   this.id = id;
   this.callback = callback;
-  this.urls = new Set(urls);
   this.callCallback = function(url, status) {
     try {
       this.callback(url, status);
     } catch (e) {
       log.warn("InstallTrigger callback threw an exception: " + e);
     }
 
-    this.urls.delete(url);
-    if (this.urls.size == 0)
-      mediator._callbacks.delete(id);
+    mediator._callbacks.delete(id);
   };
 }
 
 function RemoteMediator(window) {
   window.QueryInterface(Ci.nsIInterfaceRequestor);
   let utils = window.getInterface(Ci.nsIDOMWindowUtils);
   this._windowID = utils.currentInnerWindowID;
 
@@ -66,59 +63,59 @@ RemoteMediator.prototype = {
 
   enabled(url) {
     let params = {
       mimetype: XPINSTALL_MIMETYPE
     };
     return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0];
   },
 
-  install(installs, principal, callback, window) {
-    let callbackID = this._addCallback(callback, installs.uris);
+  install(install, principal, callback, window) {
+    let callbackID = this._addCallback(callback);
 
-    installs.mimetype = XPINSTALL_MIMETYPE;
-    installs.principalToInherit = principal;
-    installs.callbackID = callbackID;
+    install.mimetype = XPINSTALL_MIMETYPE;
+    install.principalToInherit = principal;
+    install.callbackID = callbackID;
 
     if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
       // When running in the main process this might be a frame inside an
       // in-content UI page, walk up to find the first frame element in a chrome
       // privileged document
       let element = window.frameElement;
       let ssm = Services.scriptSecurityManager;
       while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal))
         element = element.ownerDocument.defaultView.frameElement;
 
       if (element) {
         let listener = Cc["@mozilla.org/addons/integration;1"].
                        getService(Ci.nsIMessageListener);
         return listener.wrappedJSObject.receiveMessage({
-          name: MSG_INSTALL_ADDONS,
+          name: MSG_INSTALL_ADDON,
           target: element,
-          data: installs,
+          data: install,
         });
       }
     }
 
     // Fall back to sending through the message manager
     let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIWebNavigation)
                                .QueryInterface(Ci.nsIDocShell)
                                .QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIContentFrameMessageManager);
 
-    return messageManager.sendSyncMessage(MSG_INSTALL_ADDONS, installs)[0];
+    return messageManager.sendSyncMessage(MSG_INSTALL_ADDON, install)[0];
   },
 
-  _addCallback(callback, urls) {
+  _addCallback(callback) {
     if (!callback || typeof callback != "function")
       return -1;
 
     let callbackID = this._windowID + "-" + ++this._lastCallbackID;
-    let callbackObject = new CallbackObject(callbackID, callback, urls, this);
+    let callbackObject = new CallbackObject(callbackID, callback, this);
     this._callbacks.set(callbackID, callbackObject);
     return callbackID;
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference])
 };
 
 
@@ -156,50 +153,49 @@ InstallTrigger.prototype = {
     return this._mediator.enabled(this._url.spec);
   },
 
   updateEnabled() {
     return this.enabled();
   },
 
   install(installs, callback) {
-    let installData = {
-      uris: [],
-      hashes: [],
-      names: [],
-      icons: [],
-    };
+    let keys = Object.keys(installs);
+    if (keys.length > 1) {
+      throw new this._window.Error("Only one XPI may be installed at a time");
+    }
+
+    let item = installs[keys[0]];
 
-    for (let name of Object.keys(installs)) {
-      let item = installs[name];
-      if (typeof item === "string") {
-        item = { URL: item };
-      }
-      if (!item.URL) {
-        throw new this._window.Error("Missing URL property for '" + name + "'");
-      }
+    if (typeof item === "string") {
+      item = { URL: item };
+    }
+    if (!item.URL) {
+      throw new this._window.Error("Missing URL property for '" + name + "'");
+    }
 
-      let url = this._resolveURL(item.URL);
-      if (!this._checkLoadURIFromScript(url)) {
-        throw new this._window.Error("Insufficient permissions to install: " + url.spec);
-      }
+    let url = this._resolveURL(item.URL);
+    if (!this._checkLoadURIFromScript(url)) {
+      throw new this._window.Error("Insufficient permissions to install: " + url.spec);
+    }
 
-      let iconUrl = null;
-      if (item.IconURL) {
-        iconUrl = this._resolveURL(item.IconURL);
-        if (!this._checkLoadURIFromScript(iconUrl)) {
-          iconUrl = null; // If page can't load the icon, just ignore it
-        }
+    let iconUrl = null;
+    if (item.IconURL) {
+      iconUrl = this._resolveURL(item.IconURL);
+      if (!this._checkLoadURIFromScript(iconUrl)) {
+        iconUrl = null; // If page can't load the icon, just ignore it
       }
+    }
 
-      installData.uris.push(url.spec);
-      installData.hashes.push(item.Hash || null);
-      installData.names.push(name);
-      installData.icons.push(iconUrl ? iconUrl.spec : null);
-    }
+    let installData = {
+      uri: url.spec,
+      hash: item.Hash || null,
+      name: item.name,
+      icon: iconUrl ? iconUrl.spec : null,
+    };
 
     return this._mediator.install(installData, this._principal, callback, this._window);
   },
 
   startSoftwareUpdate(url, flags) {
     let filename = Services.io.newURI(url, null, null)
                               .QueryInterface(Ci.nsIURL)
                               .filename;
--- a/toolkit/mozapps/extensions/moz.build
+++ b/toolkit/mozapps/extensions/moz.build
@@ -10,17 +10,16 @@ if CONFIG['MOZ_BUILD_APP'] == 'mobile/an
     DEFINES['MOZ_FENNEC'] = True
 
 DIRS += ['internal']
 TEST_DIRS += ['test']
 
 XPIDL_SOURCES += [
     'amIAddonManager.idl',
     'amIAddonPathService.idl',
-    'amIWebInstaller.idl',
     'amIWebInstallListener.idl',
 ]
 
 XPIDL_MODULE = 'extensions'
 
 EXTRA_COMPONENTS += [
     'addonManager.js',
     'amContentHandler.js',
deleted file mode 100644
index 74e877f26fe36641a92e66d3c5960e7f52e39e4c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/toolkit/mozapps/extensions/test/xpinstall/browser.ini
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser.ini
@@ -1,12 +1,11 @@
 [DEFAULT]
 support-files =
   amosigned.xpi
-  amosigned2.xpi
   authRedirect.sjs
   bug540558.html
   bug638292.html
   bug645699.html
   concurrent_installs.html
   cookieRedirect.sjs
   corrupt.xpi
   empty.xpi
@@ -21,17 +20,16 @@ support-files =
   redirect.sjs
   restartless.xpi
   restartless-unsigned.xpi
   signed-no-cn.xpi
   signed-no-o.xpi
   signed-tampered.xpi
   signed-untrusted.xpi
   signed.xpi
-  signed2.xpi
   slowinstall.sjs
   startsoftwareupdate.html
   theme.xpi
   triggerredirect.html
   unsigned.xpi
 
 [browser_amosigned_trigger.js]
 [browser_amosigned_trigger_iframe.js]
@@ -45,17 +43,16 @@ support-files =
 [browser_badhash.js]
 [browser_badhashtype.js]
 [browser_bug540558.js]
 [browser_bug611242.js]
 [browser_bug638292.js]
 [browser_bug645699.js]
 [browser_bug672485.js]
 skip-if = true # disabled due to a leak. See bug 682410.
-[browser_cancel.js]
 [browser_concurrent_installs.js]
 [browser_cookies.js]
 [browser_cookies2.js]
 [browser_cookies3.js]
 [browser_cookies4.js]
 skip-if = true # Bug 1084646
 [browser_corrupt.js]
 [browser_datauri.js]
@@ -78,19 +75,19 @@ skip-if = true # Bug 1084646
 [browser_localfile4.js]
 [browser_navigateaway.js]
 [browser_navigateaway2.js]
 [browser_navigateaway3.js]
 skip-if = (os == "mac" || os == "win") # Bug 1198261
 [browser_navigateaway4.js]
 [browser_offline.js]
 [browser_relative.js]
-[browser_signed_multiple.js]
+[browser_signed_no_o.js]
 skip-if = require_signing
-[browser_signed_naming.js]
+[browser_signed_no_cn.js]
 skip-if = require_signing
 [browser_signed_tampered.js]
 skip-if = require_signing
 [browser_signed_trigger.js]
 skip-if = require_signing
 [browser_signed_untrusted.js]
 skip-if = require_signing
 [browser_signed_url.js]
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_cancel.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// ----------------------------------------------------------------------------
-// Tests that cancelling multiple installs doesn't fail
-function test() {
-  Harness.installConfirmCallback = confirm_install;
-  Harness.installEndedCallback = install_ended;
-  Harness.installsCompletedCallback = finish_test;
-  Harness.setup();
-
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-  var triggers = encodeURIComponent(JSON.stringify({
-    "Unsigned XPI": TESTROOT + "amosigned.xpi",
-    "Unsigned XPI 2": TESTROOT + "amosigned2.xpi",
-  }));
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
-}
-
-function get_item(items, url) {
-  for (let item of items) {
-    if (item.url == url)
-      return item;
-  }
-  ok(false, "Item for " + url + " was not listed");
-  return null;
-}
-
-function confirm_install(window) {
-  let items = window.document.getElementById("itemList").childNodes;
-  is(items.length, 2, "Should be 2 items listed in the confirmation dialog");
-  let item = get_item(items, TESTROOT + "amosigned.xpi");
-  if (item) {
-    is(item.name, "XPI Test", "Should have seen the name from the trigger list");
-    is(item.signed, "false", "Should have listed the item as signed");
-  }
-  item = get_item(items, TESTROOT + "amosigned2.xpi");
-  if (item) {
-    is(item.name, "Signed XPI Test", "Should have seen the name from the trigger list");
-    is(item.signed, "false", "Should have listed the item as signed");
-  }
-  return false;
-}
-
-function install_ended(install, addon) {
-  ok(false, "Should not have seen installs complete");
-}
-
-function finish_test(count) {
-  is(count, 0, "No add-ons should have been successfully installed");
-
-  Services.perms.remove(makeURI("http://example.com"), "install");
-
-  gBrowser.removeCurrentTab();
-  Harness.finish();
-}
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_httphash6.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_httphash6.js
@@ -58,19 +58,19 @@ function finish_failed_download() {
     "X-Target-Digest": "sha1:36ffb0acfd9c6e9682473aaebaab394d38b473c9",
     "Location": "http://example.com/browser/" + RELATIVE_DIR + "amosigned.xpi"
   });
 
   // The harness expects onNewInstall events for all installs that are about to start
   Harness.onNewInstall(gInstall);
 
   // Restart the install as a regular webpage install so the harness tracks it
-  AddonManager.installAddonsFromWebpage("application/x-xpinstall",
-                                        gBrowser.selectedBrowser,
-                                        gBrowser.contentPrincipal, [gInstall]);
+  AddonManager.installAddonFromWebpage("application/x-xpinstall",
+                                       gBrowser.selectedBrowser,
+                                       gBrowser.contentPrincipal, gInstall);
 }
 
 function install_ended(install, addon) {
   install.cancel();
 }
 
 function finish_test(count) {
   is(count, 1, "1 Add-on should have been successfully installed");
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_signed_multiple.js
+++ /dev/null
@@ -1,72 +0,0 @@
-// ----------------------------------------------------------------------------
-// Tests installing two signed add-ons in the same trigger works.
-// This verifies bug 453545
-function test() {
-  Harness.installConfirmCallback = confirm_install;
-  Harness.installEndedCallback = install_ended;
-  Harness.installsCompletedCallback = finish_test;
-  Harness.setup();
-
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-  var triggers = encodeURIComponent(JSON.stringify({
-    "Signed XPI": TESTROOT + "signed.xpi",
-    "Signed XPI 2": TESTROOT + "signed2.xpi",
-    "Signed XPI 3": TESTROOT + "signed-no-o.xpi",
-    "Signed XPI 4": TESTROOT + "signed-no-cn.xpi",
-    "Signed XPI 5": TESTROOT + "unsigned.xpi"
-  }));
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
-}
-
-function get_item(items, url) {
-  for (let item of items) {
-    if (item.url == url)
-      return item;
-  }
-  ok(false, "Item for " + url + " was not listed");
-  return null;
-}
-
-function confirm_install(window) {
-
-  var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"].
-                       getService(Components.interfaces.nsIStringBundleService);
-  var bundle = sbs.createBundle("chrome://mozapps/locale/xpinstall/xpinstallConfirm.properties");
-
-  var expectedIntroString = bundle.formatStringFromName("itemWarnIntroMultiple", ["5"], 1);
-
-  var introStringNode = window.document.getElementById("itemWarningIntro");
-  is(introStringNode.textContent, expectedIntroString, "Should have the correct intro string");
-
-  var items = window.document.getElementById("itemList").childNodes;
-  is(items.length, 5, "Should be 5 items listed in the confirmation dialog");
-  let item = get_item(items, TESTROOT + "signed.xpi");
-  if (item) {
-    is(item.name, "Signed XPI Test", "Should have seen the name from the trigger list");
-    is(item.cert, "(Object Signer)", "Should have seen the signer");
-    is(item.signed, "true", "Should have listed the item as signed");
-  }
-  item = get_item(items, TESTROOT + "signed2.xpi");
-  if (item) {
-    is(item.name, "Signed XPI Test", "Should have seen the name from the trigger list");
-    is(item.cert, "(Object Signer)", "Should have seen the signer");
-    is(item.signed, "true", "Should have listed the item as signed");
-  }
-  return true;
-}
-
-function install_ended(install, addon) {
-  install.cancel();
-}
-
-function finish_test(count) {
-  is(count, 5, "5 Add-ons should have been successfully installed");
-
-  Services.perms.remove(makeURI("http://example.com"), "install");
-
-  gBrowser.removeCurrentTab();
-  Harness.finish();
-}
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_signed_naming.js
+++ /dev/null
@@ -1,67 +0,0 @@
-// ----------------------------------------------------------------------------
-// Tests that the correct signer is presented for combinations of O and CN present.
-// The signed files have (when present) O=Mozilla Testing, CN=Object Signer
-// This verifies bug 372980
-function test() {
-  Harness.installConfirmCallback = confirm_install;
-  Harness.installEndedCallback = install_ended;
-  Harness.installsCompletedCallback = finish_test;
-  Harness.setup();
-
-  var pm = Services.perms;
-  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
-
-  var triggers = encodeURIComponent(JSON.stringify({
-    "Signed XPI (O and CN)": TESTROOT + "signed.xpi",
-    "Signed XPI (CN)": TESTROOT + "signed-no-o.xpi",
-    "Signed XPI (O)": TESTROOT + "signed-no-cn.xpi",
-  }));
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
-}
-
-function get_item(items, url) {
-  for (let item of items) {
-    if (item.url == url)
-      return item;
-  }
-  ok(false, "Item for " + url + " was not listed");
-  return null;
-}
-
-function confirm_install(window) {
-  let items = window.document.getElementById("itemList").childNodes;
-  is(items.length, 3, "Should be 3 items listed in the confirmation dialog");
-  let item = get_item(items, TESTROOT + "signed.xpi");
-  if (item) {
-    is(item.name, "Signed XPI Test", "Should have seen the name from the trigger list");
-    is(item.cert, "(Object Signer)", "Should have seen the signer");
-    is(item.signed, "true", "Should have listed the item as signed");
-  }
-  item = get_item(items, TESTROOT + "signed-no-o.xpi");
-  if (item) {
-    is(item.name, "Signed XPI Test (No Org)", "Should have seen the name from the trigger list");
-    is(item.cert, "(Object Signer)", "Should have seen the signer");
-    is(item.signed, "true", "Should have listed the item as signed");
-  }
-  item = get_item(items, TESTROOT + "signed-no-cn.xpi");
-  if (item) {
-    is(item.name, "Signed XPI Test (No Common Name)", "Should have seen the name from the trigger list");
-    is(item.cert, "(Mozilla Testing)", "Should have seen the signer");
-    is(item.signed, "true", "Should have listed the item as signed");
-  }
-  return true;
-}
-
-function install_ended(install, addon) {
-  install.cancel();
-}
-
-function finish_test(count) {
-  is(count, 3, "3 Add-ons should have been successfully installed");
-
-  Services.perms.remove(makeURI("http://example.com"), "install");
-
-  gBrowser.removeCurrentTab();
-  Harness.finish();
-}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_signed_no_cn.js
@@ -0,0 +1,42 @@
+// ----------------------------------------------------------------------------
+// Tests that the correct signer is presented for combinations of O and CN present.
+// The signed files have (when present) O=Mozilla Testing, CN=Object Signer
+// This verifies bug 372980
+function test() {
+  Harness.installConfirmCallback = confirm_install;
+  Harness.installEndedCallback = install_ended;
+  Harness.installsCompletedCallback = finish_test;
+  Harness.setup();
+
+  var pm = Services.perms;
+  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+  const trigger = encodeURIComponent(JSON.stringify({
+    "Signed XPI (O)": TESTROOT + "signed-no-cn.xpi",
+  }));
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.loadURI(TESTROOT + "installtrigger.html?" + trigger);
+}
+
+function confirm_install(window) {
+  let items = window.document.getElementById("itemList").childNodes;
+  is(items.length, 1, "Should be 1 item listed in the confirmation dialog");
+  let item = items[0];
+  is(item.name, "Signed XPI Test (No Common Name)", "Should have seen the name from the trigger list");
+  is(item.cert, "(Mozilla Testing)", "Should have seen the signer");
+  is(item.signed, "true", "Should have listed the item as signed");
+  return true;
+}
+
+function install_ended(install, addon) {
+  install.cancel();
+}
+
+function finish_test(count) {
+  is(count, 1, "1 add-on should have been successfully installed");
+
+  Services.perms.remove(makeURI("http://example.com"), "install");
+
+  gBrowser.removeCurrentTab();
+  Harness.finish();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_signed_no_o.js
@@ -0,0 +1,42 @@
+// ----------------------------------------------------------------------------
+// Tests that the correct signer is presented for combinations of O and CN present.
+// The signed files have (when present) O=Mozilla Testing, CN=Object Signer
+// This verifies bug 372980
+function test() {
+  Harness.installConfirmCallback = confirm_install;
+  Harness.installEndedCallback = install_ended;
+  Harness.installsCompletedCallback = finish_test;
+  Harness.setup();
+
+  var pm = Services.perms;
+  pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+  const trigger = encodeURIComponent(JSON.stringify({
+    "Signed XPI (CN)": TESTROOT + "signed-no-o.xpi",
+  }));
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.loadURI(TESTROOT + "installtrigger.html?" + trigger);
+}
+
+function confirm_install(window) {
+  let items = window.document.getElementById("itemList").childNodes;
+  is(items.length, 1, "Should be 1 item listed in the confirmation dialog");
+  let item = items[0];
+  is(item.name, "Signed XPI Test (No Org)", "Should have seen the name from the trigger list");
+  is(item.cert, "(Object Signer)", "Should have seen the signer");
+  is(item.signed, "true", "Should have listed the item as signed");
+  return true;
+}
+
+function install_ended(install, addon) {
+  install.cancel();
+}
+
+function finish_test(count) {
+  is(count, 1, "1 add-on should have been successfully installed");
+
+  Services.perms.remove(makeURI("http://example.com"), "install");
+
+  gBrowser.removeCurrentTab();
+  Harness.finish();
+}
deleted file mode 100644
index 085efbbf7fdd8f9cf475e903b16552e614f9139c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001