Bug 1139472 - Extend the attention state of the Downloads Indicator to indicate success or failure. r=paolo
authorPanos Astithas <past@mozilla.com>
Tue, 29 Mar 2016 12:08:39 +0300
changeset 355523 ab85f19b67b1d093e060bfe52a672a3a9995e252
parent 355447 e086812341563534fb85964abda7effdcca09e1c
child 355527 a0cdc83e9d6ea7cf0646b621b57608a61b12f2e5
child 355529 b50652d71510d898b7e1f94d8f8d4330b7d1a486
child 355530 811cdc858ddc43b67f615742f7cad1bb10defc8b
push id16317
push userbmo:past@mozilla.com
push dateFri, 22 Apr 2016 20:09:39 +0000
reviewerspaolo
bugs1139472
milestone48.0a1
Bug 1139472 - Extend the attention state of the Downloads Indicator to indicate success or failure. r=paolo With this change two new attention states are introduced, "severe" and "warning", along with the previous "success" and "no attention" states. "No attention" is still a falsy value in order to mitigate any add-on compatibility issues. "severe" and "warning states" now display an icon badge to the downloads button (or the hamburger button if the downloads button is tucked away). MozReview-Commit-ID: 3gc9Ji7zCXY
browser/base/content/browser.css
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/test/general/browser_menuButtonBadgeManager.js
browser/components/downloads/DownloadsCommon.jsm
browser/components/downloads/content/allDownloadsViewOverlay.js
browser/components/downloads/content/download.xml
browser/components/downloads/content/indicator.js
browser/components/downloads/content/indicatorOverlay.xul
browser/themes/linux/downloads/indicator.css
browser/themes/osx/browser.css
browser/themes/osx/downloads/indicator.css
browser/themes/shared/customizableui/panelUIOverlay.inc.css
browser/themes/windows/downloads/indicator.css
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -989,26 +989,26 @@ chatbar[customSize] > .chatbar-innerbox 
   -moz-binding: url("chrome://browser/content/downloads/download.xml#download-toolbarbutton");
 }
 
 /*** Visibility of downloads indicator controls ***/
 
 /* Bug 924050: If we've loaded the indicator, for now we hide it in the menu panel,
    and just show the icon. This is a hack to side-step very weird layout bugs that
    seem to be caused by the indicator stack interacting with the menu panel. */
-#downloads-button[indicator]:not([cui-areatype="menu-panel"]) > image.toolbarbutton-icon,
+#downloads-button[indicator]:not([cui-areatype="menu-panel"]) > .toolbarbutton-badge-stack > image.toolbarbutton-icon,
 #downloads-button[indicator][cui-areatype="menu-panel"] > #downloads-indicator-anchor {
   display: none;
 }
 
-toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > image.toolbarbutton-icon {
+toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > .toolbarbutton-badge-stack > image.toolbarbutton-icon {
   display: -moz-box;
 }
 
-toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > stack.toolbarbutton-icon {
+toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > #downloads-indicator-anchor {
   display: none;
 }
 
 #downloads-button:-moz-any([progress], [counter], [paused]) #downloads-indicator-icon,
 #downloads-button:not(:-moz-any([progress], [counter], [paused]))
                                                    #downloads-indicator-progress-area
 {
   visibility: hidden;
@@ -1021,17 +1021,17 @@ toolbar[mode="text"] > #downloads-button
   -moz-box-orient: vertical;
   -moz-box-pack: center;
 }
 
 toolbar[mode="text"] > #downloads-button > .toolbarbutton-text {
   -moz-box-ordinal-group: 1;
 }
 
