Bug 1323129 part 2: remove amIWebInstaller r=rhelmer
authorAndrew Swan <aswan@mozilla.com>
Wed, 04 Jan 2017 10:13:16 -0800
changeset 456635 308b6c4b0e8ff09723cc87229faddd3a0ed35441
parent 456634 d97b2eeffc46de2ef3f9312dd60e79cce68f5208
child 456636 4afc167417876569e9e317a44ad41fa9da5c8177
push id40575
push userjwwang@mozilla.com
push dateFri, 06 Jan 2017 02:27:46 +0000
reviewersrhelmer
bugs1323129
milestone53.0a1
Bug 1323129 part 2: remove amIWebInstaller r=rhelmer MozReview-Commit-ID: O0jtQi9BzQ
b2g/installer/package-manifest.in
browser/base/content/browser-addons.js
browser/installer/package-manifest.in
mobile/android/installer/package-manifest.in
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/amIWebInstallListener.idl
toolkit/mozapps/extensions/amIWebInstallPrompt.idl
toolkit/mozapps/extensions/amWebInstallListener.js
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/extensions.manifest
toolkit/mozapps/extensions/moz.build
toolkit/mozapps/extensions/test/browser/browser_bug567127.js
toolkit/mozapps/extensions/test/browser/browser_dragdrop.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
toolkit/mozapps/extensions/test/xpinstall/head.js
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -444,17 +444,16 @@
 #ifndef DISABLE_MOZ_RIL_GEOLOC
 #endif
 #endif // MOZ_WIDGET_GONK && MOZ_B2G_RIL
 
 #ifndef MOZ_WIDGET_GONK
 @RESPATH@/components/addonManager.js
 @RESPATH@/components/amContentHandler.js
 @RESPATH@/components/amInstallTrigger.js
-@RESPATH@/components/amWebInstallListener.js
 
 @RESPATH@/components/OopCommandLine.js
 @RESPATH@/components/CommandLine.js
 #endif
 @RESPATH@/components/extensions.manifest
 @RESPATH@/components/nsBlocklistService.js
 @RESPATH@/components/BootstrapCommandLine.js
 
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -210,17 +210,17 @@ const gXPInstallObserver = {
 
     Services.telemetry
             .getHistogramById("SECURITY_UI")
             .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
   },
 
   observe(aSubject, aTopic, aData) {
     var brandBundle = document.getElementById("bundle_brand");
-    var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
+    var installInfo = aSubject.wrappedJSObject;
     var browser = installInfo.browser;
 
     // Make sure the browser is still alive.
     if (!browser || gBrowser.browsers.indexOf(browser) == -1)
       return;
 
     const anchorID = "addons-notification-icon";
     var messageString, action;
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -407,17 +407,16 @@
 @RESPATH@/components/NetworkGeolocationProvider.js
 @RESPATH@/components/extensions.manifest
 @RESPATH@/components/EditorUtils.manifest
 @RESPATH@/components/EditorUtils.js
 @RESPATH@/components/addonManager.js
 @RESPATH@/components/amContentHandler.js
 @RESPATH@/components/amInstallTrigger.js
 @RESPATH@/components/amWebAPI.js
-@RESPATH@/components/amWebInstallListener.js
 @RESPATH@/components/nsBlocklistService.js
 @RESPATH@/components/nsBlocklistServiceContent.js
 #ifdef MOZ_UPDATER
 @RESPATH@/components/nsUpdateService.manifest
 @RESPATH@/components/nsUpdateService.js
 @RESPATH@/components/nsUpdateServiceStub.js
 #endif
 @RESPATH@/components/nsUpdateTimerManager.manifest
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -298,17 +298,16 @@
 @BINPATH@/components/EditorUtils.js
 @BINPATH@/components/extensions.manifest
 @BINPATH@/components/utils.manifest
 @BINPATH@/components/simpleServices.js
 @BINPATH@/components/addonManager.js
 @BINPATH@/components/amContentHandler.js
 @BINPATH@/components/amInstallTrigger.js
 @BINPATH@/components/amWebAPI.js
-@BINPATH@/components/amWebInstallListener.js
 @BINPATH@/components/nsBlocklistService.js
 #ifndef RELEASE_OR_BETA
 @BINPATH@/components/TabSource.js
 #endif
 @BINPATH@/components/webvtt.xpt
 @BINPATH@/components/WebVTT.manifest
 @BINPATH@/components/WebVTTParserWrapper.js
 
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -84,16 +84,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                   "resource://gre/modules/addons/AddonRepository.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+                                  "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PromptUtils",
+                                  "resource://gre/modules/SharedPromptUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "CertUtils", function() {
   let certUtils = {};
   Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
   return certUtils;
 });
 
 const INTEGER = /^[1-9]\d*$/;
@@ -439,16 +443,267 @@ BrowserListener.prototype = {
     this.unregister();
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
                                          Ci.nsIWebProgressListener,
                                          Ci.nsIObserver])
 };
 
