Bug 1139656 - Implement the first pieces of the all-doorhanger install flow for add-ons installed from websites. r=dtownsend
☠☠ backed out by 6dd5b23f5558 ☠ ☠
authorDão Gottwald <dao@mozilla.com>
Tue, 24 Mar 2015 18:54:59 +0100
changeset 265730 37bd20ca096a57932b1bb093f31e0e15033af38b
parent 265729 983493507fe4645be2cb0410fdb357ecc18661c9
child 265731 aa1bd6ac6c215b673aeab75ff624dc95a939334b
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdtownsend
bugs1139656
milestone39.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 1139656 - Implement the first pieces of the all-doorhanger install flow for add-ons installed from websites. r=dtownsend
browser/app/profile/firefox.js
browser/base/content/browser-addons.js
browser/base/content/browser.js
browser/base/content/popup-notifications.inc
browser/base/content/test/general/browser_bug553455.js
browser/base/content/urlbarBindings.xml
browser/locales/en-US/chrome/browser/browser.properties
browser/themes/linux/browser.css
browser/themes/osx/browser.css
browser/themes/windows/browser.css
toolkit/content/widgets/notification.xml
toolkit/modules/PopupNotifications.jsm
toolkit/mozapps/extensions/amWebInstallListener.js
toolkit/mozapps/extensions/test/browser/head.js
toolkit/mozapps/extensions/test/xpinstall/head.js
toolkit/themes/linux/global/global.css
toolkit/themes/linux/global/notification.css
toolkit/themes/osx/global/global.css
toolkit/themes/osx/global/notification.css
toolkit/themes/windows/global/global.css
toolkit/themes/windows/global/notification.css
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -32,16 +32,18 @@ pref("extensions.strictCompatibility", f
 
 // Specifies a minimum maxVersion an addon needs to say it's compatible with
 // for it to be compatible by default.
 pref("extensions.minCompatibleAppVersion", "4.0");
 // Temporary preference to forcibly make themes more safe with Australis even if
 // extensions.checkCompatibility=false has been set.
 pref("extensions.checkCompatibility.temporaryThemeOverride_minAppVersion", "29.0a1");
 
+pref("xpinstall.customConfirmationUI", true);
+
 // Preferences for AMO integration
 pref("extensions.getAddons.cache.enabled", true);
 pref("extensions.getAddons.maxResults", 15);
 pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%");
 pref("extensions.getAddons.getWithPerformance.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%");
 pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%&platform=%OS%&appver=%VERSION%");
 pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%/%COMPATIBILITY_MODE%?src=firefox");
 pref("extensions.webservice.discoverURL", "https://services.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%");
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -43,19 +43,25 @@ const gXPInstallObserver = {
     var brandShortName = brandBundle.getString("brandShortName");
 
     var notificationID = aTopic;
     // Make notifications persist a minimum of 30 seconds
     var options = {
       timeout: Date.now() + 30000
     };
 
+    try {
+      options.originHost = installInfo.originatingURI.host;
+    } catch (e) {
+      // originatingURI might be missing or 'host' might throw for non-nsStandardURL nsIURIs.
+    }
+
     switch (aTopic) {
-    case "addon-install-disabled":
-      notificationID = "xpinstall-disabled"
+    case "addon-install-disabled": {
+      notificationID = "xpinstall-disabled";
 
       if (gPrefService.prefIsLocked("xpinstall.enabled")) {
         messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
         buttons = [];
       }
       else {
         messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
 
@@ -65,71 +71,78 @@ const gXPInstallObserver = {
           callback: function editPrefs() {
             gPrefService.setBoolPref("xpinstall.enabled", true);
           }
         };
       }
 
       PopupNotifications.show(browser, notificationID, messageString, anchorID,
                               action, null, options);
-      break;
-    case "addon-install-blocked":
-      let originatingHost;
-      try {
-        originatingHost = installInfo.originatingURI.host;
-      } catch (ex) {
+      break; }
+    case "addon-install-blocked": {
+      if (!options.originHost) {
         // Need to deal with missing originatingURI and with about:/data: URIs more gracefully,
         // see bug 1063418 - but for now, bail:
         return;
       }
-      messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
-                        [brandShortName, originatingHost]);
+      messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage",
+                        [brandShortName]);
 
       let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
       action = {
         label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
         accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
         callback: function() {
           secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH);
           installInfo.install();
         }
       };
 
       secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
       PopupNotifications.show(browser, notificationID, messageString, anchorID,
                               action, null, options);
-      break;
-    case "addon-install-started":
-      var needsDownload = function needsDownload(aInstall) {
+      break; }
+    case "addon-install-started": {
+      let needsDownload = function needsDownload(aInstall) {
         return aInstall.state != AddonManager.STATE_DOWNLOADED;
       }
       // If all installs have already been downloaded then there is no need to
       // show the download progress
       if (!installInfo.installs.some(needsDownload))
         return;
       notificationID = "addon-progress";
-      messageString = gNavigatorBundle.getString("addonDownloading");
+      messageString = gNavigatorBundle.getString("addonDownloadingAndVerifying");
       messageString = PluralForm.get(installInfo.installs.length, messageString);
       options.installs = installInfo.installs;
       options.contentWindow = browser.contentWindow;
       options.sourceURI = browser.currentURI;
-      options.eventCallback = function(aEvent) {
-        if (aEvent != "removed")
-          return;
-        options.contentWindow = null;
-        options.sourceURI = null;
+      options.eventCallback = (aEvent) => {
+        switch (aEvent) {
+          case "removed":
+            options.contentWindow = null;
+            options.sourceURI = null;
+            break;
+        }
       };
-      PopupNotifications.show(browser, notificationID, messageString, anchorID,
-                              null, null, options);
-      break;
-    case "addon-install-failed":
+      let notification = PopupNotifications.show(browser, notificationID, messageString,
+                                                 anchorID, null, null, options);
+      notification._startTime = Date.now();
+
+      let cancelButton = document.getElementById("addon-progress-cancel");
+      cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
+      cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
+
+      let acceptButton = document.getElementById("addon-progress-accept");
+      acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
+      acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
+      break; }
+    case "addon-install-failed": {
       // TODO This isn't terribly ideal for the multiple failure case
       for (let install of installInfo.installs) {
-        let host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
-                   installInfo.originatingURI.host;
+        let host = options.originHost;
         if (!host)
           host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
                  install.sourceURI.host;
 
         let error = (host || install.error == 0) ? "addonError" : "addonLocalError";
         if (install.error != 0)
           error += install.error;
         else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
@@ -142,19 +155,110 @@ const gXPInstallObserver = {
         if (host)
           messageString = messageString.replace("#2", host);
         messageString = messageString.replace("#3", brandShortName);
         messageString = messageString.replace("#4", Services.appinfo.version);
 
         PopupNotifications.show(browser, notificationID, messageString, anchorID,
                                 action, null, options);
       }
-      break;
-    case "addon-install-complete":
-      var needsRestart = installInfo.installs.some(function(i) {
+      this._removeProgressNotification(browser);
+      break; }
+    case "addon-install-confirmation": {
+      options.eventCallback = (aEvent) => {
+        switch (aEvent) {
+          case "removed":
+            if (installInfo) {
+              for (let install of installInfo.installs)
+                install.cancel();
+            }
+            this.acceptInstallation = null;
+            break;
+          case "shown":
+            let addonList = document.getElementById("addon-install-confirmation-content");
+            while (addonList.firstChild)
+              addonList.firstChild.remove();
+
+            for (let install of installInfo.installs) {
+              let container = document.createElement("hbox");
+              let name = document.createElement("label");
+              let author = document.createElement("label");
+              name.setAttribute("value", install.addon.name);
+              author.setAttribute("value", !install.addon.creator ? "" :
+                gNavigatorBundle.getFormattedString("addonConfirmInstall.author", [install.addon.creator]));
+              name.setAttribute("class", "addon-install-confirmation-name");
+              author.setAttribute("class", "addon-install-confirmation-author");
+              container.appendChild(name);
+              container.appendChild(author);
+              addonList.appendChild(container);
+            }
+
+            this.acceptInstallation = () => {
+              for (let install of installInfo.installs)
+                install.install();
+              installInfo = null;
+
+              Services.telemetry
+                      .getHistogramById("SECURITY_UI")
+                      .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
+            };
+            break;
+        }
+      };
+
+      messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
+      messageString = PluralForm.get(installInfo.installs.length, messageString);
+      messageString = messageString.replace("#1", brandShortName);
+      messageString = messageString.replace("#2", installInfo.installs.length);
+
+      let cancelButton = document.getElementById("addon-install-confirmation-cancel");
+      cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
+      cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
+
+      let acceptButton = document.getElementById("addon-install-confirmation-accept");
+      acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
+      acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
+
+      let showNotification = () => {
+        // The download may have been cancelled during the security delay
+        if (!PopupNotifications.getNotification("addon-progress", browser))
+          return;
+
+        let tab = gBrowser.getTabForBrowser(browser);
+        if (tab)
+          gBrowser.selectedTab = tab;
+
+        if (PopupNotifications.isPanelOpen) {
+          let rect = document.getElementById("addon-progress-notification").getBoundingClientRect();
+          let notification = document.getElementById("addon-install-confirmation-notification");
+          notification.style.minHeight = rect.height + "px";
+        }
+
+        PopupNotifications.show(browser, notificationID, messageString, anchorID,
+                                action, null, options);
+
+        this._removeProgressNotification(browser);
+
+        Services.telemetry
+                .getHistogramById("SECURITY_UI")
+                .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
+      };
+
+      let downloadDuration = 0;
+      let progressNotification = PopupNotifications.getNotification("addon-progress", browser);
+      if (progressNotification)
+        downloadDuration = Date.now() - progressNotification._startTime;
+      let securityDelay = Services.prefs.getIntPref("security.dialog_enable_delay") - downloadDuration;
+      if (securityDelay > 0)
+        setTimeout(showNotification, securityDelay);
+      else
+        showNotification();
+      break; }
+    case "addon-install-complete": {
+      let needsRestart = installInfo.installs.some(function(i) {
         return i.addon.pendingOperations != AddonManager.PENDING_NONE;
       });
 
       if (needsRestart) {
         messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
         action = {
           label: gNavigatorBundle.getString("addonInstallRestartButton"),
           accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
@@ -175,18 +279,23 @@ const gXPInstallObserver = {
 
       // Remove notificaion on dismissal, since it's possible to cancel the
       // install through the addons manager UI, making the "restart" prompt
       // irrelevant.
       options.removeOnDismissal = true;
 
       PopupNotifications.show(browser, notificationID, messageString, anchorID,
                               action, null, options);
-      break;
+      break; }
     }
+  },
+  _removeProgressNotification(aBrowser) {
+    let notification = PopupNotifications.getNotification("addon-progress", aBrowser);
+    if (notification)
+      notification.remove();
   }
 };
 
 var LightWeightThemeWebInstaller = {
   handleEvent: function (event) {
     switch (event.type) {
       case "InstallBrowserTheme":
       case "PreviewBrowserTheme":
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1218,16 +1218,17 @@ var gBrowserInit = {
     setTimeout(function() { SafeBrowsing.init(); }, 2000);
 #endif
 
     Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
+    Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
     window.messageManager.addMessageListener("Browser:URIFixup", gKeywordURIFixup);
 
     BrowserOffline.init();
     OfflineApps.init();
     IndexedDBPromptHelper.init();
 #ifdef E10S_TESTING_ONLY
     gRemoteTabsUI.init();
@@ -1529,16 +1530,17 @@ var gBrowserInit = {
       LoopUI.uninit();
       FullZoom.destroy();
 
       Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
+      Services.obs.removeObserver(gXPInstallObserver, "addon-install-confirmation");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
       window.messageManager.removeMessageListener("Browser:URIFixup", gKeywordURIFixup);
       window.messageManager.removeMessageListener("Browser:LoadURI", RedirectLoad);
 
       try {
         gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
       } catch (ex) {
         Cu.reportError(ex);
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -5,62 +5,56 @@
            footertype="promobox"
            position="after_start"
            hidden="true"
            orient="vertical"
            role="alert"/>
 
     <popupnotification id="webRTC-shareDevices-notification" hidden="true">
       <popupnotificationcontent id="webRTC-selectCamera" orient="vertical">
-        <separator class="thin"/>
         <label value="&getUserMedia.selectCamera.label;"
                accesskey="&getUserMedia.selectCamera.accesskey;"
                control="webRTC-selectCamera-menulist"/>
         <menulist id="webRTC-selectCamera-menulist">
           <menupopup id="webRTC-selectCamera-menupopup"/>
         </menulist>
       </popupnotificationcontent>
 
       <popupnotificationcontent id="webRTC-selectWindowOrScreen" orient="vertical">
-        <separator class="thin"/>
         <label id="webRTC-selectWindow-label"
                control="webRTC-selectWindow-menulist"/>
         <menulist id="webRTC-selectWindow-menulist"
                   oncommand="gWebRTCUI.updateMainActionLabel(this);">
           <menupopup id="webRTC-selectWindow-menupopup"/>
         </menulist>
         <description id="webRTC-all-windows-shared" hidden="true">&getUserMedia.allWindowsShared.message;</description>
       </popupnotificationcontent>
 
       <popupnotificationcontent id="webRTC-selectMicrophone" orient="vertical">
-        <separator class="thin"/>
         <label value="&getUserMedia.selectMicrophone.label;"
                accesskey="&getUserMedia.selectMicrophone.accesskey;"
                control="webRTC-selectMicrophone-menulist"/>
         <menulist id="webRTC-selectMicrophone-menulist">
           <menupopup id="webRTC-selectMicrophone-menupopup"/>
         </menulist>
       </popupnotificationcontent>
     </popupnotification>
 
     <popupnotification id="webapps-install-progress-notification" hidden="true">
-      <popupnotificationcontent id="webapps-install-progress-content" orient="vertical" align="start">
-        <separator class="thin"/>
-      </popupnotificationcontent>
+      <popupnotificationcontent id="webapps-install-progress-content" orient="vertical" align="start"/>
     </popupnotification>
 
     <popupnotification id="servicesInstall-notification" hidden="true">
       <popupnotificationcontent orient="vertical" align="start">
         <!-- XXX bug 974146, tests are looking for this, can't remove yet. -->
       </popupnotificationcontent>
     </popupnotification>
 
     <popupnotification id="pointerLock-notification" hidden="true">
       <popupnotificationcontent orient="vertical" align="start">
-        <separator class="thin"/>
         <label id="pointerLock-cancel">&pointerLock.notification.message;</label>
       </popupnotificationcontent>
     </popupnotification>
 
     <popupnotification id="password-notification" hidden="true">
       <popupnotificationcontent orient="vertical">
         <textbox id="password-notification-username" disabled="true"/>
         <textbox id="password-notification-password" type="password"
@@ -68,8 +62,23 @@
       </popupnotificationcontent>
     </popupnotification>
 
 #ifdef E10S_TESTING_ONLY
     <popupnotification id="enable-e10s-notification" hidden="true">
       <popupnotificationcontent orient="vertical"/>
     </popupnotification>
 #endif
+
+    <popupnotification id="addon-progress-notification" hidden="true">
+      <button id="addon-progress-cancel"
+              oncommand="this.parentNode.cancel();"/>
+      <button id="addon-progress-accept" disabled="true"/>
+    </popupnotification>
+
+    <popupnotification id="addon-install-confirmation-notification" hidden="true">
+      <popupnotificationcontent id="addon-install-confirmation-content" orient="vertical"/>
+      <button id="addon-install-confirmation-cancel"
+              oncommand="PopupNotifications.getNotification('addon-install-confirmation').remove();"/>
+      <button id="addon-install-confirmation-accept"
+              oncommand="gXPInstallObserver.acceptInstallation();
+                         PopupNotifications.getNotification('addon-install-confirmation').remove();"/>
+    </popupnotification>
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -1,92 +1,96 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const SECUREROOT = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
-const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
-const PROGRESS_NOTIFICATION = "addon-progress-notification";
+const PROGRESS_NOTIFICATION = "addon-progress";
 
 var rootDir = getRootDirectory(gTestPath);
 var path = rootDir.split('/');
 var chromeName = path[0] + '//' + path[2];
 var croot = chromeName + "/content/browser/toolkit/mozapps/extensions/test/xpinstall/";
 var jar = getJar(croot);
 if (jar) {
   var tmpdir = extractJarToTmp(jar);
   croot = 'file://' + tmpdir.path + '/';
 }
 const CHROMEROOT = croot;
 
 var gApp = document.getElementById("bundle_brand").getString("brandShortName");
 var gVersion = Services.appinfo.version;
-var check_notification;
+
+function get_observer_topic(aNotificationId) {
+  let topic = aNotificationId;
+  if (topic == "xpinstall-disabled")
+    topic = "addon-install-disabled";
+  else if (topic == "addon-progress")
+    topic = "addon-install-started";
+  return topic;
+}
 
 function wait_for_progress_notification(aCallback) {
   wait_for_notification(PROGRESS_NOTIFICATION, aCallback, "popupshowing");
 }
 
 function wait_for_notification(aId, aCallback, aEvent = "popupshown") {
   info("Waiting for " + aId + " notification");
-  check_notification = function() {
+
+  let topic = get_observer_topic(aId);
+  function observer(aSubject, aTopic, aData) {
     // Ignore the progress notification unless that is the notification we want
-    if (aId != PROGRESS_NOTIFICATION && PopupNotifications.panel.childNodes[0].id == PROGRESS_NOTIFICATION)
+    if (aId != PROGRESS_NOTIFICATION &&
+        aTopic == get_observer_topic(PROGRESS_NOTIFICATION))
       return;
 
-    PopupNotifications.panel.removeEventListener(aEvent, check_notification, false);
+    Services.obs.removeObserver(observer, topic);
+
+    if (PopupNotifications.isPanelOpen)
+      executeSoon(verify);
+    else
+      PopupNotifications.panel.addEventListener(aEvent, event_listener);
+  }
+
+  function event_listener() {
+    // Ignore the progress notification unless that is the notification we want
+    if (aId != PROGRESS_NOTIFICATION &&
+        PopupNotifications.panel.childNodes[0].id == PROGRESS_NOTIFICATION + "-notification")
+      return;
+
+    PopupNotifications.panel.removeEventListener(aEvent, event_listener);
+
+    verify();
+  }
+
+  function verify() {
     info("Saw a notification");
+    ok(PopupNotifications.isPanelOpen, "Panel should be open");
     is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
-    if (PopupNotifications.panel.childNodes.length)
-      is(PopupNotifications.panel.childNodes[0].id, aId, "Should have seen the right notification");
+    if (PopupNotifications.panel.childNodes.length) {
+      is(PopupNotifications.panel.childNodes[0].id,
+         aId + "-notification", "Should have seen the right notification");
+    }
     aCallback(PopupNotifications.panel);
-  };
-  PopupNotifications.panel.addEventListener(aEvent, check_notification, false);
+  }
+
+  Services.obs.addObserver(observer, topic, false);
 }
 
 function wait_for_notification_close(aCallback) {
   info("Waiting for notification to close");
   PopupNotifications.panel.addEventListener("popuphidden", function() {
     PopupNotifications.panel.removeEventListener("popuphidden", arguments.callee, false);
     aCallback();
   }, false);
 }
 
-function wait_for_install_dialog(aCallback) {
-  info("Waiting for install dialog");
-  Services.wm.addListener({
-    onOpenWindow: function(aXULWindow) {
-      info("Install dialog opened, waiting for focus");
-      Services.wm.removeListener(this);
-
-      var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                                .getInterface(Ci.nsIDOMWindow);
-      waitForFocus(function() {
-        info("Saw install dialog");
-        is(domwindow.document.location.href, XPINSTALL_URL, "Should have seen the right window open");
-
-        // Override the countdown timer on the accept button
-        var button = domwindow.document.documentElement.getButton("accept");
-        button.disabled = false;
-
-        aCallback(domwindow);
-      }, domwindow);
-    },
-
-    onCloseWindow: function(aXULWindow) {
-    },
-
-    onWindowTitleChange: function(aXULWindow, aNewTitle) {
-    }
-  });
-}
-
 function wait_for_single_notification(aCallback) {
   function inner_waiter() {
     info("Waiting for single notification");
     // Notification should never close while we wait
     ok(PopupNotifications.isPanelOpen, "Notification should still be open");
     if (PopupNotifications.panel.childNodes.length == 2) {
       executeSoon(inner_waiter);
       return;
@@ -109,17 +113,17 @@ function setup_redirect(aSettings) {
   req.send(null);
 }
 
 var TESTS = [
 function test_disabled_install() {
   Services.prefs.setBoolPref("xpinstall.enabled", false);
 
   // Wait for the disabled notification
-  wait_for_notification("xpinstall-disabled-notification", function(aPanel) {
+  wait_for_notification("xpinstall-disabled", function(aPanel) {
     let notification = aPanel.childNodes[0];
     is(notification.button.label, "Enable", "Should have seen the right button");
     is(notification.getAttribute("label"),
        "Software installation is currently disabled. Click Enable and try again.");
 
     wait_for_notification_close(function() {
       try {
         ok(Services.prefs.getBoolPref("xpinstall.enabled"), "Installation should be enabled");
@@ -146,105 +150,112 @@ function test_disabled_install() {
     "XPI": "unsigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_blocked_install() {
   // Wait for the blocked notification
-  wait_for_notification("addon-install-blocked-notification", function(aPanel) {
+  wait_for_notification("addon-install-blocked", function(aPanel) {
     let notification = aPanel.childNodes[0];
     is(notification.button.label, "Allow", "Should have seen the right button");
+    is(notification.getAttribute("originhost"), "example.com",
+       "Should have seen the right origin host");
     is(notification.getAttribute("label"),
-       gApp + " prevented this site (example.com) from asking you to install " +
-       "software on your computer.",
+       gApp + " prevented this site from asking you to install software on your computer.",
        "Should have seen the right message");
 
     // Wait for the install confirmation dialog
-    wait_for_install_dialog(function(aWindow) {
+    wait_for_notification("addon-install-confirmation", function(aPanel) {
       // Wait for the complete notification
-      wait_for_notification("addon-install-complete-notification", function(aPanel) {
+      wait_for_notification("addon-install-complete", function(aPanel) {
         let notification = aPanel.childNodes[0];
         is(notification.button.label, "Restart Now", "Should have seen the right button");
         is(notification.getAttribute("label"),
            "XPI Test will be installed after you restart " + gApp + ".",
            "Should have seen the right message");
 
         AddonManager.getAllInstalls(function(aInstalls) {
         is(aInstalls.length, 1, "Should be one pending install");
           aInstalls[0].cancel();
 
           wait_for_notification_close(runNextTest);
           gBrowser.removeTab(gBrowser.selectedTab);
         });
       });
 
-      aWindow.document.documentElement.acceptDialog();
+      document.getElementById("addon-install-confirmation-accept").click();
     });
 
     // Click on Allow
     EventUtils.synthesizeMouse(notification.button, 20, 10, {});
 
     // Notification should have changed to progress notification
     ok(PopupNotifications.isPanelOpen, "Notification should still be open");
     notification = aPanel.childNodes[0];
     is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
-
   });
 
   var triggers = encodeURIComponent(JSON.stringify({
     "XPI": "unsigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_whitelisted_install() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
+    gBrowser.selectedTab = originalTab;
+
     // Wait for the install confirmation dialog
-    wait_for_install_dialog(function(aWindow) {
+    wait_for_notification("addon-install-confirmation", function(aPanel) {
+      is(gBrowser.selectedTab, tab,
+         "tab selected in response to the addon-install-confirmation notification");
+
       // Wait for the complete notification
-      wait_for_notification("addon-install-complete-notification", function(aPanel) {
+      wait_for_notification("addon-install-complete", function(aPanel) {
         let notification = aPanel.childNodes[0];
         is(notification.button.label, "Restart Now", "Should have seen the right button");
         is(notification.getAttribute("label"),
            "XPI Test will be installed after you restart " + gApp + ".",
            "Should have seen the right message");
 
         AddonManager.getAllInstalls(function(aInstalls) {
           is(aInstalls.length, 1, "Should be one pending install");
           aInstalls[0].cancel();
 
           Services.perms.remove("example.com", "install");
           wait_for_notification_close(runNextTest);
           gBrowser.removeTab(gBrowser.selectedTab);
         });
       });
 
-      aWindow.document.documentElement.acceptDialog();
+      document.getElementById("addon-install-confirmation-accept").click();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
     "XPI": "unsigned.xpi"
   }));
-  gBrowser.selectedTab = gBrowser.addTab();
+  let originalTab = gBrowser.selectedTab;
+  let tab = gBrowser.addTab();
+  gBrowser.selectedTab = tab;
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_failed_download() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the failed notification
-    wait_for_notification("addon-install-failed-notification", function(aPanel) {
+    wait_for_notification("addon-install-failed", function(aPanel) {
       let notification = aPanel.childNodes[0];
       is(notification.getAttribute("label"),
          "The add-on could not be downloaded because of a connection failure " +
          "on example.com.",
          "Should have seen the right message");
 
       Services.perms.remove("example.com", "install");
       wait_for_notification_close(runNextTest);
@@ -261,17 +272,17 @@ function test_failed_download() {
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_corrupt_file() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the failed notification
-    wait_for_notification("addon-install-failed-notification", function(aPanel) {
+    wait_for_notification("addon-install-failed", function(aPanel) {
       let notification = aPanel.childNodes[0];
       is(notification.getAttribute("label"),
          "The add-on downloaded from example.com could not be installed " +
          "because it appears to be corrupt.",
          "Should have seen the right message");
 
       Services.perms.remove("example.com", "install");
       wait_for_notification_close(runNextTest);
@@ -288,17 +299,17 @@ function test_corrupt_file() {
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_incompatible() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the failed notification
-    wait_for_notification("addon-install-failed-notification", function(aPanel) {
+    wait_for_notification("addon-install-failed", function(aPanel) {
       let notification = aPanel.childNodes[0];
       is(notification.getAttribute("label"),
          "XPI Test could not be installed because it is not compatible with " +
          gApp + " " + gVersion + ".",
          "Should have seen the right message");
 
       Services.perms.remove("example.com", "install");
       wait_for_notification_close(runNextTest);
@@ -315,19 +326,19 @@ function test_incompatible() {
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_restartless() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the install confirmation dialog
-    wait_for_install_dialog(function(aWindow) {
+    wait_for_notification("addon-install-confirmation", function(aPanel) {
       // Wait for the complete notification
-      wait_for_notification("addon-install-complete-notification", function(aPanel) {
+      wait_for_notification("addon-install-complete", function(aPanel) {
         let notification = aPanel.childNodes[0];
         is(notification.getAttribute("label"),
            "XPI Test has been installed successfully.",
            "Should have seen the right message");
 
         AddonManager.getAllInstalls(function(aInstalls) {
           is(aInstalls.length, 0, "Should be no pending installs");
 
@@ -336,17 +347,17 @@ function test_restartless() {
 
             Services.perms.remove("example.com", "install");
             wait_for_notification_close(runNextTest);
             gBrowser.removeTab(gBrowser.selectedTab);
           });
         });
       });
 
-      aWindow.document.documentElement.acceptDialog();
+      document.getElementById("addon-install-confirmation-accept").click();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
     "XPI": "restartless.xpi"
@@ -354,19 +365,19 @@ function test_restartless() {
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_multiple() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the install confirmation dialog
-    wait_for_install_dialog(function(aWindow) {
+    wait_for_notification("addon-install-confirmation", function(aPanel) {
       // Wait for the complete notification
-      wait_for_notification("addon-install-complete-notification", function(aPanel) {
+      wait_for_notification("addon-install-complete", function(aPanel) {
         let notification = aPanel.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");
 
         AddonManager.getAllInstalls(function(aInstalls) {
           is(aInstalls.length, 1, "Should be one pending install");
@@ -377,17 +388,17 @@ function test_multiple() {
 
             Services.perms.remove("example.com", "install");
             wait_for_notification_close(runNextTest);
             gBrowser.removeTab(gBrowser.selectedTab);
           });
         });
       });
 
-      aWindow.document.documentElement.acceptDialog();
+      document.getElementById("addon-install-confirmation-accept").click();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
     "Unsigned XPI": "unsigned.xpi",
@@ -396,35 +407,35 @@ function test_multiple() {
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_url() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the install confirmation dialog
-    wait_for_install_dialog(function(aWindow) {
+    wait_for_notification("addon-install-confirmation", function(aPanel) {
       // Wait for the complete notification
-      wait_for_notification("addon-install-complete-notification", function(aPanel) {
+      wait_for_notification("addon-install-complete", function(aPanel) {
         let notification = aPanel.childNodes[0];
         is(notification.button.label, "Restart Now", "Should have seen the right button");
         is(notification.getAttribute("label"),
            "XPI Test will be installed after you restart " + gApp + ".",
            "Should have seen the right message");
 
         AddonManager.getAllInstalls(function(aInstalls) {
           is(aInstalls.length, 1, "Should be one pending install");
           aInstalls[0].cancel();
 
           wait_for_notification_close(runNextTest);
           gBrowser.removeTab(gBrowser.selectedTab);
         });
       });
 
-      aWindow.document.documentElement.acceptDialog();
+      document.getElementById("addon-install-confirmation-accept").click();
     });
   });
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "unsigned.xpi");
 },
 
 function test_localfile() {
@@ -462,17 +473,17 @@ function test_wronghost() {
     if (gBrowser.currentURI.spec != TESTROOT2 + "enabled.html")
       return;
 
     gBrowser.removeEventListener("load", arguments.callee, true);
 
     // Wait for the progress notification
     wait_for_progress_notification(function(aPanel) {
       // Wait for the complete notification
-      wait_for_notification("addon-install-failed-notification", function(aPanel) {
+      wait_for_notification("addon-install-failed", function(aPanel) {
         let notification = aPanel.childNodes[0];
         is(notification.getAttribute("label"),
            "The add-on downloaded from example.com could not be installed " +
            "because it appears to be corrupt.",
            "Should have seen the right message");
 
         wait_for_notification_close(runNextTest);
         gBrowser.removeTab(gBrowser.selectedTab);
@@ -483,19 +494,19 @@ function test_wronghost() {
   }, true);
   gBrowser.loadURI(TESTROOT2 + "enabled.html");
 },
 
 function test_reload() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the install confirmation dialog
-    wait_for_install_dialog(function(aWindow) {
+    wait_for_notification("addon-install-confirmation", function(aPanel) {
       // Wait for the complete notification
-      wait_for_notification("addon-install-complete-notification", function(aPanel) {
+      wait_for_notification("addon-install-complete", function(aPanel) {
         let notification = aPanel.childNodes[0];
         is(notification.button.label, "Restart Now", "Should have seen the right button");
         is(notification.getAttribute("label"),
            "XPI Test will be installed after you restart " + gApp + ".",
            "Should have seen the right message");
 
         function test_fail() {
           ok(false, "Reloading should not have hidden the notification");
@@ -518,17 +529,17 @@ function test_reload() {
             Services.perms.remove("example.com", "install");
             wait_for_notification_close(runNextTest);
             gBrowser.removeTab(gBrowser.selectedTab);
           });
         }, true);
         gBrowser.loadURI(TESTROOT2 + "enabled.html");
       });
 
-      aWindow.document.documentElement.acceptDialog();
+      document.getElementById("addon-install-confirmation-accept").click();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
     "Unsigned XPI": "unsigned.xpi"
@@ -536,19 +547,19 @@ function test_reload() {
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_theme() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the install confirmation dialog
-    wait_for_install_dialog(function(aWindow) {
+    wait_for_notification("addon-install-confirmation", function(aPanel) {
       // Wait for the complete notification
-      wait_for_notification("addon-install-complete-notification", function(aPanel) {
+      wait_for_notification("addon-install-complete", function(aPanel) {
         let notification = aPanel.childNodes[0];
         is(notification.button.label, "Restart Now", "Should have seen the right button");
         is(notification.getAttribute("label"),
            "Theme Test will be installed after you restart " + gApp + ".",
            "Should have seen the right message");
 
         AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", function(aAddon) {
           ok(aAddon.userDisabled, "Should be switching away from the default theme.");
@@ -561,39 +572,39 @@ function test_theme() {
 
             Services.perms.remove("example.com", "install");
             wait_for_notification_close(runNextTest);
             gBrowser.removeTab(gBrowser.selectedTab);
           });
         });
       });
 
-      aWindow.document.documentElement.acceptDialog();
+      document.getElementById("addon-install-confirmation-accept").click();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
     "Theme XPI": "theme.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_renotify_blocked() {
   // Wait for the blocked notification
-  wait_for_notification("addon-install-blocked-notification", function(aPanel) {
+  wait_for_notification("addon-install-blocked", function(aPanel) {
     let notification = aPanel.childNodes[0];
 
     wait_for_notification_close(function () {
       info("Timeouts after this probably mean bug 589954 regressed");
       executeSoon(function () {
-        wait_for_notification("addon-install-blocked-notification", function(aPanel) {
+        wait_for_notification("addon-install-blocked", function(aPanel) {
           AddonManager.getAllInstalls(function(aInstalls) {
           is(aInstalls.length, 2, "Should be two pending installs");
             aInstalls[0].cancel();
             aInstalls[1].cancel();
 
             info("Closing browser tab");
             wait_for_notification_close(runNextTest);
             gBrowser.removeTab(gBrowser.selectedTab);
@@ -614,135 +625,104 @@ function test_renotify_blocked() {
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_renotify_installed() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the install confirmation dialog
-    wait_for_install_dialog(function(aWindow) {
+    wait_for_notification("addon-install-confirmation", function(aPanel) {
       // Wait for the complete notification
-      wait_for_notification("addon-install-complete-notification", function(aPanel) {
+      wait_for_notification("addon-install-complete", function(aPanel) {
         // Dismiss the notification
         wait_for_notification_close(function () {
           // Install another
           executeSoon(function () {
             // Wait for the progress notification
             wait_for_progress_notification(function(aPanel) {
               // Wait for the install confirmation dialog
-              wait_for_install_dialog(function(aWindow) {
+              wait_for_notification("addon-install-confirmation", function(aPanel) {
                 info("Timeouts after this probably mean bug 589954 regressed");
 
                 // Wait for the complete notification
-                wait_for_notification("addon-install-complete-notification", function(aPanel) {
+                wait_for_notification("addon-install-complete", function(aPanel) {
                   AddonManager.getAllInstalls(function(aInstalls) {
                   is(aInstalls.length, 1, "Should be one pending installs");
                     aInstalls[0].cancel();
 
                     Services.perms.remove("example.com", "install");
                     wait_for_notification_close(runNextTest);
                     gBrowser.removeTab(gBrowser.selectedTab);
                   });
                 });
 
-                aWindow.document.documentElement.acceptDialog();
+                document.getElementById("addon-install-confirmation-accept").click();
               });
             });
 
             gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
           });
         });
 
         // hide the panel (this simulates the user dismissing it)
         aPanel.hidePopup();
       });
 
-      aWindow.document.documentElement.acceptDialog();
+      document.getElementById("addon-install-confirmation-accept").click();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
     "XPI": "unsigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
-function test_cancel_restart() {
+function test_cancel() {
   function complete_install(callback) {
     let url = TESTROOT + "slowinstall.sjs?continue=true"
     NetUtil.asyncFetch(url, callback || (() => {}));
   }
 
   // Wait for the progress notification
   wait_for_notification(PROGRESS_NOTIFICATION, function(aPanel) {
     let notification = aPanel.childNodes[0];
     // Close the notification
     let anchor = document.getElementById("addons-notification-icon");
     anchor.click();
     // Reopen the notification
     anchor.click();
 
     ok(PopupNotifications.isPanelOpen, "Notification should still be open");
     is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
-    isnot(notification, aPanel.childNodes[0], "Should have reconstructed the notification UI");
     notification = aPanel.childNodes[0];
     is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
-    let button = document.getAnonymousElementByAttribute(notification, "anonid", "cancel");
+    let button = document.getElementById("addon-progress-cancel");
 
     // Wait for the install to fully cancel
     let install = notification.notification.options.installs[0];
     install.addListener({
       onDownloadCancelled: function() {
         install.removeListener(this);
 
         executeSoon(function() {
-          ok(PopupNotifications.isPanelOpen, "Notification should still be open");
-          is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
-          isnot(notification, aPanel.childNodes[0], "Should have reconstructed the notification UI");
-          notification = aPanel.childNodes[0];
-          is(notification.id, "addon-install-cancelled-notification", "Should have seen the cancelled notification");
+          ok(!PopupNotifications.isPanelOpen, "Notification should be closed");
 
-          // Wait for the install confirmation dialog
-          wait_for_install_dialog(function(aWindow) {
-            // Wait for the complete notification
-            wait_for_notification("addon-install-complete-notification", function(aPanel) {
-              let notification = aPanel.childNodes[0];
-              is(notification.button.label, "Restart Now", "Should have seen the right button");
-              is(notification.getAttribute("label"),
-                 "XPI Test will be installed after you restart " + gApp + ".",
-                 "Should have seen the right message");
-
-              AddonManager.getAllInstalls(function(aInstalls) {
-                is(aInstalls.length, 1, "Should be one pending install");
-                aInstalls[0].cancel();
+          AddonManager.getAllInstalls(function(aInstalls) {
+            is(aInstalls.length, 0, "Should be no pending install");
 
-                Services.perms.remove("example.com", "install");
-                wait_for_notification_close(runNextTest);
-                gBrowser.removeTab(gBrowser.selectedTab);
-              });
-            });
-
-            aWindow.document.documentElement.acceptDialog();
+            Services.perms.remove("example.com", "install");
+            gBrowser.removeTab(gBrowser.selectedTab);
+            runNextTest();
           });
-
-          // Restart the download
-          EventUtils.synthesizeMouseAtCenter(notification.button, {});
-
-          // Should be back to a progress notification
-          ok(PopupNotifications.isPanelOpen, "Notification should still be open");
-          is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
-          notification = aPanel.childNodes[0];
-          is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
-
-          complete_install();
         });
       }
     });
 
     // Cancel the download
     EventUtils.synthesizeMouseAtCenter(button, {});
   });
 
@@ -759,17 +739,17 @@ function test_cancel_restart() {
 function test_failed_security() {
   Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
 
   setup_redirect({
     "Location": TESTROOT + "unsigned.xpi"
   });
 
   // Wait for the blocked notification
-  wait_for_notification("addon-install-blocked-notification", function(aPanel) {
+  wait_for_notification("addon-install-blocked", function(aPanel) {
     let notification = aPanel.childNodes[0];
 
     // Click on Allow
     EventUtils.synthesizeMouse(notification.button, 20, 10, {});
 
     // Notification should have changed to progress notification
     ok(PopupNotifications.isPanelOpen, "Notification should still be open");
     is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
@@ -836,36 +816,37 @@ var XPInstallObserver = {
 
 function test() {
   requestLongerTimeout(4);
   waitForExplicitFinish();
 
   Services.prefs.setBoolPref("extensions.logging.enabled", true);
   Services.prefs.setBoolPref("extensions.strictCompatibility", true);
   Services.prefs.setBoolPref("extensions.install.requireSecureOrigin", false);
+  Services.prefs.setIntPref("security.dialog_enable_delay", 0);
 
   Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);
   Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
   Services.obs.addObserver(XPInstallObserver, "addon-install-failed", false);
   Services.obs.addObserver(XPInstallObserver, "addon-install-complete", false);
 
   registerCleanupFunction(function() {
     // Make sure no more test parts run in case we were timed out
     TESTS = [];
-    PopupNotifications.panel.removeEventListener("popupshown", check_notification, false);
 
     AddonManager.getAllInstalls(function(aInstalls) {
       aInstalls.forEach(function(aInstall) {
         aInstall.cancel();
       });
     });
 
     Services.prefs.clearUserPref("extensions.logging.enabled");
     Services.prefs.clearUserPref("extensions.strictCompatibility");
     Services.prefs.clearUserPref("extensions.install.requireSecureOrigin");
+    Services.prefs.clearUserPref("security.dialog_enable_delay");
 
     Services.obs.removeObserver(XPInstallObserver, "addon-install-started");
     Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked");
     Services.obs.removeObserver(XPInstallObserver, "addon-install-failed");
     Services.obs.removeObserver(XPInstallObserver, "addon-install-complete");
   });
 
   runNextTest();
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1568,26 +1568,27 @@
     </implementation>
   </binding>
 
   <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
     <content align="start">
       <xul:image class="popup-notification-icon"
                  xbl:inherits="popupid,src=icon"/>
       <xul:vbox flex="1">
-        <xul:description class="popup-notification-description addon-progress-description"
-                         xbl:inherits="xbl:text=label"/>
+        <xul:label class="popup-notification-originHost header"
+                   xbl:inherits="value=originhost"
+                   crop="end"/>
+        <xul:description class="popup-notification-description"
+                         xbl:inherits="xbl:text=label,popupid"/>
+        <xul:progressmeter anonid="progressmeter" flex="1" mode="undetermined" class="popup-progress-meter"/>
+        <xul:label anonid="progresstext" class="popup-progress-label" flex="1" crop="end"/>
         <xul:spacer flex="1"/>
-        <xul:hbox align="center">
-          <xul:progressmeter anonid="progressmeter" flex="1" mode="undetermined" class="popup-progress-meter"/>
-          <xul:button anonid="cancel" class="popup-progress-cancel" oncommand="document.getBindingParent(this).cancel()"/>
-        </xul:hbox>
-        <xul:label anonid="progresstext" class="popup-progress-label"/>
         <xul:hbox class="popup-notification-button-container"
                   pack="end" align="center">
+          <children includes="button"/>
           <xul:button anonid="button"
                       class="popup-notification-menubutton"
                       type="menu-button"
                       xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
             <xul:menupopup anonid="menupopup"
                            xbl:inherits="oncommand=menucommand">
               <children/>
               <xul:menuitem class="menuitem-iconic popup-notification-closeitem close-icon"
@@ -1601,17 +1602,18 @@
         <xul:toolbarbutton anonid="closebutton"
                            class="messageCloseButton close-icon popup-notification-closebutton tabbable"
                            xbl:inherits="oncommand=closebuttoncommand"
                            tooltiptext="&closeNotification.tooltip;"/>
       </xul:vbox>
     </content>
     <implementation>
       <constructor><![CDATA[
-        this.cancelbtn.setAttribute("tooltiptext", gNavigatorBundle.getString("addonDownloadCancelTooltip"));
+        if (!this.notification)
+          return;
 
         this.notification.options.installs.forEach(function(aInstall) {
           aInstall.addListener(this);
         }, this);
 
         // Calling updateProgress can sometimes cause this notification to be
         // removed in the middle of refreshing the notification panel which
         // makes the panel get refreshed again. Just initialise to the
@@ -1626,27 +1628,27 @@
       ]]></destructor>
 
       <field name="progressmeter" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "progressmeter");
       </field>
       <field name="progresstext" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "progresstext");
       </field>
-      <field name="cancelbtn" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "cancel");
-      </field>
       <field name="DownloadUtils" readonly="true">
         let utils = {};
         Components.utils.import("resource://gre/modules/DownloadUtils.jsm", utils);
         utils.DownloadUtils;
       </field>
 
       <method name="destroy">
         <body><![CDATA[
+          if (!this.notification)
+            return;
+
           this.notification.options.installs.forEach(function(aInstall) {
             aInstall.removeListener(this);
           }, this);
           clearTimeout(this._updateProgressTimeout);
         ]]></body>
       </method>
 
       <method name="setProgress">
@@ -1681,83 +1683,64 @@
             speed = speed * 0.9 + this.notification.speed * 0.1;
 
           this.notification.lastUpdate = now;
           this.notification.lastProgress = aProgress;
           this.notification.speed = speed;
 
           let status = null;
           [status, this.notification.last] = this.DownloadUtils.getDownloadStatus(aProgress, aMaxProgress, speed, this.notification.last);
-          this.progresstext.value = status;
+          this.progresstext.value = this.progresstext.tooltipText = status;
         ]]></body>
       </method>
 
       <method name="cancel">
         <body><![CDATA[
-          // Cache these as cancelling the installs will remove this
-          // notification which will drop these references
-          let browser = this.notification.browser;
-          let sourceURI = this.notification.options.sourceURI;
-
           let installs = this.notification.options.installs;
           installs.forEach(function(aInstall) {
             try {
               aInstall.cancel();
             }
             catch (e) {
               // Cancel will throw if the download has already failed
             }
           }, this);
 
-          let anchorID = "addons-notification-icon";
-          let notificationID = "addon-install-cancelled";
-          let messageString = gNavigatorBundle.getString("addonDownloadCancelled");
-          messageString = PluralForm.get(installs.length, messageString);
-          let buttonText = gNavigatorBundle.getString("addonDownloadRestart");
-          buttonText = PluralForm.get(installs.length, buttonText);
-
-          let action = {
-            label: buttonText,
-            accessKey: gNavigatorBundle.getString("addonDownloadRestart.accessKey"),
-            callback: function() {
-              let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
-                                getService(Ci.amIWebInstallListener);
-              if (weblistener.onWebInstallRequested(browser, sourceURI,
-                                                    installs, installs.length)) {
-                installs.forEach(function(aInstall) {
-                  aInstall.install();
-                });
-              }
-            }
-          };
-
-          PopupNotifications.show(browser, notificationID, messageString,
-                                  anchorID, action);
+          PopupNotifications.remove(this.notification);
         ]]></body>
       </method>
 
       <method name="updateProgress">
         <body><![CDATA[
+          if (!this.notification)
+            return;
+
           let downloadingCount = 0;
           let progress = 0;
           let maxProgress = 0;
 
           this.notification.options.installs.forEach(function(aInstall) {
             if (aInstall.maxProgress == -1)
               maxProgress = -1;
             progress += aInstall.progress;
             if (maxProgress >= 0)
               maxProgress += aInstall.maxProgress;
             if (aInstall.state < AddonManager.STATE_DOWNLOADED)
               downloadingCount++;
           });
 
           if (downloadingCount == 0) {
             this.destroy();
-            PopupNotifications.remove(this.notification);
+            if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+              this.progressmeter.mode = "undetermined";
+              this.progresstext.value = this.progresstext.tooltipText =
+                gNavigatorBundle.getString("addonDownloadVerifying");
+            } else {
+              PopupNotifications.remove(this.notification);
+            }
           }
           else {
             this.setProgress(progress, maxProgress);
           }
         ]]></body>
       </method>
 
       <method name="onDownloadProgress">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -14,36 +14,48 @@ droponhomemsg=Do you want this document 
 # %2$S is the selection string.
 contextMenuSearch=Search %1$S for "%2$S"
 contextMenuSearch.accesskey=S
 
 # bookmark dialog strings
 
 bookmarkAllTabsDefault=[Folder Name]
 
-xpinstallPromptWarning=%S prevented this site (%S) from asking you to install software on your computer.
+xpinstallPromptMessage=%S prevented this site from asking you to install software on your computer.
 xpinstallPromptAllowButton=Allow
 # Accessibility Note:
 # Be sure you do not choose an accesskey that is used elsewhere in the active context (e.g. main menu bar, submenu of the warning popup button)
 # See http://www.mozilla.org/access/keyboard/accesskey for details
 xpinstallPromptAllowButton.accesskey=A
 xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
 xpinstallDisabledMessage=Software installation is currently disabled. Click Enable and try again.
 xpinstallDisabledButton=Enable
 xpinstallDisabledButton.accesskey=n
 
-# LOCALIZATION NOTE (addonDownloading, addonDownloadCancelled, addonDownloadRestart):
+# LOCALIZATION NOTE (addonDownloading):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
-addonDownloading=Add-on downloading;Add-ons downloading
-addonDownloadCancelled=Add-on download cancelled.;Add-on downloads cancelled.
-addonDownloadRestart=Restart Download;Restart Downloads
-addonDownloadRestart.accessKey=R
-addonDownloadCancelTooltip=Cancel
+addonDownloadingAndVerifying=Downloading and verifying add-on…;Downloading and verifying add-ons…
+addonDownloadVerifying=Verifying
+
+addonInstall.cancelButton.label=Cancel
+addonInstall.cancelButton.accesskey=C
+addonInstall.acceptButton.label=Install
+addonInstall.acceptButton.accesskey=I
+
+# LOCALIZATION NOTE (addonConfirmInstallMessage):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is brandShortName
+# #2 is the number of add-ons being installed
+addonConfirmInstall.message=This site would like to install an add-on in #1:;This site would like to install #2 add-ons in #1:
+# LOCALIZATION NOTE (addonConfirmInstall.author):
+# %S is the add-on author's name
+addonConfirmInstall.author=by %S
 
 addonwatch.slow=%1$S might be making %2$S run slowly
 addonwatch.disable.label=Disable %S
 addonwatch.disable.accesskey=D
 addonwatch.ignoreSession.label=Ignore for now
 addonwatch.ignoreSession.accesskey=I
 addonwatch.ignorePerm.label=Ignore permanently
 addonwatch.ignorePerm.accesskey=p
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1127,73 +1127,59 @@ toolbarbutton[sdk-button="true"][cui-are
 }
 
 #identity-popup-button-container {
   background: linear-gradient(to bottom, rgba(0,0,0,0.04) 60%, transparent);
   padding: 10px;
   margin-top: 5px;
 }
 
-/* Notification popup */
-#notification-popup {
-  min-width: 280px;
-}
-
 .popup-notification-icon {
   width: 64px;
   height: 64px;
   -moz-margin-end: 10px;
 }
 
 .popup-notification-icon[popupid="geolocation"] {
   list-style-image: url(chrome://browser/skin/Geolocation-64.png);
 }
 
 .popup-notification-icon[popupid="xpinstall-disabled"],
 .popup-notification-icon[popupid="addon-progress"],
-.popup-notification-icon[popupid="addon-install-cancelled"],
 .popup-notification-icon[popupid="addon-install-blocked"],
 .popup-notification-icon[popupid="addon-install-failed"],
+.popup-notification-icon[popupid="addon-install-confirmation"],
 .popup-notification-icon[popupid="addon-install-complete"] {
   list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
   width: 32px;
   height: 32px;
 }
 
+.popup-notification-description[popupid="addon-progress"],
+.popup-notification-description[popupid="addon-install-confirmation"] {
+  width: 27em;
+  max-width: 27em;
+}
+
+.popup-progress-meter {
+  margin-top: .5em;
+}
+
+.addon-install-confirmation-name {
+  font-weight: bold;
+}
+
 .popup-notification-icon[popupid="click-to-play-plugins"] {
   list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
 }
 
 .popup-notification-icon[popupid="web-notifications"] {
   list-style-image: url(chrome://browser/skin/notification-64.png);
 }
 
-.addon-progress-description {
-  width: 350px;
-  max-width: 350px;
-}
-
-.popup-progress-label,
-.popup-progress-meter {
-  -moz-margin-start: 0;
-  -moz-margin-end: 0;
-}
-
-.popup-progress-cancel {
-  -moz-appearance: none;
-  background: transparent;
-  border: none;
-  padding: 0;
-  margin: 0;
-  -moz-margin-start: 5px;
-  min-height: 0;
-  min-width: 0;
-  list-style-image: url("moz-icon://stock/gtk-cancel?size=menu");
-}
-
 .popup-notification-icon[popupid="indexedDB-permissions-prompt"],
 .popup-notification-icon[popupid*="offline-app-requested"],
 .popup-notification-icon[popupid="offline-app-usage"] {
   list-style-image: url(chrome://global/skin/icons/question-64.png);
 }
 
 .popup-notification-icon[popupid="password"] {
   list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -4145,58 +4145,49 @@ notification[value="loop-sharing-notific
 @media (min-resolution: 2dppx) {
   .popup-notification-icon[popupid="web-notifications"] {
     list-style-image: url(chrome://browser/skin/notification-64@2x.png);
   }
 }
 
 .popup-notification-icon[popupid="xpinstall-disabled"],
 .popup-notification-icon[popupid="addon-progress"],
-.popup-notification-icon[popupid="addon-install-cancelled"],
 .popup-notification-icon[popupid="addon-install-blocked"],
 .popup-notification-icon[popupid="addon-install-failed"],
+.popup-notification-icon[popupid="addon-install-confirmation"],
 .popup-notification-icon[popupid="addon-install-complete"] {
   list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
   width: 32px;
   height: 32px;
 }
 
-.popup-notification-icon[popupid="click-to-play-plugins"] {
-  list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
-}
-
-.addon-progress-description {
-  width: 350px;
-  max-width: 350px;
+.popup-notification-description[popupid="addon-progress"],
+.popup-notification-description[popupid="addon-install-confirmation"] {
+  width: 27em;
+  max-width: 27em;
 }
 
 .popup-progress-label,
 .popup-progress-meter {
   -moz-margin-start: 0;
   -moz-margin-end: 0;
 }
 
-.popup-progress-cancel {
-  -moz-appearance: none;
-  min-height: 16px;
-  min-width: 16px;
-  max-height: 16px;
-  max-width: 16px;
-  padding: 0;
-  margin: 0 1px 0 1px;
-  list-style-image: url(chrome://mozapps/skin/downloads/buttons.png);
-  -moz-image-region: rect(0px, 16px, 16px, 0px);
-}
-
-.popup-progress-cancel:hover {
-  -moz-image-region: rect(0px, 32px, 16px, 16px);
-}
-
-.popup-progress-cancel:active {
-  -moz-image-region: rect(0px, 48px, 16px, 32px);
+.popup-progress-meter,
+#addon-install-confirmation-content {
+  margin-top: 1em;
+}
+
+.addon-install-confirmation-name {
+  font-weight: bold;
+  -moz-margin-start: 0 !important; /* override default label margin to match description margin */
+}
+
+.popup-notification-icon[popupid="click-to-play-plugins"] {
+  list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
 }
 
 .popup-notification-icon[popupid="indexedDB-permissions-prompt"],
 .popup-notification-icon[popupid*="offline-app-requested"],
 .popup-notification-icon[popupid="offline-app-usage"] {
   list-style-image: url(chrome://global/skin/icons/question-64.png);
 }
 
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2153,64 +2153,48 @@ toolbarbutton.bookmark-item[dragover="tr
 }
 
 .popup-notification-icon[popupid="geolocation"] {
   list-style-image: url(chrome://browser/skin/Geolocation-64.png);
 }
 
 .popup-notification-icon[popupid="xpinstall-disabled"],
 .popup-notification-icon[popupid="addon-progress"],
-.popup-notification-icon[popupid="addon-install-cancelled"],
 .popup-notification-icon[popupid="addon-install-blocked"],
 .popup-notification-icon[popupid="addon-install-failed"],
+.popup-notification-icon[popupid="addon-install-confirmation"],
 .popup-notification-icon[popupid="addon-install-complete"] {
   list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
   width: 32px;
   height: 32px;
 }
 
+.popup-notification-description[popupid="addon-progress"],
+.popup-notification-description[popupid="addon-install-confirmation"] {
+  width: 27em;
+  max-width: 27em;
+}
+
+.popup-progress-meter,
+#addon-install-confirmation-content {
+  margin-top: 1em;
+}
+
+.addon-install-confirmation-name {
+  font-weight: bold;
+}
+
 .popup-notification-icon[popupid="click-to-play-plugins"] {
   list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
 }
 
 .popup-notification-icon[popupid="web-notifications"] {
   list-style-image: url(chrome://browser/skin/notification-64.png);
 }
 
-.addon-progress-description {
-  width: 350px;
-  max-width: 350px;
-}
-
-.popup-progress-label,
-.popup-progress-meter {
-  -moz-margin-start: 0;
-  -moz-margin-end: 0;
-}
-
-.popup-progress-cancel {
-  -moz-appearance: none;
-  background: transparent;
-  border: none;
-  padding: 0;
-  margin: 0;
-  min-height: 0;
-  min-width: 0;
-  list-style-image: url(chrome://mozapps/skin/downloads/downloadButtons.png);
-  -moz-image-region: rect(0px, 32px, 16px, 16px);
-}
-
-.popup-progress-cancel:hover {
-  -moz-image-region: rect(16px, 32px, 32px, 16px);
-}
-
-.popup-progress-cancel:active {
-  -moz-image-region: rect(32px, 32px, 48px, 16px);
-}
-
 .popup-notification-icon[popupid="indexedDB-permissions-prompt"],
 .popup-notification-icon[popupid*="offline-app-requested"],
 .popup-notification-icon[popupid="offline-app-usage"] {
   list-style-image: url(chrome://global/skin/icons/question-64.png);
 }
 
 .popup-notification-icon[popupid="password"] {
   list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
--- a/toolkit/content/widgets/notification.xml
+++ b/toolkit/content/widgets/notification.xml
@@ -465,28 +465,34 @@
             }
           ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
   <binding id="popup-notification">
-    <content align="start">
-      <xul:image class="popup-notification-icon"
-                 xbl:inherits="popupid,src=icon"/>
+    <content>
+      <xul:vbox>
+        <xul:image class="popup-notification-icon"
+                   xbl:inherits="popupid,src=icon"/>
+      </xul:vbox>
       <xul:vbox flex="1">
+        <xul:label class="popup-notification-originHost header"
+                   xbl:inherits="value=originhost"
+                   crop="end"/>
         <xul:description class="popup-notification-description"
-                         xbl:inherits="xbl:text=label"/>
+                         xbl:inherits="xbl:text=label,popupid"/>
         <children includes="popupnotificationcontent"/>
         <xul:label class="text-link popup-notification-learnmore-link"
                xbl:inherits="href=learnmoreurl">&learnMore;</xul:label>
         <xul:spacer flex="1"/>
         <xul:hbox class="popup-notification-button-container"
                   pack="end" align="center">
+          <children includes="button"/>
           <xul:button anonid="button"
                       class="popup-notification-menubutton"
                       type="menu-button"
                       xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
             <xul:menupopup anonid="menupopup"
                            xbl:inherits="oncommand=menucommand">
               <children/>
               <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -265,16 +265,18 @@ PopupNotifications.prototype = {
    *        popupIconURL:
    *                     A string. URL of the image to be displayed in the popup.
    *                     Normally specified in CSS using list-style-image and the
    *                     .popup-notification-icon[popupid=...] selector.
    *        learnMoreURL:
    *                     A string URL. Setting this property will make the
    *                     prompt display a "Learn More" link that, when clicked,
    *                     opens the URL in a new tab.
+   *        originHost:  The host name of the page the notification came from.
+   *                     If present, this will be displayed above the message.
    * @returns the Notification object corresponding to the added notification.
    */
   show: function PopupNotifications_show(browser, id, message, anchorID,
                                          mainAction, secondaryActions, options) {
     function isInvalidAction(a) {
       return !a || !(typeof(a.callback) == "function") || !a.label || !a.accessKey;
     }
 
@@ -494,22 +496,22 @@ PopupNotifications.prototype = {
 
       // If this notification was provided by the chrome document rather than
       // created ad hoc, move it back to where we got it from.
       let originalParent = gNotificationParents.get(popupnotification);
       if (originalParent) {
         popupnotification.notification = null;
 
         // Remove nodes dynamically added to the notification's menu button
-        // in _refreshPanel. Keep popupnotificationcontent nodes; they are
-        // provided by the chrome document.
+        // in _refreshPanel.
         let contentNode = popupnotification.lastChild;
         while (contentNode) {
           let previousSibling = contentNode.previousSibling;
-          if (contentNode.nodeName != "popupnotificationcontent")
+          if (contentNode.nodeName == "menuitem" ||
+              contentNode.nodeName == "menuseparator")
             popupnotification.removeChild(contentNode);
           contentNode = previousSibling;
         }
 
         // Re-hide the notification such that it isn't rendered in the chrome
         // document. _refreshPanel will unhide it again when needed.
         popupnotification.hidden = true;
 
@@ -554,21 +556,27 @@ PopupNotifications.prototype = {
         popupnotification.removeAttribute("buttonaccesskey");
         popupnotification.removeAttribute("buttoncommand");
         popupnotification.removeAttribute("menucommand");
         popupnotification.removeAttribute("closeitemcommand");
       }
 
       if (n.options.popupIconURL)
         popupnotification.setAttribute("icon", n.options.popupIconURL);
+
       if (n.options.learnMoreURL)
         popupnotification.setAttribute("learnmoreurl", n.options.learnMoreURL);
       else
         popupnotification.removeAttribute("learnmoreurl");
 
+      if (n.options.originHost)
+        popupnotification.setAttribute("originhost", n.options.originHost);
+      else
+        popupnotification.removeAttribute("originhost");
+
       popupnotification.notification = n;
 
       if (n.secondaryActions) {
         n.secondaryActions.forEach(function (a) {
           let item = doc.createElementNS(XUL_NS, "menuitem");
           item.setAttribute("label", a.label);
           item.setAttribute("accesskey", a.accessKey);
           item.notification = n;
--- a/toolkit/mozapps/extensions/amWebInstallListener.js
+++ b/toolkit/mozapps/extensions/amWebInstallListener.js
@@ -14,16 +14,17 @@
 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,
@@ -149,16 +150,21 @@ Installer.prototype = {
       }, this);
       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;
 
+    if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+      notifyObservers("addon-install-confirmation", this.browser, this.url, this.downloads);
+      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;
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -27,16 +27,17 @@ const RELATIVE_DIR = pathParts.slice(4).
 const TESTROOT = "http://example.com/" + RELATIVE_DIR;
 const TESTROOT2 = "http://example.org/" + RELATIVE_DIR;
 const CHROMEROOT = pathParts.join("/") + "/";
 const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
 const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
 const PREF_XPI_ENABLED = "xpinstall.enabled";
 const PREF_UPDATEURL = "extensions.update.url";
 const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
+const PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI = "xpinstall.customConfirmationUI";
 
 const MANAGER_URI = "about:addons";
 const INSTALL_URI = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
 const PREF_SEARCH_MAXRESULTS = "extensions.getAddons.maxResults";
 const PREF_STRICT_COMPAT = "extensions.strictCompatibility";
 
 var PREF_CHECK_COMPATIBILITY;
@@ -57,16 +58,17 @@ var PREF_CHECK_COMPATIBILITY;
 
 var gPendingTests = [];
 var gTestsRun = 0;
 var gTestStart = null;
 
 var gUseInContentUI = !gTestInWindow && ("switchToTabHavingURI" in window);
 
 var gRestorePrefs = [{name: PREF_LOGGING_ENABLED},
+                     {name: PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI},
                      {name: "extensions.webservice.discoverURL"},
                      {name: "extensions.update.url"},
                      {name: "extensions.update.background.url"},
                      {name: "extensions.update.enabled"},
                      {name: "extensions.update.autoUpdateDefault"},
                      {name: "extensions.getAddons.get.url"},
                      {name: "extensions.getAddons.getWithPerformance.url"},
                      {name: "extensions.getAddons.search.browseURL"},
@@ -90,16 +92,18 @@ for (let pref of gRestorePrefs) {
     pref.value = Services.prefs.getIntPref(pref.name);
   else if (pref.type == Services.prefs.PREF_STRING)
     pref.value = Services.prefs.getCharPref(pref.name);
 }
 
 // Turn logging on for all tests
 Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true);
 
+Services.prefs.setBoolPref(PREF_CUSTOM_XPINSTALL_CONFIRMATION_UI, false);
+
 // Helper to register test failures and close windows if any are left open
 function checkOpenWindows(aWindowID) {
   let windows = Services.wm.getEnumerator(aWindowID);
   let found = false;
   while (windows.hasMoreElements()) {
     let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
     if (!win.closed) {
       found = true;
--- a/toolkit/mozapps/extensions/test/xpinstall/head.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/head.js
@@ -3,16 +3,17 @@ const RELATIVE_DIR = "toolkit/mozapps/ex
 const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
 const TESTROOT2 = "http://example.org/browser/" + RELATIVE_DIR;
 const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 const PROMPT_URL = "chrome://global/content/commonDialog.xul";
 const ADDONS_URL = "chrome://mozapps/content/extensions/extensions.xul";
 const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
 const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
 const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
+const PREF_CUSTOM_CONFIRMATION_UI = "xpinstall.customConfirmationUI";
 const CHROME_NAME = "mochikit";
 
 function getChromeRoot(path) {
   if (path === undefined) {
     return "chrome://" + CHROME_NAME + "/content/browser/" + RELATIVE_DIR
   }
   return getRootDirectory(path);
 }
@@ -22,16 +23,21 @@ function extractChromeRoot(path) {
   var jar = getJar(chromeRootPath);
   if (jar) {
     var tmpdir = extractJarToTmp(jar);
     return "file://" + tmpdir.path + "/";
   }
   return chromeRootPath;
 }
 
+Services.prefs.setBoolPref(PREF_CUSTOM_CONFIRMATION_UI, false);
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref(PREF_CUSTOM_CONFIRMATION_UI);
+});
+
 /**
  * This is a test harness designed to handle responding to UI during the process
  * of installing an XPI. A test can set callbacks to hear about specific parts
  * of the sequence.
  * Before use setup must be called and finish must be called afterwards.
  */
 var Harness = {
   // If set then the callback is called when an install is attempted and
--- a/toolkit/themes/linux/global/global.css
+++ b/toolkit/themes/linux/global/global.css
@@ -276,16 +276,20 @@ label[disabled="true"] {
   border: 1px dotted -moz-DialogText;
 }
 
 notification > button {
   margin-top: 0;
   margin-bottom: 0;
 }
 
+popupnotificationcontent {
+  margin-top: .5em;
+}
+
 /* :::::: autoscroll popup ::::: */
 
 .autoscroller {
   height: 28px;
   width: 28px;
   border: none;
   margin: -14px;
   padding: 0;
--- a/toolkit/themes/linux/global/notification.css
+++ b/toolkit/themes/linux/global/notification.css
@@ -62,19 +62,24 @@ notification[type="critical"] {
 }
 
 /* Popup notification */
 
 .popup-notification-description {
   max-width: 24em;
 }
 
-.popup-notification-learnmore-link {
-  margin-top: 1em !important;
+.popup-notification-originHost:not([value]),
+.popup-notification-learnmore-link:not([href]) {
+  display: none;
 }
 
-.popup-notification-learnmore-link:not([href]) {
-  display: none;
+.popup-notification-originHost {
+  margin-bottom: .3em !important;
+}
+
+.popup-notification-learnmore-link {
+  margin-top: .5em !important;
 }
 
 .popup-notification-button-container {
   margin-top: 17px;
 }
--- a/toolkit/themes/osx/global/global.css
+++ b/toolkit/themes/osx/global/global.css
@@ -261,16 +261,20 @@ notification > button:-moz-focusring {
 notification > button:active:hover:-moz-focusring {
   box-shadow: @focusRingShadow@, @roundButtonPressedShadow@;
 }
 
 notification > button > .button-box > .button-text {
   margin: 0 !important;
 }
 
+popupnotificationcontent {
+  margin-top: .5em;
+}
+
 /* :::::: autoscroll popup ::::: */
 
 .autoscroller {
   height: 28px;
   width: 28px;
   border: none;
   margin: -14px;
   padding: 0;
--- a/toolkit/themes/osx/global/notification.css
+++ b/toolkit/themes/osx/global/notification.css
@@ -100,23 +100,29 @@ notification[type="info"]:not([value="tr
 }
 
 /* Popup notification */
 
 .popup-notification-description {
   max-width: 24em;
 }
 
-.popup-notification-learnmore-link {
-  margin-top: 1em !important;
+.popup-notification-originHost:not([value]),
+.popup-notification-learnmore-link:not([href]) {
+  display: none;
+}
+
+.popup-notification-originHost {
+  margin-bottom: .3em !important;
   -moz-margin-start: 0 !important; /* override default label margin to match description margin */
 }
 
-.popup-notification-learnmore-link:not([href]) {
-  display: none;
+.popup-notification-learnmore-link {
+  margin-top: .5em !important;
+  -moz-margin-start: 0 !important; /* override default label margin to match description margin */
 }
 
 .popup-notification-button-container {
   margin-top: 17px;
 }
 
 .popup-notification-menubutton {
   -moz-appearance: none;
--- a/toolkit/themes/windows/global/global.css
+++ b/toolkit/themes/windows/global/global.css
@@ -281,16 +281,20 @@ label[disabled="true"]:-moz-system-metri
 .text-link:hover {
   text-decoration: underline;
 }
 
 .text-link:-moz-focusring {
   border: 1px dotted -moz-DialogText;
 }
 
+popupnotificationcontent {
+  margin-top: .5em;
+}
+
 /* :::::: autoscroll popup ::::: */
 
 .autoscroller {
   height: 28px;
   width: 28px;
   border: none;
   margin: -14px;
   padding: 0;
--- a/toolkit/themes/windows/global/notification.css
+++ b/toolkit/themes/windows/global/notification.css
@@ -57,22 +57,27 @@ notification[type="critical"] {
 }
 
 /* Popup notification */
 
 .popup-notification-description {
   max-width: 24em;
 }
 
-.popup-notification-learnmore-link {
-  margin-top: 1em !important;
+.popup-notification-originHost:not([value]),
+.popup-notification-learnmore-link:not([href]) {
+  display: none;
 }
 
-.popup-notification-learnmore-link:not([href]) {
-  display: none;
+.popup-notification-originHost {
+  margin-bottom: .3em !important;
+}
+
+.popup-notification-learnmore-link {
+  margin-top: .5em !important;
 }
 
 .popup-notification-button-container {
   margin-top: 17px;
 }
 
 %ifdef XP_WIN
 /*