Bug 1068660 - Add confirmation dialog to unblock downloads. r=Paolo r=MattN
authorAlex Bardas <alex.bardas@gmail.com>
Tue, 14 Oct 2014 11:19:00 +0200
changeset 210429 2640523d8ef80fb8818023b1f303cd1c497dab22
parent 210428 ff22254c233dc32b4c0db999bee11aed90b9c31f
child 210430 d369649bb2657762c504a0c8a6678051ce7b1f3f
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersPaolo, MattN
bugs1068660
milestone36.0a1
Bug 1068660 - Add confirmation dialog to unblock downloads. r=Paolo r=MattN
browser/components/downloads/DownloadsCommon.jsm
browser/components/downloads/test/browser/browser.ini
browser/components/downloads/test/browser/browser_basic_functionality.js
browser/components/downloads/test/browser/browser_confirm_unblock_download.js
browser/components/downloads/test/browser/browser_first_download_panel.js
browser/components/downloads/test/browser/browser_overflow_anchor.js
browser/components/downloads/test/browser/head.js
toolkit/components/prompts/content/commonDialog.xul
toolkit/themes/linux/global/global.css
toolkit/themes/osx/global/global.css
toolkit/themes/windows/global/global.css
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -62,16 +62,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsLogger",
                                   "resource:///modules/DownloadsLogger.jsm");
 
 const nsIDM = Ci.nsIDownloadManager;
 
 const kDownloadsStringBundleUrl =
   "chrome://browser/locale/downloads/downloads.properties";
 