+function installNotifyObservers(aTopic, aBrowser, aUri, aInstalls, aInstallFn) {
+  let info = {
+    wrappedJSObject: {
+      browser: aBrowser,
+      originatingURI: aUri,
+      installs: aInstalls,
+      install: aInstallFn,
+    },
+  };
+  Services.obs.notifyObservers(info, aTopic, null);
+}
+
+/**
+ * Creates a new installer to monitor downloads and prompt to install when
+ * ready
+ *
+ * @param  aBrowser
+ *         The browser that started the installations
+ * @param  aUrl
+ *         The URL that started the installations
+ * @param  aInstalls
+ *         An array of AddonInstalls
+ */
+function Installer(aBrowser, aUrl, aInstalls) {
+  this.browser = aBrowser;
+  this.url = aUrl;
+  this.downloads = aInstalls;
+  this.installed = [];
+
+  installNotifyObservers("addon-install-started", aBrowser, aUrl, aInstalls);
+
+  const READY_STATES = [
+    AddonManager.STATE_AVAILABLE,
+    AddonManager.STATE_DOWNLOAD_FAILED,
+    AddonManager.STATE_INSTALL_FAILED,
+    AddonManager.STATE_CANCELLED,
+  ];
+  for (let install of aInstalls) {
+    install.addListener(this);
+
+    // Start downloading if it hasn't already begun
+    if (READY_STATES.indexOf(install.state) != -1)
+      install.install();
+  }
+
+  this.checkAllDownloaded();
+}
+
+Installer.prototype = {
+  URI_XPINSTALL_DIALOG: "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul",
+  browser: null,
+  downloads: null,
+  installed: null,
+  isDownloading: true,
+
+  /**
+   * Checks if all downloads are now complete and if so prompts to install.
+   */
+  checkAllDownloaded: function() {
+    // Prevent re-entrancy caused by the confirmation dialog cancelling unwanted
+    // installs.
+    if (!this.isDownloading)
+      return;
+
+    var failed = [];
+    var installs = [];
+
+    for (let install of this.downloads) {
+      switch (install.state) {
+        case AddonManager.STATE_AVAILABLE:
+        case AddonManager.STATE_DOWNLOADING:
+          // Exit early if any add-ons haven't started downloading yet or are
+          // still downloading
+          return;
+        case AddonManager.STATE_DOWNLOAD_FAILED:
+          failed.push(install);
+          break;
+        case AddonManager.STATE_DOWNLOADED:
+          // App disabled items are not compatible and so fail to install
+          if (install.addon.appDisabled)
+            failed.push(install);
+          else
+            installs.push(install);
+          break;
+        case AddonManager.STATE_CANCELLED:
+          // Just ignore cancelled downloads
+          break;
+        default:
+          logger.warn("Download of " + install.sourceURI.spec + " in unexpected state " +
+                      install.state);
+      }
+    }
+
+    this.isDownloading = false;
+    this.downloads = installs;
+
+    if (failed.length > 0) {
+      // Stop listening and cancel any installs that are failed because of
+      // compatibility reasons.
+      for (let install of failed) {
+        if (install.state == AddonManager.STATE_DOWNLOADED) {
+          install.removeListener(this);
+          install.cancel();
+        }
+      }
+      installNotifyObservers("addon-install-failed", this.browser, this.url, failed);
+    }
+
+    // If none of the downloads were successful then exit early
+    if (this.downloads.length == 0)
+      return;
+
+    // Check for a custom installation prompt that may be provided by the
+    // applicaton
+    if ("@mozilla.org/addons/web-install-prompt;1" in Cc) {
+      try {
+        let prompt = Cc["@mozilla.org/addons/web-install-prompt;1"].
+                                  getService(Ci.amIWebInstallPrompt);
+        prompt.confirm(this.browser, this.url, this.downloads, this.downloads.length);
+        return;
+      } catch (e) {}
+    }
+
+    if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+      installNotifyObservers("addon-install-confirmation", this.browser, this.url, this.downloads);
+      return;
+    }
+
+    let args = {};
+    args.url = this.url;
+    args.installs = this.downloads;
+    args.wrappedJSObject = args;
+
+    try {
+      Cc["@mozilla.org/base/telemetry;1"].
+                   getService(Ci.nsITelemetry).
+                   getHistogramById("SECURITY_UI").
+                   add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
+      let parentWindow = null;
+      if (this.browser) {
+        parentWindow = this.browser.ownerDocument.defaultView;
+        PromptUtils.fireDialogEvent(parentWindow, "DOMWillOpenModalDialog", this.browser);
+      }
+      Services.ww.openWindow(parentWindow, this.URI_XPINSTALL_DIALOG,
+                             null, "chrome,modal,centerscreen", args);
+    } catch (e) {
+      logger.warn("Exception showing install confirmation dialog", e);
+      for (let install of this.downloads) {
+        install.removeListener(this);
+        // Cancel the installs, as currently there is no way to make them fail
+        // from here.
+        install.cancel();
+      }
+      installNotifyObservers("addon-install-cancelled", this.browser, this.url,
+                      this.downloads);
+    }
+  },
+
+  /**
+   * Checks if all installs are now complete and if so notifies observers.
+   */
+  checkAllInstalled: function() {
+    var failed = [];
+
+    for (let install of this.downloads) {
+      switch (install.state) {
+        case AddonManager.STATE_DOWNLOADED:
+        case AddonManager.STATE_INSTALLING:
+          // Exit early if any add-ons haven't started installing yet or are
+          // still installing
+          return;
+        case AddonManager.STATE_INSTALL_FAILED:
+          failed.push(install);
+          break;
+      }
+    }
+
+    this.downloads = null;
+
+    if (failed.length > 0)
+      installNotifyObservers("addon-install-failed", this.browser, this.url, failed);
+
+    if (this.installed.length > 0)
+      installNotifyObservers("addon-install-complete", this.browser, this.url, this.installed);
+    this.installed = null;
+  },
+
+  onDownloadCancelled: function(aInstall) {
+    aInstall.removeListener(this);
+    this.checkAllDownloaded();
+  },
+
+  onDownloadFailed: function(aInstall) {
+    aInstall.removeListener(this);
+    this.checkAllDownloaded();
+  },
+
+  onDownloadEnded: function(aInstall) {
+    this.checkAllDownloaded();
+    return false;
+  },
+
+  onInstallCancelled: function(aInstall) {
+    aInstall.removeListener(this);
+    this.checkAllInstalled();
+  },
+
+  onInstallFailed: function(aInstall) {
+    aInstall.removeListener(this);
+    this.checkAllInstalled();
+  },
+
+  onInstallEnded: function(aInstall) {
+    aInstall.removeListener(this);
+    this.installed.push(aInstall);
+
+    // If installing a theme that is disabled and can be enabled then enable it
+    if (aInstall.addon.type == "theme" &&
+        aInstall.addon.userDisabled == true &&
+        aInstall.addon.appDisabled == false) {
+          aInstall.addon.userDisabled = false;
+    }
+
+    this.checkAllInstalled();
+  }
+};
+
+const weblistener = {
+  onWebInstallDisabled: function(aBrowser, aUri, aInstalls) {
+    installNotifyObservers("addon-install-disabled", aBrowser, aUri, aInstalls);
+  },
+
+  onWebInstallOriginBlocked: function(aBrowser, aUri, aInstalls) {
+    installNotifyObservers("addon-install-origin-blocked", aBrowser, aUri, aInstalls);
+    return false;
+  },
+
+  onWebInstallBlocked: function(aBrowser, aUri, aInstalls) {
+    installNotifyObservers("addon-install-blocked", aBrowser, aUri, aInstalls,
+                           function() { new Installer(this.browser, this.originatingURI, this.installs); });
+    return false;
+  },
+
+  onWebInstallRequested: function(aBrowser, aUri, aInstalls) {
+    new Installer(aBrowser, aUri, aInstalls);
+
+    // We start the installs ourself
+    return false;
+  },
+};
+
 /**
  * This represents an author of an add-on (e.g. creator or developer)
  *
  * @param  aName
  *         The name of the author
  * @param  aURL
  *         The URL of the author's profile page
  */
@@ -2040,52 +2295,41 @@ var AddonManagerInternal = {
     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 (!("@mozilla.org/addons/web-install-listener;1" in Cc)) {
-      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.
     let topBrowser = aBrowser;
     let docShell = aBrowser.ownerDocument.defaultView
                            .QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDocShell)
                            .QueryInterface(Ci.nsIDocShellTreeItem);
     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)) {
         aInstall.cancel();
 
         weblistener.onWebInstallDisabled(topBrowser, aInstallingPrincipal.URI,
                                          [aInstall], 1);
         return;
       } else if (!aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) {
         aInstall.cancel();
 
-        if (weblistener instanceof Ci.amIWebInstallListener2) {
-          weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI,
-                                                [aInstall], 1);
-        }
+        weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI,
+                                              [aInstall], 1);
         return;
       }
 
       // The install may start now depending on the web install listener,
       // listen for the browser navigating to a new origin and cancel the
       // install in that case.
       new BrowserListener(aBrowser, aInstallingPrincipal, aInstall);
 