-toolbar[mode="text"] > #downloads-button > .toolbarbutton-icon {
+toolbar[mode="text"] > #downloads-button > .toolbarbutton-badge-stack > .toolbarbutton-icon {
   display: -moz-box;
   -moz-box-ordinal-group: 2;
   visibility: collapse;
 }
 
 /* hide chat chrome when chat is fullscreen */
 #chat-window[sizemode="fullscreen"] chatbox > .chat-titlebar {
   display: none;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2492,19 +2492,21 @@ function SetPageProxyState(aState)
 function PageProxyClickHandler(aEvent)
 {
   if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
     middleMousePaste(aEvent);
 }
 
 var gMenuButtonBadgeManager = {
   BADGEID_APPUPDATE: "update",
+  BADGEID_DOWNLOAD: "download",
   BADGEID_FXA: "fxa",
 
   fxaBadge: null,
+  downloadBadge: null,
   appUpdateBadge: null,
 
   init: function () {
     PanelUI.panel.addEventListener("popupshowing", this, true);
   },
 
   uninit: function () {
     PanelUI.panel.removeEventListener("popupshowing", this, true);
@@ -2512,32 +2514,34 @@ var gMenuButtonBadgeManager = {
 
   handleEvent: function (e) {
     if (e.type === "popupshowing") {
       this.clearBadges();
     }
   },
 
   _showBadge: function () {
-    let badgeToShow = this.appUpdateBadge || this.fxaBadge;
+    let badgeToShow = this.downloadBadge || this.appUpdateBadge || this.fxaBadge;
 
     if (badgeToShow) {
       PanelUI.menuButton.setAttribute("badge-status", badgeToShow);
     } else {
       PanelUI.menuButton.removeAttribute("badge-status");
     }
   },
 
   _changeBadge: function (badgeId, badgeStatus = null) {
     if (badgeId == this.BADGEID_APPUPDATE) {
       this.appUpdateBadge = badgeStatus;
+    } else if (badgeId == this.BADGEID_DOWNLOAD) {
+      this.downloadBadge = badgeStatus;
     } else if (badgeId == this.BADGEID_FXA) {
       this.fxaBadge = badgeStatus;
     } else {
-      Cu.reportError("This badge ID is unknown!");
+      Cu.reportError("The badge ID '" + badgeId + "' is unknown!");
     }
     this._showBadge();
   },
 
   addBadge: function (badgeId, badgeStatus) {
     if (!badgeStatus) {
       Cu.reportError("badgeStatus must be defined");
       return;
@@ -2546,16 +2550,17 @@ var gMenuButtonBadgeManager = {
   },
 
   removeBadge: function (badgeId) {
     this._changeBadge(badgeId);
   },
 
   clearBadges: function () {
     this.appUpdateBadge = null;
+    this.downloadBadge = null;
     this.fxaBadge = null;
     this._showBadge();
   }
 };
 
 // Setup the hamburger button badges for updates, if enabled.
 var gMenuButtonUpdateBadge = {
   enabled: false,
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -895,17 +895,18 @@
                       command="Browser:ShowAllBookmarks"
                       key="manBookmarkKb"/>
           </menupopup>
         </toolbarbutton>
 
         <!-- This is a placeholder for the Downloads Indicator.  It is visible
              during the customization of the toolbar, in the palette, and before
              the Downloads Indicator overlay is loaded. -->
-        <toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+        <toolbarbutton id="downloads-button"
+                       class="toolbarbutton-1 chromeclass-toolbar-additional badged-button"
                        key="key_openDownloads"
                        oncommand="DownloadsIndicatorView.onCommand(event);"
                        ondrop="DownloadsIndicatorView.onDrop(event);"
                        ondragover="DownloadsIndicatorView.onDragOver(event);"
                        ondragenter="DownloadsIndicatorView.onDragOver(event);"
                        label="&downloads.label;"
                        removable="true"
                        cui-areatype="toolbar"
--- a/browser/base/content/test/general/browser_menuButtonBadgeManager.js
+++ b/browser/base/content/test/general/browser_menuButtonBadgeManager.js
@@ -12,26 +12,35 @@ add_task(function* testButtonActivities(
   is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
 
   gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-succeeded");
   is(menuButton.getAttribute("badge-status"), "update-succeeded", "Should have update-succeeded badge status (update > fxa)");
 
   gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-failed");
   is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
 
+  gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, "download-severe");
+  is(menuButton.getAttribute("badge-status"), "download-severe", "Should have download-severe badge status");
+
+  gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, "download-warning");
+  is(menuButton.getAttribute("badge-status"), "download-warning", "Should have download-warning badge status");
+
   gMenuButtonBadgeManager.addBadge("unknownbadge", "attr");
-  is(menuButton.getAttribute("badge-status"), "update-failed", "Should not have changed badge status");
+  is(menuButton.getAttribute("badge-status"), "download-warning", "Should not have changed badge status");
+
+  gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD);
+  is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
 
   gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE);
   is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
 
   gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_FXA);
   is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
 
   yield PanelUI.show();
   is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (Hamburger menu opened)");
   PanelUI.hide();
 
   gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication");
-  gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_UPDATE, "update-succeeded");
+  gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-succeeded");
   gMenuButtonBadgeManager.clearBadges();
   is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (clearBadges called)");
 });
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -135,16 +135,21 @@ 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 = {
+  ATTENTION_NONE: "",
+  ATTENTION_SUCCESS: "success",
+  ATTENTION_WARNING: "warning",
+  ATTENTION_SEVERE: "severe",
+
   /**
    * Returns an object whose keys are the string names from the downloads string
    * bundle, and whose values are either the translated strings or functions
    * returning formatted strings.
    */
   get strings() {
     let strings = {};
     let sb = Services.strings.createBundle(kDownloadsStringBundleUrl);
@@ -717,17 +722,17 @@ DownloadsDataCtor.prototype = {
    */
   removeFinished() {
     let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
                                                         : Downloads.PUBLIC);
     promiseList.then(list => list.removeFinished())
                .then(null, Cu.reportError);
     let indicatorData = this._isPrivate ? PrivateDownloadsIndicatorData
                                         : DownloadsIndicatorData;
-    indicatorData.attention = false;
+    indicatorData.attention = DownloadsCommon.ATTENTION_NONE;
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Integration with the asynchronous Downloads back-end
 
   onDownloadAdded(download) {
     // Download objects do not store the end time of downloads, as the Downloads
     // API does not need to persist this information for all platforms. Once a
@@ -1148,18 +1153,39 @@ DownloadsIndicatorDataCtor.prototype = {
   },
 
   onDownloadAdded(download, newest) {
     this._itemCount++;
     this._updateViews();
   },
 
   onDownloadStateChanged(download) {
-    if (download.succeeded || download.error) {
-      this.attention = true;
+    if (!download.succeeded && download.error && download.error.reputationCheckVerdict) {
+      switch (download.error.reputationCheckVerdict) {
+        case Downloads.Error.BLOCK_VERDICT_UNCOMMON: // fall-through
+        case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
+          // Existing higher level attention indication trumps ATTENTION_WARNING.
+          if (this._attention != DownloadsCommon.ATTENTION_SEVERE) {
+            this.attention = DownloadsCommon.ATTENTION_WARNING;
+          }
+          break;
+        case Downloads.Error.BLOCK_VERDICT_MALWARE:
+          this.attention = DownloadsCommon.ATTENTION_SEVERE;
+          break;
+        default:
+          this.attention = DownloadsCommon.ATTENTION_SEVERE;
+          Cu.reportError("Unknown reputation verdict: " +
+                         download.error.reputationCheckVerdict);
+      }
+    } else if (download.succeeded || download.error) {
+      // Existing higher level attention indication trumps ATTENTION_SUCCESS.
+      if (this._attention != DownloadsCommon.ATTENTION_SEVERE &&
+          this._attention != DownloadsCommon.ATTENTION_WARNING) {
+        this.attention = DownloadsCommon.ATTENTION_SUCCESS;
+      }
     }
 
     // Since the state of a download changed, reset the estimated time left.
     this._lastRawTimeLeft = -1;
     this._lastTimeLeft = -1;
   },
 
   onDownloadChanged(download) {
@@ -1184,25 +1210,25 @@ DownloadsIndicatorDataCtor.prototype = {
   /**
    * Indicates whether the download indicators should be highlighted.
    */
   set attention(aValue) {
     this._attention = aValue;
     this._updateViews();
     return aValue;
   },
-  _attention: false,
+  _attention: DownloadsCommon.ATTENTION_NONE,
 
   /**
    * Indicates whether the user is interacting with downloads, thus the
    * attention indication should not be shown even if requested.
    */
   set attentionSuppressed(aValue) {
     this._attentionSuppressed = aValue;
-    this._attention = false;
+    this._attention = DownloadsCommon.ATTENTION_NONE;
     this._updateViews();
     return aValue;
   },
   _attentionSuppressed: false,
 
   /**
    * Computes aggregate values and propagates the changes to our views.
    */
@@ -1222,17 +1248,18 @@ DownloadsIndicatorDataCtor.prototype = {
    * @param aView
    *        DownloadsIndicatorView object to be updated.
    */
   _updateView(aView) {
     aView.hasDownloads = this._hasDownloads;
     aView.counter = this._counter;
     aView.percentComplete = this._percentComplete;
     aView.paused = this._paused;
-    aView.attention = this._attention && !this._attentionSuppressed;
+    aView.attention = this._attentionSuppressed ? DownloadsCommon.ATTENTION_NONE
+                                                : this._attention;
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Property updating based on current download status
 
   /**
    * Number of download items that are available to be displayed.
    */
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -497,17 +497,17 @@ function DownloadsPlacesView(aRichListBo
   // Register as a downloads view. The places data will be initialized by
   // the places setter.
   this._initiallySelectedElement = null;
   this._downloadsData = DownloadsCommon.getData(window.opener || window);
   this._downloadsData.addView(this);
 
   // Get the Download button out of the attention state since we're about to
   // view all downloads.
-  DownloadsCommon.getIndicatorData(window).attention = false;
+  DownloadsCommon.getIndicatorData(window).attention = DownloadsCommon.ATTENTION_NONE;
 
   // Make sure to unregister the view if the window is closed.
   window.addEventListener("unload", () => {
     window.controllers.removeController(this);
     this._downloadsData.removeView(this);
     this.result = null;
   }, true);
   // Resizing the window may change items visibility.
--- a/browser/components/downloads/content/download.xml
+++ b/browser/components/downloads/content/download.xml
@@ -69,19 +69,22 @@
         <xul:button class="downloadButton downloadChooseOpen downloadIconShow"
                     tooltiptext="&cmd.chooseOpen.label;"
                     oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseOpen');"/>
       </xul:stack>
     </content>
   </binding>
 
   <binding id="download-toolbarbutton"
-           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged">
     <content>
-      <children />
-      <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
+      <xul:stack class="toolbarbutton-badge-stack">
+        <children />
+        <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
+        <xul:label class="toolbarbutton-badge" xbl:inherits="value=badge" top="0" end="0" crop="none"/>
+      </xul:stack>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
                  xbl:inherits="value=label,accesskey,crop,wrap"/>
       <xul:label class="toolbarbutton-multiline-text" flex="1"
                  xbl:inherits="xbl:text=label,accesskey,wrap"/>
     </content>
   </binding>
 </bindings>
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -233,17 +233,17 @@ const DownloadsIndicatorView = {
     window.removeEventListener("unload", this.onWindowUnload, false);
     DownloadsCommon.getIndicatorData(window).removeView(this);
 
     // Reset the view properties, so that a neutral indicator is displayed if we
     // are visible only temporarily as an anchor.
     this.counter = "";
     this.percentComplete = 0;
     this.paused = false;
-    this.attention = false;
+    this.attention = DownloadsCommon.ATTENTION_NONE;
   },
 
   /**
    * Ensures that the user interface elements required to display the indicator
    * are loaded, then invokes the given callback.
    */
   _ensureOperational(aCallback) {
     if (this._operational) {
@@ -461,25 +461,38 @@ const DownloadsIndicatorView = {
    */
   set attention(aValue) {
     if (!this._operational) {
       return this._attention;
     }
 
     if (this._attention != aValue) {
       this._attention = aValue;
-      if (aValue) {
-        this.indicator.setAttribute("attention", "true");
+
+      // Check if the downloads button is in the menu panel, to determine which
+      // button needs to get a badge.
+      let widgetGroup = CustomizableUI.getWidget("downloads-button");
+      let inMenu = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
+
+      if (aValue == DownloadsCommon.ATTENTION_NONE) {
+        this.indicator.removeAttribute("attention");
+        if (inMenu) {
+          gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD);
+        }
       } else {
-        this.indicator.removeAttribute("attention");
+        this.indicator.setAttribute("attention", aValue);
+        if (inMenu) {
+          let badgeClass = "download-" + aValue;
+          gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, badgeClass);
+        }
       }
     }
     return aValue;
   },
-  _attention: false,
+  _attention: DownloadsCommon.ATTENTION_NONE,
 
   //////////////////////////////////////////////////////////////////////////////
   //// User interface event functions
 
   onWindowUnload() {
     // This function is registered as an event listener, we can't use "this".
     DownloadsIndicatorView.ensureTerminated();
   },
--- a/browser/components/downloads/content/indicatorOverlay.xul
+++ b/browser/components/downloads/content/indicatorOverlay.xul
@@ -18,17 +18,17 @@
   <!-- We dynamically add the stack with the progress meter and notification icon,
        originally loaded lazily because of performance reasons, to the existing
        downloads-button. -->
   <toolbarbutton id="downloads-button" indicator="true">
     <!-- The panel's anchor area is smaller than the outer button, but must
          always be visible and must not move or resize when the indicator
          state changes, otherwise the panel could change its position or lose
          its arrow unexpectedly. -->
-    <stack id="downloads-indicator-anchor" class="toolbarbutton-icon"
+    <stack id="downloads-indicator-anchor"
            consumeanchor="downloads-button">
       <vbox id="downloads-indicator-progress-area" pack="center">
         <description id="downloads-indicator-counter"/>
         <progressmeter id="downloads-indicator-progress" class="plain"
                        min="0" max="100"/>
       </vbox>
       <vbox id="downloads-indicator-icon"/>
     </stack>
--- a/browser/themes/linux/downloads/indicator.css
+++ b/browser/themes/linux/downloads/indicator.css
@@ -21,52 +21,76 @@
 
 #downloads-button[cui-areatype="toolbar"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 198, 18, 180) center no-repeat;
   min-width: 18px;
   min-height: 18px;
 }
 
-toolbar[brighttext] #downloads-button[cui-areatype="toolbar"]:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[brighttext] #downloads-button[cui-areatype="toolbar"]:not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-icon {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"),
                               0, 198, 18, 180) center no-repeat;
 }
 
-#downloads-button[cui-areatype="toolbar"][attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+#downloads-button[cui-areatype="toolbar"][attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#downloads-button[cui-areatype="toolbar"][attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  display: -moz-box;
+  height: 13px;
+  width: 13px;
+  background-size: contain;
+  border: none;
+  box-shadow: none;
+  filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
+}
+
+#downloads-button[cui-areatype="toolbar"][attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #D90000 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[cui-areatype="toolbar"][attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #FFBF00 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[cui-areatype="toolbar"][attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
+#downloads-button[cui-areatype="toolbar"][attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
+  filter: none;
+}
+
+#downloads-button[cui-areatype="toolbar"][attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
 }
 
-toolbar[brighttext] #downloads-button[cui-areatype="toolbar"][attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[brighttext] #downloads-button[cui-areatype="toolbar"][attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
 }
 
-#downloads-button[cui-areatype="menu-panel"][attention] {
+#downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
 #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar-small.png"),
                               0, 16, 16, 0) center no-repeat;
   background-size: 12px;
 }
 
-toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
 }
 
-#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
 }
 
-toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1058,16 +1058,17 @@ toolbar .toolbarbutton-1 > .toolbarbutto
 }
 
 @media (-moz-mac-lion-theme) {
   #main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-icon,
   #main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-badge-stack > .toolbarbutton-icon,
   #main-window:not([customizing]) .toolbarbutton-1 > .toolbarbutton-menubutton-button[disabled="true"] > .toolbarbutton-icon,
   #main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-menu-dropmarker,
   #main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-menubutton-dropmarker,
+  .toolbarbutton-1:not(:hover):-moz-window-inactive > #downloads-indicator-anchor,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-icon,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-text,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-badge-stack > .toolbarbutton-icon,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-menu-dropmarker,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
     opacity: .5;
   }