@@ -138,16 +140,23 @@ PrefObserver.register({
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadsCommon
 
 /**
  * This object is exposed directly to the consumers of this JavaScript module,
  * and provides shared methods for all the instances of the user interface.
  */
 this.DownloadsCommon = {
+  /**
+   * Constants with the different types of unblock messages.
+   */
+  BLOCK_VERDICT_MALWARE: "Malware",
+  BLOCK_VERDICT_POTENTIALLY_UNWANTED: "PotentiallyUnwanted",
+  BLOCK_VERDICT_UNCOMMON: "Uncommon",
+
   log: function DC_log(...aMessageArgs) {
     delete this.log;
     this.log = function DC_log(...aMessageArgs) {
       if (!PrefObserver.debug) {
         return;
       }
       DownloadsLogger.log.apply(DownloadsLogger, aMessageArgs);
     }
@@ -506,17 +515,79 @@ this.DownloadsCommon = {
           // If launch also fails (probably because it's not implemented), let
           // the OS handler try to open the parent.
           Cc["@mozilla.org/uriloader/external-protocol-service;1"]
             .getService(Ci.nsIExternalProtocolService)
             .loadUrl(NetUtil.newURI(parent));
         }
       }
     }
-  }
+  },
+
+  /**
+   * Displays an alert message box which asks the user if they want to
+   * unblock the downloaded file or not.
+   *
+   * @param aType
+   *        The type of malware the downloaded file contains.
+   * @param aOwnerWindow
+   *        The window with which this action is associated.
+   *
+   * @return True to unblock the file, false to keep the user safe and
+   *         cancel the operation.
+   */
+  confirmUnblockDownload: Task.async(function* DP_confirmUnblockDownload(aType, aOwnerWindow) {
+    let s = DownloadsCommon.strings;
+    let title = s.unblockHeader;
+    let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
+                      (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
+    let type = "";
+    let message = s.unblockTip;
+    let okButton = s.unblockButtonContinue;
+    let cancelButton = s.unblockButtonCancel;
+
+    switch (aType) {
+      case this.BLOCK_VERDICT_MALWARE:
+        type = s.unblockTypeMalware;
+        break;
+      case this.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
+        type = s.unblockTypePotentiallyUnwanted;
+        break;
+      case this.BLOCK_VERDICT_UNCOMMON:
+        type = s.unblockTypeUncommon;
+        break;
+    }
+
+    if (type) {
+      message = type + "\n\n" + message;
+    }
+
+    Services.ww.registerNotification(function onOpen(subj, topic) {
+      if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
+        // Make sure to listen for "DOMContentLoaded" because it is fired
+        // before the "load" event.
+        subj.addEventListener("DOMContentLoaded", function onLoad() {
+          subj.removeEventListener("DOMContentLoaded", onLoad);
+          if (subj.document.documentURI ==
+              "chrome://global/content/commonDialog.xul") {
+            Services.ww.unregisterNotification(onOpen);
+            let dialog = subj.document.getElementById("commonDialog");
+            if (dialog) {
+              // Change the dialog to use a warning icon.
+              dialog.classList.add("alert-dialog");
+            }
+          }
+        });
+      }
+    });
+
+    let rv = Services.prompt.confirmEx(aOwnerWindow, title, message, buttonFlags,
+                                       cancelButton, okButton, null, null, {});
+    return (rv == 1);
+  }),
 };
 
 /**
  * Returns true if we are executing on Windows Vista or a later version.
  */
 XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () {
   let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
   if (os != "WINNT") {
--- a/browser/components/downloads/test/browser/browser.ini
+++ b/browser/components/downloads/test/browser/browser.ini
@@ -2,8 +2,9 @@
 support-files = head.js
 
 [browser_basic_functionality.js]
 skip-if = buildapp == "mulet" || e10s
 [browser_first_download_panel.js]
 skip-if = os == "linux" # Bug 949434
 [browser_overflow_anchor.js]
 skip-if = os == "linux" # Bug 952422
+[browser_confirm_unblock_download.js]
--- a/browser/components/downloads/test/browser/browser_basic_functionality.js
+++ b/browser/components/downloads/test/browser/browser_basic_functionality.js
@@ -1,57 +1,55 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+registerCleanupFunction(function*() {
+  yield task_resetState();
+});
+
 /**
  * Make sure the downloads panel can display items in the right order and
  * contains the expected data.
  */
-function test_task()
-{
+add_task(function* test_basic_functionality() {
   // Display one of each download state.
   const DownloadData = [
     { state: nsIDM.DOWNLOAD_NOTSTARTED },
     { state: nsIDM.DOWNLOAD_PAUSED },
     { state: nsIDM.DOWNLOAD_FINISHED },
     { state: nsIDM.DOWNLOAD_FAILED },
     { state: nsIDM.DOWNLOAD_CANCELED },
   ];
 
-  try {
-    // Wait for focus first
-    yield promiseFocus();
+  // Wait for focus first
+  yield promiseFocus();
 
-    // Ensure that state is reset in case previous tests didn't finish.
-    yield task_resetState();
+  // Ensure that state is reset in case previous tests didn't finish.
+  yield task_resetState();
 
-    // For testing purposes, show all the download items at once.
-    var originalCountLimit = DownloadsView.kItemCountLimit;
-    DownloadsView.kItemCountLimit = DownloadData.length;
-    registerCleanupFunction(function () {
-      DownloadsView.kItemCountLimit = originalCountLimit;
-    });
+  // For testing purposes, show all the download items at once.
+  var originalCountLimit = DownloadsView.kItemCountLimit;
+  DownloadsView.kItemCountLimit = DownloadData.length;
+  registerCleanupFunction(function () {
+    DownloadsView.kItemCountLimit = originalCountLimit;
+  });
 
-    // Populate the downloads database with the data required by this test.
-    yield task_addDownloads(DownloadData);
+  // Populate the downloads database with the data required by this test.
+  yield task_addDownloads(DownloadData);
 
-    // Open the user interface and wait for data to be fully loaded.
-    yield task_openPanel();
+  // Open the user interface and wait for data to be fully loaded.
+  yield task_openPanel();
 
-    // Test item data and count.  This also tests the ordering of the display.
-    let richlistbox = document.getElementById("downloadsListBox");
-/* disabled for failing intermittently (bug 767828)
+  // Test item data and count.  This also tests the ordering of the display.
+  let richlistbox = document.getElementById("downloadsListBox");
+  /* disabled for failing intermittently (bug 767828)
     is(richlistbox.children.length, DownloadData.length,
        "There is the correct number of richlistitems");
-*/
-    let itemCount = richlistbox.children.length;
-    for (let i = 0; i < itemCount; i++) {
-      let element = richlistbox.children[itemCount - i - 1];
-      let dataItem = new DownloadsViewItemController(element).dataItem;
-      is(dataItem.state, DownloadData[i].state, "Download states match up");
-    }
-  } finally {
-    // Clean up when the test finishes.
-    yield task_resetState();
+  */
+  let itemCount = richlistbox.children.length;
+  for (let i = 0; i < itemCount; i++) {
+    let element = richlistbox.children[itemCount - i - 1];
+    let dataItem = new DownloadsViewItemController(element).dataItem;
+    is(dataItem.state, DownloadData[i].state, "Download states match up");
   }
-}
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_confirm_unblock_download.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the dialog which allows the user to unblock a downloaded file.
+
+registerCleanupFunction(() => {});
+
+function addDialogOpenObserver(buttonAction) {
+  Services.ww.registerNotification(function onOpen(subj, topic, data) {
+    if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
+      // The test listens for the "load" event which guarantees that the alert
+      // class has already been added (it is added when "DOMContentLoaded" is
+      // fired).
+      subj.addEventListener("load", function onLoad() {
+        subj.removeEventListener("load", onLoad);
+        if (subj.document.documentURI ==
+            "chrome://global/content/commonDialog.xul") {
+          Services.ww.unregisterNotification(onOpen);
+
+          let dialog = subj.document.getElementById("commonDialog");
+          ok(dialog.classList.contains("alert-dialog"),
+             "The dialog element should contain an alert class.");
+
+          let doc = subj.document.documentElement;
+          doc.getButton(buttonAction).click();
+        }
+      });
+    }
+  });
+}
+
+add_task(function* test_confirm_unblock_dialog_unblock() {
+  addDialogOpenObserver("cancel");
+  let result = yield DownloadsCommon.confirmUnblockDownload(DownloadsCommon.UNBLOCK_MALWARE,
+                                                            window);
+  ok(result, "Should return true when the user clicks on `Unblock` button.");
+});
+
+add_task(function* test_confirm_unblock_dialog_keep_safe() {
+  addDialogOpenObserver("accept");
+  let result = yield DownloadsCommon.confirmUnblockDownload(DownloadsCommon.UNBLOCK_MALWARE,
+                                                            window);
+  ok(!result, "Should return false when the user clicks on `Keep me safe` button.");
+});
--- a/browser/components/downloads/test/browser/browser_first_download_panel.js
+++ b/browser/components/downloads/test/browser/browser_first_download_panel.js
@@ -3,63 +3,55 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Make sure the downloads panel only opens automatically on the first
  * download it notices. All subsequent downloads, even across sessions, should
  * not open the panel automatically.
  */
-function test_task()
-{
+add_task(function* test_first_download_panel() {
   // Clear the download panel has shown preference first as this test is used to
   // verify this preference's behaviour.
-  let oldPrefValue = true;
-  try {
-    oldPrefValue = Services.prefs.getBoolPref("browser.download.panel.shown");
-  } catch(ex) { }
+  let oldPrefValue = Services.prefs.getBoolPref("browser.download.panel.shown");
   Services.prefs.setBoolPref("browser.download.panel.shown", false);
 
-  try {
-    // Ensure that state is reset in case previous tests didn't finish.
+  registerCleanupFunction(function*() {
+    // Clean up when the test finishes.
     yield task_resetState();
 
-    // With this set to false, we should automatically open the panel the first
-    // time a download is started.
-    DownloadsCommon.getData(window).panelHasShownBefore = false;
-
-    let promise = promisePanelOpened();
-    DownloadsCommon.getData(window)._notifyDownloadEvent("start");
-    yield promise;
-
-    // If we got here, that means the panel opened.
-    DownloadsPanel.hidePanel();
-
-    ok(DownloadsCommon.getData(window).panelHasShownBefore,
-       "Should have recorded that the panel was opened on a download.")
-
-    // Next, make sure that if we start another download, we don't open the
-    // panel automatically.
-    let originalOnPopupShown = DownloadsPanel.onPopupShown;
-    DownloadsPanel.onPopupShown = function () {
-      originalOnPopupShown.apply(this, arguments);
-      ok(false, "Should not have opened the downloads panel.");
-    };
-
-    try {
-      DownloadsCommon.getData(window)._notifyDownloadEvent("start");
-
-      // Wait 2 seconds to ensure that the panel does not open.
-      let deferTimeout = Promise.defer();
-      setTimeout(deferTimeout.resolve, 2000);
-      yield deferTimeout.promise;
-    } finally {
-      DownloadsPanel.onPopupShown = originalOnPopupShown;
-    }
-  } finally {
-    // Clean up when the test finishes.
-    yield task_resetState();
     // Set the preference instead of clearing it afterwards to ensure the
     // right value is used no matter what the default was. This ensures the
     // panel doesn't appear and affect other tests.
     Services.prefs.setBoolPref("browser.download.panel.shown", oldPrefValue);
-  }
-}
+  });
+
+  // Ensure that state is reset in case previous tests didn't finish.
+  yield task_resetState();
+
+  // With this set to false, we should automatically open the panel the first
+  // time a download is started.
+  DownloadsCommon.getData(window).panelHasShownBefore = false;
+
+  let promise = promisePanelOpened();
+  DownloadsCommon.getData(window)._notifyDownloadEvent("start");
+  yield promise;
+
+  // If we got here, that means the panel opened.
+  DownloadsPanel.hidePanel();
+
+  ok(DownloadsCommon.getData(window).panelHasShownBefore,
+     "Should have recorded that the panel was opened on a download.")
+
+  // Next, make sure that if we start another download, we don't open the
+  // panel automatically.
+  let originalOnPopupShown = DownloadsPanel.onPopupShown;
+  DownloadsPanel.onPopupShown = function () {
+    originalOnPopupShown.apply(this, arguments);
+    ok(false, "Should not have opened the downloads panel.");
+  };
+
+  DownloadsCommon.getData(window)._notifyDownloadEvent("start");
+
+  // Wait 2 seconds to ensure that the panel does not open.
+  yield new Promise(resolve => setTimeout(resolve, 2000));
+  DownloadsPanel.onPopupShown = originalOnPopupShown;
+});
--- a/browser/components/downloads/test/browser/browser_overflow_anchor.js
+++ b/browser/components/downloads/test/browser/browser_overflow_anchor.js
@@ -1,74 +1,74 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+registerCleanupFunction(function*() {
+  // Clean up when the test finishes.
+  yield task_resetState();
+});
+
 /**
  * Make sure the downloads button and indicator overflows into the nav-bar
  * chevron properly, and then when those buttons are clicked in the overflow
  * panel that the downloads panel anchors to the chevron.
  */
-function test_task() {
-  try {
-    // Ensure that state is reset in case previous tests didn't finish.
-    yield task_resetState();
+add_task(function* test_overflow_anchor() {
+  // Ensure that state is reset in case previous tests didn't finish.
+  yield task_resetState();
 
-    // Record the original width of the window so we can put it back when
-    // this test finishes.
-    let oldWidth = window.outerWidth;
+  // Record the original width of the window so we can put it back when
+  // this test finishes.
+  let oldWidth = window.outerWidth;
 
-    // The downloads button should not be overflowed to begin with.
-    let button = CustomizableUI.getWidget("downloads-button")
-                               .forWindow(window);
-    ok(!button.overflowed, "Downloads button should not be overflowed.");
+  // The downloads button should not be overflowed to begin with.
+  let button = CustomizableUI.getWidget("downloads-button")
+                             .forWindow(window);
+  ok(!button.overflowed, "Downloads button should not be overflowed.");
 
-    // Hack - we lock the size of the default flex-y items in the nav-bar,
-    // namely, the URL and search inputs. That way we can resize the
-    // window without worrying about them flexing.
-    const kFlexyItems = ["urlbar-container", "search-container"];
-    registerCleanupFunction(() => unlockWidth(kFlexyItems));
-    lockWidth(kFlexyItems);
+  // Hack - we lock the size of the default flex-y items in the nav-bar,
+  // namely, the URL and search inputs. That way we can resize the
+  // window without worrying about them flexing.
+  const kFlexyItems = ["urlbar-container", "search-container"];
+  registerCleanupFunction(() => unlockWidth(kFlexyItems));
+  lockWidth(kFlexyItems);
 
-    // Resize the window to half of its original size. That should
-    // be enough to overflow the downloads button.
-    window.resizeTo(oldWidth / 2, window.outerHeight);
-    yield waitForOverflowed(button, true);
+  // Resize the window to half of its original size. That should
+  // be enough to overflow the downloads button.
+  window.resizeTo(oldWidth / 2, window.outerHeight);
+  yield waitForOverflowed(button, true);
 
-    let promise = promisePanelOpened();
-    button.node.doCommand();
-    yield promise;
-
-    let panel = DownloadsPanel.panel;
-    let chevron = document.getElementById("nav-bar-overflow-button");
-    is(panel.anchorNode, chevron, "Panel should be anchored to the chevron.");
+  let promise = promisePanelOpened();
+  button.node.doCommand();
+  yield promise;
 
-    DownloadsPanel.hidePanel();
+  let panel = DownloadsPanel.panel;
+  let chevron = document.getElementById("nav-bar-overflow-button");
+  is(panel.anchorNode, chevron, "Panel should be anchored to the chevron.");
 
-    // Unlock the widths on the flex-y items.
-    unlockWidth(kFlexyItems);
+  DownloadsPanel.hidePanel();
 
-    // Put the window back to its original dimensions.
-    window.resizeTo(oldWidth, window.outerHeight);
+  // Unlock the widths on the flex-y items.
+  unlockWidth(kFlexyItems);
 
-    // The downloads button should eventually be un-overflowed.
-    yield waitForOverflowed(button, false);
+  // Put the window back to its original dimensions.
+  window.resizeTo(oldWidth, window.outerHeight);
 
-    // Now try opening the panel again.
-    promise = promisePanelOpened();
-    button.node.doCommand();
-    yield promise;
+  // The downloads button should eventually be un-overflowed.
+  yield waitForOverflowed(button, false);
 
-    is(panel.anchorNode.id, "downloads-indicator-anchor");
+  // Now try opening the panel again.
+  promise = promisePanelOpened();
+  button.node.doCommand();
+  yield promise;
 
-    DownloadsPanel.hidePanel();
-  } finally {
-    // Clean up when the test finishes.
-    yield task_resetState();
-  }
-}
+  is(panel.anchorNode.id, "downloads-indicator-anchor");
+
+  DownloadsPanel.hidePanel();
+});
 
 /**
  * For some node IDs, finds the nodes and sets their min-width's to their
  * current width, preventing them from flex-shrinking.
  *
  * @param aItemIDs an array of item IDs to set min-width on.
  */
 function lockWidth(aItemIDs) {
--- a/browser/components/downloads/test/browser/head.js
+++ b/browser/components/downloads/test/browser/head.js
@@ -24,25 +24,16 @@ const nsIDM = Ci.nsIDownloadManager;
 
 let gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
 gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
 registerCleanupFunction(function () {
   gTestTargetFile.remove(false);
 });
 
 ////////////////////////////////////////////////////////////////////////////////
-//// Infrastructure
-
-function test()
-{
-  waitForExplicitFinish();
-  Task.spawn(test_task).then(null, ex => ok(false, ex)).then(finish);
-}
-
-////////////////////////////////////////////////////////////////////////////////
 //// Asynchronous support subroutines
 
 function promiseFocus()
 {
   let deferred = Promise.defer();
   waitForFocus(deferred.resolve);
   return deferred.promise;
 }
--- a/toolkit/components/prompts/content/commonDialog.xul
+++ b/toolkit/components/prompts/content/commonDialog.xul
@@ -8,26 +8,30 @@
 <?xml-stylesheet href="chrome://global/content/commonDialog.css" type="text/css"?>
 <?xml-stylesheet href="chrome://global/skin/commonDialog.css" type="text/css"?>
 
 <!DOCTYPE dialog SYSTEM "chrome://global/locale/commonDialog.dtd">
 
 <dialog id="commonDialog"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         aria-describedby="info.body"
-        onload="commonDialogOnLoad();"
         onunload="commonDialogOnUnload();"
         ondialogaccept="Dialog.onButton0(); return true;"
         ondialogcancel="Dialog.onButton1(); return true;"
         ondialogextra1="Dialog.onButton2(); window.close();"
         ondialogextra2="Dialog.onButton3(); window.close();"
         buttonpack="center">
 
   <script type="application/javascript" src="chrome://global/content/commonDialog.js"/>
   <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+  <script type="application/javascript">
+    document.addEventListener("DOMContentLoaded", function() {
+      commonDialogOnLoad();
+    });
+  </script>
 
   <commandset id="selectEditMenuItems">
     <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')" disabled="true"/>
     <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/>
   </commandset>
 
   <popupset id="contentAreaContextSet">
     <menupopup id="contentAreaContextMenu"
--- a/toolkit/themes/linux/global/global.css
+++ b/toolkit/themes/linux/global/global.css
@@ -55,16 +55,17 @@ window.dialog {
 }
 
 /* ::::: alert icons :::::*/
 
 .message-icon {
   list-style-image: url("moz-icon://stock/gtk-dialog-info?size=dialog");
 }
 
+.alert-dialog #info\.icon,
 .alert-icon {
   list-style-image: url("moz-icon://stock/gtk-dialog-warning?size=dialog");
 }
 
 .error-icon {
   list-style-image: url("moz-icon://stock/gtk-dialog-error?size=dialog");
 }
 
--- a/toolkit/themes/osx/global/global.css
+++ b/toolkit/themes/osx/global/global.css
@@ -73,16 +73,17 @@ window.dialog {
   margin: 6px;
   -moz-margin-end: 20px;
 }
 
 .message-icon {
   list-style-image: url("chrome://global/skin/icons/information-64.png");
 }
 
+.alert-dialog #info\.icon,
 .alert-icon {
   list-style-image: url("chrome://global/skin/icons/warning-64.png");
 }
 
 .error-icon {
   list-style-image: url("chrome://global/skin/icons/error-64.png");
 }
 
--- a/toolkit/themes/windows/global/global.css
+++ b/toolkit/themes/windows/global/global.css
@@ -51,16 +51,17 @@ window.dialog {
   width: 32px;
   height: 32px;
 }
 
 .message-icon {
   list-style-image: url("chrome://global/skin/icons/information-32.png");
 }
 
+.alert-dialog #info\.icon,
 .alert-icon {
   list-style-image: url("chrome://global/skin/icons/Warning.png");
 }
 
 .error-icon {
   list-style-image: url("chrome://global/skin/icons/Error.png");
 }