@@ -2103,16 +2347,35 @@ var AddonManagerInternal = {
       // calling onWebInstallBlocked or onWebInstallRequested the
       // install should get cancelled.
       logger.warn("Failure calling web installer", e);
       aInstall.cancel();
     }
   },
 
   /**
+   * Starts installation of an AddonInstall created from add-ons manager
+   * front-end code (e.g., drag-and-drop of xpis or "Install Add-on from File"
+   *
+   * @param  browser
+   *         The browser element where the installation was initiated
+   * @param  uri
+   *         The URI of the page where the installation was initiated
+   * @param  install
+   *         The AddonInstall to be installed
+   */
+  installAddonFromAOM(browser, uri, install) {
+    if (!gStarted)
+      throw Components.Exception("AddonManager is not initialized",
+                                 Cr.NS_ERROR_NOT_INITIALIZED);
+
+    weblistener.onWebInstallRequested(browser, uri, [install]);
+  },
+
+  /**
    * Adds a new InstallListener if the listener is not already registered.
    *
    * @param  aListener
    *         The InstallListener to add
    */
   addInstallListener(aListener) {
     if (!aListener || typeof aListener != "object")
       throw Components.Exception("aListener must be a InstallListener object",
@@ -3404,16 +3667,20 @@ this.AddonManager = {
   },
 
   installAddonFromWebpage(aType, aBrowser, aInstallingPrincipal, aInstall) {
     AddonManagerInternal.installAddonFromWebpage(aType, aBrowser,
                                                  aInstallingPrincipal,
                                                  aInstall);
   },
 
+  installAddonFromAOM(aBrowser, aUri, aInstall) {
+    AddonManagerInternal.installAddonFromAOM(aBrowser, aUri, aInstall);
+  },
+
   installTemporaryAddon(aDirectory) {
     return AddonManagerInternal.installTemporaryAddon(aDirectory);
   },
 
   installAddonFromSources(aDirectory) {
     return AddonManagerInternal.installAddonFromSources(aDirectory);
   },
 
rename from toolkit/mozapps/extensions/amIWebInstallListener.idl
rename to toolkit/mozapps/extensions/amIWebInstallPrompt.idl
--- a/toolkit/mozapps/extensions/amIWebInstallListener.idl
+++ b/toolkit/mozapps/extensions/amIWebInstallPrompt.idl
@@ -4,116 +4,16 @@
 
 #include "nsISupports.idl"
 
 interface nsIDOMElement;
 interface nsIURI;
 interface nsIVariant;
 
 /**
- * amIWebInstallInfo is used by the default implementation of
- * amIWebInstallListener to communicate with the running application and allow
- * it to warn the user about blocked installs and start the installs running.
- */
-[scriptable, uuid(fa0b47a3-f819-47ac-bc66-4bd1d7f67b1d)]
-interface amIWebInstallInfo : nsISupports
-{
-  readonly attribute nsIDOMElement browser;
-  readonly attribute nsIURI originatingURI;
-  readonly attribute nsIVariant installs;
-
-  /**
-   * Starts all installs.
-   */
-  void install();
-};
-
-/**
- * The registered amIWebInstallListener is used to notify about new installs
- * triggered by websites. The default implementation displays a confirmation
- * dialog when add-ons are ready to install and uses the observer service to
- * notify when installations are blocked.
- */
-[scriptable, uuid(d9240d4b-6b3a-4cad-b402-de6c93337e0c)]
-interface amIWebInstallListener : nsISupports
-{
-  /**
-   * Called when installation by websites is currently disabled.
-   *
-   * @param  aBrowser
-   *         The browser that triggered the installs
-   * @param  aUri
-   *         The URI of the site that triggered the installs
-   * @param  aInstalls
-   *         The AddonInstalls that were blocked
-   * @param  aCount
-   *         The number of AddonInstalls
-   */
-  void onWebInstallDisabled(in nsIDOMElement aBrowser, in nsIURI aUri,
-                            [array, size_is(aCount)] in nsIVariant aInstalls,
-                            [optional] in uint32_t aCount);
-
-  /**
-   * Called when the website is not allowed to directly prompt the user to
-   * install add-ons.
-   *
-   * @param  aBrowser
-   *         The browser that triggered the installs
-   * @param  aUri
-   *         The URI of the site that triggered the installs
-   * @param  aInstalls
-   *         The AddonInstalls that were blocked
-   * @param  aCount
-   *         The number of AddonInstalls
-   * @return true if the caller should start the installs
-   */
-  boolean onWebInstallBlocked(in nsIDOMElement aBrowser, in nsIURI aUri,
-                              [array, size_is(aCount)] in nsIVariant aInstalls,
-                              [optional] in uint32_t aCount);
-
-  /**
-   * Called when a website wants to ask the user to install add-ons.
-   *
-   * @param  aBrowser
-   *         The browser that triggered the installs
-   * @param  aUri
-   *         The URI of the site that triggered the installs
-   * @param  aInstalls
-   *         The AddonInstalls that were requested
-   * @param  aCount
-   *         The number of AddonInstalls
-   * @return true if the caller should start the installs
-   */
-  boolean onWebInstallRequested(in nsIDOMElement aBrowser, in nsIURI aUri,
-                                [array, size_is(aCount)] in nsIVariant aInstalls,
-                                [optional] in uint32_t aCount);
-};
-
-[scriptable, uuid(a80b89ad-bb1a-4c43-9cb7-3ae656556f78)]
-interface amIWebInstallListener2 : nsISupports
-{
-  /**
-   * Called when a non-same-origin resource attempted to initiate an install.
-   * Installs will have already been cancelled and cannot be restarted.
-   *
-   * @param  aBrowser
-   *         The browser that triggered the installs
-   * @param  aUri
-   *         The URI of the site that triggered the installs
-   * @param  aInstalls
-   *         The AddonInstalls that were blocked
-   * @param  aCount
-   *         The number of AddonInstalls
-   */
-  boolean onWebInstallOriginBlocked(in nsIDOMElement aBrowser, in nsIURI aUri,
-                                    [array, size_is(aCount)] in nsIVariant aInstalls,
-                                    [optional] in uint32_t aCount);
-};
-
-/**
  * amIWebInstallPrompt is used, if available, by the default implementation of 
  * amIWebInstallInfo to display a confirmation UI to the user before running
  * installs.
  */
 [scriptable, uuid(386906f1-4d18-45bf-bc81-5dcd68e42c3b)]
 interface amIWebInstallPrompt : nsISupports
 {
   /**
deleted file mode 100644
--- a/toolkit/mozapps/extensions/amWebInstallListener.js
+++ /dev/null
@@ -1,336 +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/. */
-
-/**
- * This is a default implementation of amIWebInstallListener that should work
- * for most applications but can be overriden. It notifies the observer service
- * about blocked installs. For normal installs it pops up an install
- * confirmation when all the add-ons have been downloaded.
- */
-
-"use strict";
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/AddonManager.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Preferences.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PromptUtils", "resource://gre/modules/SharedPromptUtils.jsm");
-
-const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
-
-// Installation can begin from any of these states
-const READY_STATES = [
-  AddonManager.STATE_AVAILABLE,
-  AddonManager.STATE_DOWNLOAD_FAILED,
-  AddonManager.STATE_INSTALL_FAILED,
-  AddonManager.STATE_CANCELLED
-];
-
-Cu.import("resource://gre/modules/Log.jsm");
-const LOGGER_ID = "addons.weblistener";
-
-// Create a new logger for use by the Addons Web Listener
-// (Requires AddonManager.jsm)
-var logger = Log.repository.getLogger(LOGGER_ID);
-
-function notifyObservers(aTopic, aBrowser, aUri, aInstalls) {
-  let info = {
-    browser: aBrowser,
-    originatingURI: aUri,
-    installs: aInstalls,
-
-    QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
-  };
-  Services.obs.notifyObservers(info, aTopic, null);
-}
-
-/**
- * Creates a new installer to monitor downloads and prompt to install when
- * ready
- *
- * @param  aBrowser
- *         The browser that started the installations
- * @param  aUrl
- *         The URL that started the installations
- * @param  aInstalls
- *         An array of AddonInstalls
- */
-function Installer(aBrowser, aUrl, aInstalls) {
-  this.browser = aBrowser;
-  this.url = aUrl;
-  this.downloads = aInstalls;
-  this.installed = [];
-
-  notifyObservers("addon-install-started", aBrowser, aUrl, aInstalls);
-
-  for (let install of aInstalls) {
-    install.addListener(this);
-
-    // Start downloading if it hasn't already begun
-    if (READY_STATES.indexOf(install.state) != -1)
-      install.install();
-  }
-
-  this.checkAllDownloaded();
-}
-
-Installer.prototype = {
-  browser: null,
-  downloads: null,
-  installed: null,
-  isDownloading: true,
-
-  /**
-   * Checks if all downloads are now complete and if so prompts to install.
-   */
-  checkAllDownloaded() {
-    // Prevent re-entrancy caused by the confirmation dialog cancelling unwanted
-    // installs.
-    if (!this.isDownloading)
-      return;
-
-    var failed = [];
-    var installs = [];
-
-    for (let install of this.downloads) {
-      switch (install.state) {
-      case AddonManager.STATE_AVAILABLE:
-      case AddonManager.STATE_DOWNLOADING:
-        // Exit early if any add-ons haven't started downloading yet or are
-        // still downloading
-        return;
-      case AddonManager.STATE_DOWNLOAD_FAILED:
-        failed.push(install);
-        break;
-      case AddonManager.STATE_DOWNLOADED:
-        // App disabled items are not compatible and so fail to install
-        if (install.addon.appDisabled)
-          failed.push(install);
-        else
-          installs.push(install);
-        break;
-      case AddonManager.STATE_CANCELLED:
-        // Just ignore cancelled downloads
-        break;
-      default:
-        logger.warn("Download of " + install.sourceURI.spec + " in unexpected state " +
-                    install.state);
-      }
-    }
-
-    this.isDownloading = false;
-    this.downloads = installs;
-
-    if (failed.length > 0) {
-      // Stop listening and cancel any installs that are failed because of
-      // compatibility reasons.
-      for (let install of failed) {
-        if (install.state == AddonManager.STATE_DOWNLOADED) {
-          install.removeListener(this);
-          install.cancel();
-        }
-      }
-      notifyObservers("addon-install-failed", this.browser, this.url, failed);
-    }
-
-    // If none of the downloads were successful then exit early
-    if (this.downloads.length == 0)
-      return;
-
-    // Check for a custom installation prompt that may be provided by the
-    // applicaton
-    if ("@mozilla.org/addons/web-install-prompt;1" in Cc) {
-      try {
-        let prompt = Cc["@mozilla.org/addons/web-install-prompt;1"].
-                     getService(Ci.amIWebInstallPrompt);
-        prompt.confirm(this.browser, this.url, this.downloads, this.downloads.length);
-        return;
-      } catch (e) {}
-    }
-
-    if (Preferences.get("xpinstall.customConfirmationUI", false)) {
-      notifyObservers("addon-install-confirmation", this.browser, this.url, this.downloads);
-      return;
-    }
-
-    let args = {};
-    args.url = this.url;
-    args.installs = this.downloads;
-    args.wrappedJSObject = args;
-
-    try {
-      Cc["@mozilla.org/base/telemetry;1"].
-            getService(Ci.nsITelemetry).
-            getHistogramById("SECURITY_UI").
-            add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
-      let parentWindow = null;
-      if (this.browser) {
-        parentWindow = this.browser.ownerDocument.defaultView;
-        PromptUtils.fireDialogEvent(parentWindow, "DOMWillOpenModalDialog", this.browser);
-      }
-      Services.ww.openWindow(parentWindow, URI_XPINSTALL_DIALOG,
-                             null, "chrome,modal,centerscreen", args);
-    } catch (e) {
-      logger.warn("Exception showing install confirmation dialog", e);
-      for (let install of this.downloads) {
-        install.removeListener(this);
-        // Cancel the installs, as currently there is no way to make them fail
-        // from here.
-        install.cancel();
-      }
-      notifyObservers("addon-install-cancelled", this.browser, this.url,
-                      this.downloads);
-    }
-  },
-
-  /**
-   * Checks if all installs are now complete and if so notifies observers.
-   */
-  checkAllInstalled() {
-    var failed = [];
-
-    for (let install of this.downloads) {
-      switch (install.state) {
-      case AddonManager.STATE_DOWNLOADED:
-      case AddonManager.STATE_INSTALLING:
-        // Exit early if any add-ons haven't started installing yet or are
-        // still installing
-        return;
-      case AddonManager.STATE_INSTALL_FAILED:
-        failed.push(install);
-        break;
-      }
-    }
-
-    this.downloads = null;
-
-    if (failed.length > 0)
-      notifyObservers("addon-install-failed", this.browser, this.url, failed);
-
-    if (this.installed.length > 0)
-      notifyObservers("addon-install-complete", this.browser, this.url, this.installed);
-    this.installed = null;
-  },
-
-  onDownloadCancelled(aInstall) {
-    aInstall.removeListener(this);
-    this.checkAllDownloaded();
-  },
-
-  onDownloadFailed(aInstall) {
-    aInstall.removeListener(this);
-    this.checkAllDownloaded();
-  },
-
-  onDownloadEnded(aInstall) {
-    this.checkAllDownloaded();
-    return false;
-  },
-
-  onInstallCancelled(aInstall) {
-    aInstall.removeListener(this);
-    this.checkAllInstalled();
-  },
-
-  onInstallFailed(aInstall) {
-    aInstall.removeListener(this);
-    this.checkAllInstalled();
-  },
-
-  onInstallEnded(aInstall) {
-    aInstall.removeListener(this);
-    this.installed.push(aInstall);
-
-    // If installing a theme that is disabled and can be enabled then enable it
-    if (aInstall.addon.type == "theme" &&
-        aInstall.addon.userDisabled == true &&
-        aInstall.addon.appDisabled == false) {
-      aInstall.addon.userDisabled = false;
-    }
-
-    this.checkAllInstalled();
-  }
-};
-
-function extWebInstallListener() {
-}
-
-extWebInstallListener.prototype = {
-  /**
-   * @see amIWebInstallListener.idl
-   */
-  onWebInstallDisabled(aBrowser, aUri, aInstalls) {
-    let info = {
-      browser: aBrowser,
-      originatingURI: aUri,
-      installs: aInstalls,
-
-      QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
-    };
-    Services.obs.notifyObservers(info, "addon-install-disabled", null);
-  },
-
-  /**
-   * @see amIWebInstallListener.idl
-   */
-  onWebInstallOriginBlocked(aBrowser, aUri, aInstalls) {
-    let info = {
-      browser: aBrowser,
-      originatingURI: aUri,
-      installs: aInstalls,
-
-      install() {
-      },
-
-      QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
-    };
-    Services.obs.notifyObservers(info, "addon-install-origin-blocked", null);
-
-    return false;
-  },
-
-  /**
-   * @see amIWebInstallListener.idl
-   */
-  onWebInstallBlocked(aBrowser, aUri, aInstalls) {
-    let info = {
-      browser: aBrowser,
-      originatingURI: aUri,
-      installs: aInstalls,
-
-      install() {
-        new Installer(this.browser, this.originatingURI, this.installs);
-      },
-
-      QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
-    };
-    Services.obs.notifyObservers(info, "addon-install-blocked", null);
-
-    return false;
-  },
-
-  /**
-   * @see amIWebInstallListener.idl
-   */
-  onWebInstallRequested(aBrowser, aUri, aInstalls) {
-    new Installer(aBrowser, aUri, aInstalls);
-
-    // We start the installs ourself
-    return false;
-  },
-
-  classDescription: "XPI Install Handler",
-  contractID: "@mozilla.org/addons/web-install-listener;1",
-  classID: Components.ID("{0f38e086-89a3-40a5-8ffc-9b694de1d04a}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallListener,
-                                         Ci.amIWebInstallListener2])
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([extWebInstallListener]);
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -1340,40 +1340,24 @@ var gViewController = {
           fp.appendFilter(gStrings.ext.GetStringFromName("installFromFile.filterName"),
                           "*.xpi;*.jar");
           fp.appendFilters(nsIFilePicker.filterAll);
         } catch (e) { }
 
         if (fp.show() != nsIFilePicker.returnOK)
           return;
 
-        var files = fp.files;
-        var installs = [];
-
-        function buildNextInstall() {
-          if (!files.hasMoreElements()) {
-            if (installs.length > 0) {
-              // Display the normal install confirmation for the installs
-              let webInstaller = Cc["@mozilla.org/addons/web-install-listener;1"].
-                                 getService(Ci.amIWebInstallListener);
-              webInstaller.onWebInstallRequested(getBrowserElement(),
-                                                 document.documentURIObject,
-                                                 installs);
-            }
-            return;
-          }
-
-          var file = files.getNext();
-          AddonManager.getInstallForFile(file, function(aInstall) {
-            installs.push(aInstall);
-            buildNextInstall();
+        let browser = getBrowserElement();
+        let files = fp.files;
+        while (files.hasMoreElements()) {
+          let file = files.getNext();
+          AddonManager.getInstallForFile(file, install => {
+            AddonManager.installAddonFromAOM(browser, document.documentURI, install);
           });
         }
-
-        buildNextInstall();
       }
     },
 
     cmd_debugAddons: {
       isEnabled() {
         return true;
       },
       doCommand() {
@@ -3884,59 +3868,36 @@ var gDragDrop = {
     var types = aEvent.dataTransfer.types;
     if (types.includes("text/uri-list") ||
         types.includes("text/x-moz-url") ||
         types.includes("application/x-moz-file"))
       aEvent.preventDefault();
   },
 
   onDrop(aEvent) {
-    var dataTransfer = aEvent.dataTransfer;
-    var urls = [];
-
-    // Convert every dropped item into a url
+    let dataTransfer = aEvent.dataTransfer;
+    let browser = getBrowserElement();
+
+    // Convert every dropped item into a url and install it
     for (var i = 0; i < dataTransfer.mozItemCount; i++) {
-      var url = dataTransfer.mozGetDataAt("text/uri-list", i);
-      if (url) {
-        urls.push(url);
-        continue;
+      let url = dataTransfer.mozGetDataAt("text/uri-list", i);
+      if (!url) {
+        url = dataTransfer.mozGetDataAt("text/x-moz-url", i);
       }
-
-      url = dataTransfer.mozGetDataAt("text/x-moz-url", i);
       if (url) {
-        urls.push(url.split("\n")[0]);
-        continue;
+        url = url.split("\n")[0];
+      } else {
+        let file = dataTransfer.mozGetDataAt("application/x-moz-file", i);
+        if (file) {
+          url = Services.io.newFileURI(file).spec;
+        }
       }
 
-      var file = dataTransfer.mozGetDataAt("application/x-moz-file", i);
-      if (file) {
-        urls.push(Services.io.newFileURI(file).spec);
-        continue;
+      if (url) {
+        AddonManager.getInstallForURL(url, install => {
+          AddonManager.installAddonFromAOM(browser, document.documentURI, install);
+        }, "application/x-xpinstall");
       }
     }
 
-    var pos = 0;
-    var installs = [];
-
-    function buildNextInstall() {
-      if (pos == urls.length) {
-        if (installs.length > 0) {
-          // Display the normal install confirmation for the installs
-          let webInstaller = Cc["@mozilla.org/addons/web-install-listener;1"].
-                             getService(Ci.amIWebInstallListener);
-          webInstaller.onWebInstallRequested(getBrowserElement(),
-                                             document.documentURIObject,
-                                             installs);
-        }
-        return;
-      }
-
-      AddonManager.getInstallForURL(urls[pos++], function(aInstall) {
-        installs.push(aInstall);
-        buildNextInstall();
-      }, "application/x-xpinstall");
-    }
-
-    buildNextInstall();
-
     aEvent.preventDefault();
   }
 };
--- a/toolkit/mozapps/extensions/extensions.manifest
+++ b/toolkit/mozapps/extensions/extensions.manifest
@@ -8,18 +8,16 @@ category update-timer nsBlocklistService
 #ifndef MOZ_WIDGET_GONK
 component {4399533d-08d1-458c-a87a-235f74451cfa} addonManager.js
 contract @mozilla.org/addons/integration;1 {4399533d-08d1-458c-a87a-235f74451cfa}
 #ifndef MOZ_WIDGET_ANDROID
 category update-timer addonManager @mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400
 #endif
 component {7beb3ba8-6ec3-41b4-b67c-da89b8518922} amContentHandler.js
 contract @mozilla.org/uriloader/content-handler;1?type=application/x-xpinstall {7beb3ba8-6ec3-41b4-b67c-da89b8518922}
-component {0f38e086-89a3-40a5-8ffc-9b694de1d04a} amWebInstallListener.js
-contract @mozilla.org/addons/web-install-listener;1 {0f38e086-89a3-40a5-8ffc-9b694de1d04a}
 component {9df8ef2b-94da-45c9-ab9f-132eb55fddf1} amInstallTrigger.js
 contract @mozilla.org/addons/installtrigger;1 {9df8ef2b-94da-45c9-ab9f-132eb55fddf1}
 category JavaScript-global-property InstallTrigger @mozilla.org/addons/installtrigger;1
 #ifndef MOZ_WIDGET_ANDROID
 category addon-provider-module PluginProvider resource://gre/modules/addons/PluginProvider.jsm
 #endif
 category addon-provider-module GMPProvider resource://gre/modules/addons/GMPProvider.jsm
 #endif
--- a/toolkit/mozapps/extensions/moz.build
+++ b/toolkit/mozapps/extensions/moz.build
@@ -10,27 +10,26 @@ if CONFIG['MOZ_BUILD_APP'] == 'mobile/an
     DEFINES['MOZ_FENNEC'] = True
 
 DIRS += ['internal']
 TEST_DIRS += ['test']
 
 XPIDL_SOURCES += [
     'amIAddonManager.idl',
     'amIAddonPathService.idl',
-    'amIWebInstallListener.idl',
+    'amIWebInstallPrompt.idl',
 ]
 
 XPIDL_MODULE = 'extensions'
 
 EXTRA_COMPONENTS += [
     'addonManager.js',
     'amContentHandler.js',
     'amInstallTrigger.js',
     'amWebAPI.js',
-    'amWebInstallListener.js',
     'nsBlocklistService.js',
     'nsBlocklistServiceContent.js',
 ]
 
 EXTRA_PP_COMPONENTS += [
     'extensions.manifest',
 ]
 
--- a/toolkit/mozapps/extensions/test/browser/browser_bug567127.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug567127.js
@@ -3,134 +3,104 @@
  */
 
 // Tests bug 567127 - Add install button to the add-ons manager
 
 var MockFilePicker = SpecialPowers.MockFilePicker;
 MockFilePicker.init(window);
 
 var gManagerWindow;
-var gSawInstallNotification = false;
 
-// This listens for the next opened window and checks it is of the right url.
-// opencallback is called when the new window is fully loaded
-// closecallback is called when the window is closed
-function WindowOpenListener(url, opencallback, closecallback) {
-  this.url = url;
-  this.opencallback = opencallback;
-  this.closecallback = closecallback;
-
-  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                     .getService(Components.interfaces.nsIWindowMediator);
-  wm.addListener(this);
-}
-
-WindowOpenListener.prototype = {
-  url: null,
-  opencallback: null,
-  closecallback: null,
-  window: null,
-  domwindow: null,
+function checkInstallConfirmation(...urls) {
+  return new Promise(resolve => {
+    let nurls = urls.length;
 
-  handleEvent(event) {
-    is(this.domwindow.document.location.href, this.url, "Should have opened the correct window");
+    let notificationCount = 0;
+    let observer = {
+      observe: function(aSubject, aTopic, aData) {
+        var installInfo = aSubject.wrappedJSObject;
+        if (gTestInWindow)
+          is(installInfo.browser, null, "Notification should have a null browser");
+        else
+          isnot(installInfo.browser, null, "Notification should have non-null browser");
+        notificationCount++;
+      }
+    };
+    Services.obs.addObserver(observer, "addon-install-started", false);
 
-    this.domwindow.removeEventListener("load", this, false);
-    // Allow any other load handlers to execute
-    var self = this;
-    executeSoon(function() { self.opencallback(self.domwindow); } );
-  },
+    let windows = new Set();
 
-  onWindowTitleChange(window, title) {
-  },
+    function handleDialog(window) {
+      let list = window.document.getElementById("itemList");
+      is(list.childNodes.length, 1, "Should be 1 install");
+      let idx = urls.indexOf(list.children[0].url);
+      isnot(idx, -1, "Install target is an expected url");
+      urls.splice(idx, 1);
 
-  onOpenWindow(window) {
-    if (this.window)
-      return;
+      window.document.documentElement.cancelDialog();
+    }
 
-    this.window = window;
-    this.domwindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                           .getInterface(Components.interfaces.nsIDOMWindow);
-    this.domwindow.addEventListener("load", this, false);
-  },
+    let listener = {
+      handleEvent(event) {
+        let window = event.currentTarget;
+        is(window.document.location.href, INSTALL_URI, "Should have opened the correct window");
 
-  onCloseWindow(window) {
-    if (this.window != window)
-      return;
+        executeSoon(() => handleDialog(window));
+      },
+
+      onWindowTitleChange() { },
 
-    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                       .getService(Components.interfaces.nsIWindowMediator);
-    wm.removeListener(this);
-    this.opencallback = null;
-    this.window = null;
-    this.domwindow = null;
+      onOpenWindow(window) {
+        windows.add(window);
+        let domwindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIDOMWindow);
+        domwindow.addEventListener("load", this, false, {once: true});
+      },
 
-    // Let the window close complete
-    executeSoon(this.closecallback);
-    this.closecallback = null;
-  }
-};
-
+      onCloseWindow(window) {
+        if (!windows.has(window)) {
+          return;
+        }
+        windows.delete(window);
 
-var gInstallNotificationObserver = {
-  observe(aSubject, aTopic, aData) {
-    var installInfo = aSubject.QueryInterface(Ci.amIWebInstallInfo);
-    if (gTestInWindow)
-      is(installInfo.browser, null, "Notification should have a null browser");
-    else
-      isnot(installInfo.browser, null, "Notification should have non-null browser");
-    gSawInstallNotification = true;
-    Services.obs.removeObserver(this, "addon-install-started");
-  }
-};
+        if (windows.size > 0) {
+          return;
+        }
 
+        is(urls.length, 0, "Saw install dialogs for all expected urls");
+
+        let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+                             .getService(Ci.nsIWindowMediator);
+        wm.removeListener(listener);
 
-function test_confirmation(aWindow, aExpectedURLs) {
-  var list = aWindow.document.getElementById("itemList");
-  is(list.childNodes.length, aExpectedURLs.length, "Should be the right number of installs");
+        is(notificationCount, nurls, `Saw ${nurls} addon-install-started notifications`);
+        Services.obs.removeObserver(observer, "addon-install-started");
 
-  for (let url of aExpectedURLs) {
-    let found = false;
-    for (let node of list.children) {
-      if (node.url == url) {
-        found = true;
-        break;
+        resolve();
       }
-    }
-    ok(found, "Should have seen " + url + " in the list");
-  }
+    };
 
-  aWindow.document.documentElement.cancelDialog();
+    let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+                         .getService(Ci.nsIWindowMediator);
+    wm.addListener(listener);
+  });
 }
 
 add_task(function* test_install_from_file() {
   gManagerWindow = yield open_manager("addons://list/extension");
 
   var filePaths = [
                    get_addon_file_url("browser_bug567127_1.xpi"),
                    get_addon_file_url("browser_bug567127_2.xpi")
                   ];
   MockFilePicker.returnFiles = filePaths.map(aPath => aPath.file);
 
-  Services.obs.addObserver(gInstallNotificationObserver,
-                           "addon-install-started", false);
-
   // Set handler that executes the core test after the window opens,
   // and resolves the promise when the window closes
-  let pInstallURIClosed = new Promise((resolve, reject) => {
-    new WindowOpenListener(INSTALL_URI, function(aWindow) {
-      try {
-        test_confirmation(aWindow, filePaths.map(aPath => aPath.spec));
-      } catch (e) {
-        reject(e);
-      }
-    }, resolve);
-  });
+  let pInstallURIClosed = checkInstallConfirmation(...filePaths.map(path => path.spec));
 
   gManagerWindow.gViewController.doCommand("cmd_installFromFile");
 
   yield pInstallURIClosed;
 
-  is(gSawInstallNotification, true, "Should have seen addon-install-started notification.");
-
   MockFilePicker.cleanup();
   yield close_manager(gManagerWindow);
 });
--- a/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js
@@ -11,224 +11,166 @@
 // Instead of loading EventUtils.js into the test scope in browser-test.js for all tests,
 // we only need EventUtils.js for a few files which is why we are using loadSubScript.
 var gManagerWindow;
 var EventUtils = {};
 this._scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                      getService(Ci.mozIJSSubScriptLoader);
 this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
-// This listens for the next opened window and checks it is of the right url.
-// opencallback is called when the new window is fully loaded
-// closecallback is called when the window is closed
-function WindowOpenListener(url, opencallback, closecallback) {
-  this.url = url;
-  this.opencallback = opencallback;
-  this.closecallback = closecallback;
+function checkInstallConfirmation(...urls) {
+  let nurls = urls.length;
 
-  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                     .getService(Components.interfaces.nsIWindowMediator);
-  wm.addListener(this);
-}
+  let notificationCount = 0;
+  let observer = {
+    observe: function(aSubject, aTopic, aData) {
+      var installInfo = aSubject.wrappedJSObject;
+      if (gTestInWindow)
+        is(installInfo.browser, null, "Notification should have a null browser");
+      else
+        isnot(installInfo.browser, null, "Notification should have non-null browser");
+      notificationCount++;
+    }
+  };
+  Services.obs.addObserver(observer, "addon-install-started", false);
+
+  let windows = new Set();
 
-WindowOpenListener.prototype = {
-  url: null,
-  opencallback: null,
-  closecallback: null,
-  window: null,
-  domwindow: null,
-
-  handleEvent(event) {
-    is(this.domwindow.document.location.href, this.url, "Should have opened the correct window");
+  function handleDialog(window) {
+    let list = window.document.getElementById("itemList");
+    is(list.childNodes.length, 1, "Should be 1 install");
+    let idx = urls.indexOf(list.children[0].url);
+    isnot(idx, -1, "Install target is an expected url");
+    urls.splice(idx, 1);
 
-    this.domwindow.removeEventListener("load", this, false);
-    // Allow any other load handlers to execute
-    var self = this;
-    executeSoon(function() { self.opencallback(self.domwindow); } );
-  },
+    window.document.documentElement.cancelDialog();
+  }
 
-  onWindowTitleChange(window, title) {
-  },
+  let listener = {
+    handleEvent(event) {
+      let window = event.currentTarget;
+      is(window.document.location.href, INSTALL_URI, "Should have opened the correct window");
 
-  onOpenWindow(window) {
-    if (this.window)
-      return;
+      executeSoon(() => handleDialog(window));
+    },
 
-    this.window = window;
-    this.domwindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                           .getInterface(Components.interfaces.nsIDOMWindow);
-    this.domwindow.addEventListener("load", this, false);
-  },
+    onWindowTitleChange() { },
 
-  onCloseWindow(window) {
-    if (this.window != window)
-      return;
+    onOpenWindow(window) {
+      windows.add(window);
+      let domwindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIDOMWindow);
+      domwindow.addEventListener("load", this, false, {once: true});
+    },
 
-    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                       .getService(Components.interfaces.nsIWindowMediator);
-    wm.removeListener(this);
-    this.opencallback = null;
-    this.window = null;
-    this.domwindow = null;
+    onCloseWindow(window) {
+      if (!windows.has(window)) {
+        return;
+      }
+      windows.delete(window);
 
-    // Let the window close complete
-    executeSoon(this.closecallback);
-    this.closecallback = null;
-  }
-};
+      if (windows.size > 0) {
+        return;
+      }
+
+      is(urls.length, 0, "Saw install dialogs for all expected urls");
+
+      let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+                           .getService(Ci.nsIWindowMediator);
+      wm.removeListener(listener);
 
-var gSawInstallNotification = false;
-var gInstallNotificationObserver = {
-  observe(aSubject, aTopic, aData) {
-    var installInfo = aSubject.QueryInterface(Ci.amIWebInstallInfo);
-    if (gTestInWindow)
-      is(installInfo.browser, null, "Notification should have a null browser");
-    else
-      isnot(installInfo.browser, null, "Notification should have non-null browser");
-    gSawInstallNotification = true;
-    Services.obs.removeObserver(this, "addon-install-started");
-  }
-};
+      is(notificationCount, nurls, `Saw ${nurls} addon-install-started notifications`);
+      Services.obs.removeObserver(observer, "addon-install-started");
 
+      executeSoon(run_next_test);
+    }
+  };
+
+  let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+                       .getService(Ci.nsIWindowMediator);
+  wm.addListener(listener);
+}
 
 function test() {
   waitForExplicitFinish();
 
   open_manager("addons://list/extension", function(aWindow) {
     gManagerWindow = aWindow;
     run_next_test();
   });
 }
 
 function end_test() {
   close_manager(gManagerWindow, function() {
     finish();
   });
 }
 
-function test_confirmation(aWindow, aExpectedURLs) {
-  var list = aWindow.document.getElementById("itemList");
-  is(list.childNodes.length, aExpectedURLs.length, "Should be the right number of installs");
-
-  for (let url of aExpectedURLs) {
-    let found = false;
-    for (let node of list.children) {
-      if (node.url == url) {
-        found = true;
-        break;
-      }
-    }
-    ok(found, "Should have seen " + url + " in the list");
-  }
-
-  aWindow.document.documentElement.cancelDialog();
-}
-
 // Simulates dropping a URL onto the manager
 add_test(function() {
   var url = TESTROOT + "addons/browser_dragdrop1.xpi";
 
-  Services.obs.addObserver(gInstallNotificationObserver,
-                           "addon-install-started", false);
-
-  new WindowOpenListener(INSTALL_URI, function(aWindow) {
-    test_confirmation(aWindow, [url]);
-  }, function() {
-    is(gSawInstallNotification, true, "Should have seen addon-install-started notification.");
-    run_next_test();
-  });
+  checkInstallConfirmation(url);
 
   var viewContainer = gManagerWindow.document.getElementById("view-port");
   var effect = EventUtils.synthesizeDrop(viewContainer, viewContainer,
                [[{type: "text/x-moz-url", data: url}]],
                "copy", gManagerWindow);
   is(effect, "copy", "Drag should be accepted");
 });
 
 // Simulates dropping a file onto the manager
 add_test(function() {
   var fileurl = get_addon_file_url("browser_dragdrop1.xpi");
 
-  Services.obs.addObserver(gInstallNotificationObserver,
-                           "addon-install-started", false);
-
-  new WindowOpenListener(INSTALL_URI, function(aWindow) {
-    test_confirmation(aWindow, [fileurl.spec]);
-  }, function() {
-    is(gSawInstallNotification, true, "Should have seen addon-install-started notification.");
-    run_next_test();
-  });
+  checkInstallConfirmation(fileurl.spec);
 
   var viewContainer = gManagerWindow.document.getElementById("view-port");
   var effect = EventUtils.synthesizeDrop(viewContainer, viewContainer,
                [[{type: "application/x-moz-file", data: fileurl.file}]],
                "copy", gManagerWindow);
   is(effect, "copy", "Drag should be accepted");
 });
 
 // Simulates dropping two urls onto the manager
 add_test(function() {
   var url1 = TESTROOT + "addons/browser_dragdrop1.xpi";
   var url2 = TESTROOT2 + "addons/browser_dragdrop2.xpi";
 
-  Services.obs.addObserver(gInstallNotificationObserver,
-                           "addon-install-started", false);
-
-  new WindowOpenListener(INSTALL_URI, function(aWindow) {
-    test_confirmation(aWindow, [url1, url2]);
-  }, function() {
-    is(gSawInstallNotification, true, "Should have seen addon-install-started notification.");
-    run_next_test();
-  });
+  checkInstallConfirmation(url1, url2);
 
   var viewContainer = gManagerWindow.document.getElementById("view-port");
   var effect = EventUtils.synthesizeDrop(viewContainer, viewContainer,
                [[{type: "text/x-moz-url", data: url1}],
                 [{type: "text/x-moz-url", data: url2}]],
                "copy", gManagerWindow);
   is(effect, "copy", "Drag should be accepted");
 });
 
 // Simulates dropping two files onto the manager
 add_test(function() {
   var fileurl1 = get_addon_file_url("browser_dragdrop1.xpi");
   var fileurl2 = get_addon_file_url("browser_dragdrop2.xpi");
 
-  Services.obs.addObserver(gInstallNotificationObserver,
-                           "addon-install-started", false);
-
-  new WindowOpenListener(INSTALL_URI, function(aWindow) {
-    test_confirmation(aWindow, [fileurl1.spec, fileurl2.spec]);
-  }, function() {
-    is(gSawInstallNotification, true, "Should have seen addon-install-started notification.");
-    run_next_test();
-  });
+  checkInstallConfirmation(fileurl1.spec, fileurl2.spec);
 
   var viewContainer = gManagerWindow.document.getElementById("view-port");
   var effect = EventUtils.synthesizeDrop(viewContainer, viewContainer,
                [[{type: "application/x-moz-file", data: fileurl1.file}],
                 [{type: "application/x-moz-file", data: fileurl2.file}]],
                "copy", gManagerWindow);
   is(effect, "copy", "Drag should be accepted");
 });
 
 // Simulates dropping a file and a url onto the manager (weird, but should still work)
 add_test(function() {
   var url = TESTROOT + "addons/browser_dragdrop1.xpi";
   var fileurl = get_addon_file_url("browser_dragdrop2.xpi");
 
-  Services.obs.addObserver(gInstallNotificationObserver,
-                           "addon-install-started", false);
-
-  new WindowOpenListener(INSTALL_URI, function(aWindow) {
-    test_confirmation(aWindow, [url, fileurl.spec]);
-  }, function() {
-    is(gSawInstallNotification, true, "Should have seen addon-install-started notification.");
-    run_next_test();
-  });
+  checkInstallConfirmation(url, fileurl.spec);
 
   var viewContainer = gManagerWindow.document.getElementById("view-port");
   var effect = EventUtils.synthesizeDrop(viewContainer, viewContainer,
                [[{type: "text/x-moz-url", data: url}],
                 [{type: "application/x-moz-file", data: fileurl.file}]],
                "copy", gManagerWindow);
   is(effect, "copy", "Drag should be accepted");
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
@@ -469,24 +469,20 @@ function Pmanual_update(aVersion) {
     let completePromises = [];
     for (let install of installs) {
       completePromises.push(new Promise(resolve => {
         install.addListener({
           onDownloadCancelled: resolve,
           onInstallEnded: resolve
         })
       }));
+
+      AddonManager.installAddonFromAOM(null, null, install);
     }
 
-    // Use the default web installer to cancel/allow installs based on whether
-    // the add-on is valid or not.
-    let webInstaller = Cc["@mozilla.org/addons/web-install-listener;1"]
-                       .getService(Ci.amIWebInstallListener);
-    webInstaller.onWebInstallRequested(null, null, installs);
-
     return Promise.all(completePromises);
   });
 }
 
 // Checks that an add-ons properties match expected values
 function check_addon(aAddon, aExpectedVersion, aExpectedUserDisabled,
                      aExpectedSoftDisabled, aExpectedState) {
   do_check_neq(aAddon, null);
--- a/toolkit/mozapps/extensions/test/xpinstall/head.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/head.js
@@ -370,17 +370,17 @@ var Harness = {
   checkTestEnded() {
     if (--this.pendingCount == 0 && !this.waitingForEvent)
       this.endTest();
   },
 
   // nsIObserver
 
   observe(subject, topic, data) {
-    var installInfo = subject.QueryInterface(Components.interfaces.amIWebInstallInfo);
+    var installInfo = subject.wrappedJSObject;
     switch (topic) {
     case "addon-install-started":
       is(this.runningInstalls.length, installInfo.installs.length,
          "Should have seen the expected number of installs started");
       break;
     case "addon-install-disabled":
       this.installDisabled(installInfo);
       break;