--- a/browser/themes/osx/downloads/indicator.css
+++ b/browser/themes/osx/downloads/indicator.css
@@ -28,47 +28,71 @@
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 198, 18, 180) center no-repeat;
 }
 
 toolbar[brighttext] #downloads-indicator-icon {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
 }
 
-#downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  display: -moz-box;
+  height: 13px;
+  width: 13px;
+  background-size: contain;
+  border: none;
+  box-shadow: none;
+  filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #D90000 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #FFBF00 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
+  filter: none;
+}
+
+#downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
 }
 
-toolbar[brighttext] #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
 }
 
-#downloads-button[cui-areatype="menu-panel"][attention] {
+#downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
 #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 198, 18, 180) center no-repeat;
   background-size: 12px;
 }
 
-toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
 }
 
-#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
 }
 
-toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
 }
 
 @media (min-resolution: 2dppx) {
   #downloads-indicator-icon {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
     background-size: 18px;
   }
@@ -81,33 +105,33 @@ toolbar[brighttext] #downloads-button:no
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
   }
 
   toolbar[brighttext] #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"),
                                       0, 396, 36, 360);
   }
 
-  #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+  #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
   }
 
-  toolbar[brighttext] #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+  toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
   }
 
-  #downloads-button[cui-areatype="menu-panel"][attention] {
+  #downloads-button[cui-areatype="menu-panel"][attention="success"] {
     list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel@2x.png");
   }
 
-  #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+  #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
   }
 
-  toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+  toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
   }
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -106,28 +106,38 @@
   border: none;
 }
 
 #PanelUI-menu-button[badge-status="update-succeeded"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   background: #74BF43 url(chrome://browser/skin/update-badge.svg) no-repeat center;
   height: 13px;
 }
 
+#PanelUI-menu-button[badge-status="download-severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #PanelUI-menu-button[badge-status="update-failed"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   background: #D90000 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
   height: 13px;
 }
 
+#PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #PanelUI-menu-button[badge-status="fxa-needs-authentication"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
-  background: transparent url(chrome://browser/skin/warning.svg) no-repeat center;
   height: 13px;
   box-shadow: none;
   filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
 }
 
+#PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #FFBF00 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#PanelUI-menu-button[badge-status="fxa-needs-authentication"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: transparent url(chrome://browser/skin/warning.svg) no-repeat center;
+}
+
+#PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
 #PanelUI-menu-button[badge-status="fxa-needs-authentication"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
   filter: none;
 }
 
 .panel-subviews {
   padding: 4px;
   background-clip: padding-box;
   border-left: 1px solid hsla(210,4%,10%,.3);
--- a/browser/themes/windows/downloads/indicator.css
+++ b/browser/themes/windows/downloads/indicator.css
@@ -39,50 +39,74 @@
 
 #downloads-indicator-icon {
   background: var(--downloads-indicator-icon) center no-repeat;
   width: 18px;
   height: 18px;
   background-size: 18px;
 }
 
-toolbar[brighttext] #downloads-button:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[brighttext] #downloads-button:not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: var(--downloads-indicator-icon-inverted);
 }
 
-#downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  display: -moz-box;
+  height: 13px;
+  width: 13px;
+  background-size: contain;
+  border: none;
+  box-shadow: none;
+  filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #D90000 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #FFBF00 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
+  filter: none;
+}
+
+#downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: var(--downloads-indicator-icon-attention);
 }
 
-toolbar[brighttext] #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: var(--downloads-indicator-icon-attention-inverted);
 }
 
-#downloads-button[cui-areatype="menu-panel"][attention] {
+#downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
 #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background: var(--downloads-indicator-icon) center no-repeat;
   background-size: 12px;
 }
 
-toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
   background-image: var(--downloads-indicator-icon-inverted);
 }
 
-#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: var(--downloads-indicator-icon-attention);
 }
 
-toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: var(--downloads-indicator-icon-attention-inverted);
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;