Bug 548763 - Show download progress in OS X app dock icon. r=dao r=josh
authorDave Vasilevsky <dave@vasilevsky.ca>
Sun, 03 Mar 2013 05:58:00 -0500
changeset 130054 eaa88688e9e86299f81cf3e6e8ff6dda13207cbe
parent 130053 05f6955dad64986165d10ddb42fefa8388412bca
child 130055 e78b82d62d9838cdc2929621658bbb98814fac3b
push id3582
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 20:50:56 +0000
treeherdermozilla-aurora@400370bbc9fa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao, josh
bugs548763
milestone22.0a1
Bug 548763 - Show download progress in OS X app dock icon. r=dao r=josh
browser/base/content/browser.js
toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm
toolkit/mozapps/downloads/Makefile.in
toolkit/mozapps/downloads/content/downloads.js
toolkit/mozapps/downloads/tests/chrome/Makefile.in
toolkit/mozapps/downloads/tests/chrome/test_taskbarprogress_downloadstates.xul
widget/Makefile.in
widget/cocoa/nsMacDockSupport.h
widget/cocoa/nsMacDockSupport.mm
widget/nsIMacDockSupport.idl
widget/tests/Makefile.in
widget/tests/taskbar_progress.xul
widget/tests/test_taskbar_progress.xul
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1481,24 +1481,19 @@ var gBrowserInit = {
 
     // Initialize the download manager some time after the app starts so that
     // auto-resume downloads begin (such as after crashing or quitting with
     // active downloads) and speeds up the first-load of the download manager UI.
     // If the user manually opens the download manager before the timeout, the
     // downloads will start right away, and getting the service again won't hurt.
     setTimeout(function() {
       Services.downloads;
-
-#ifdef XP_WIN
-      if (Win7Features) {
-        let DownloadTaskbarProgress =
-          Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
-        DownloadTaskbarProgress.onBrowserWindowLoad(window);
-      }
-#endif
+      let DownloadTaskbarProgress =
+        Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
+      DownloadTaskbarProgress.onBrowserWindowLoad(window);
     }, 10000);
 
     // The object handling the downloads indicator is also initialized here in the
     // delayed startup function, but the actual indicator element is not loaded
     // unless there are downloads to be displayed.
     DownloadsButton.initializeIndicator();
 
 #ifndef XP_MACOSX
--- a/toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm
+++ b/toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm
@@ -9,33 +9,42 @@ this.EXPORTED_SYMBOLS = [
 ];
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Constants
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
-const kTaskbarID = "@mozilla.org/windows-taskbar;1";
+const kTaskbarIDWin = "@mozilla.org/windows-taskbar;1";
+const kTaskbarIDMac = "@mozilla.org/widget/macdocksupport;1";
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadTaskbarProgress Object
 
 this.DownloadTaskbarProgress =
 {
+  init: function DTP_init()
+  {
+    if (DownloadTaskbarProgressUpdater) {
+      DownloadTaskbarProgressUpdater._init();
+    }
+  },
+
   /**
    * Called when a browser window appears. This has an effect only when we
    * don't already have an active window.
    *
    * @param aWindow
    *        The browser window that we'll potentially use to display the
    *        progress.
    */
   onBrowserWindowLoad: function DTP_onBrowserWindowLoad(aWindow)
   {
+    this.init();
     if (!DownloadTaskbarProgressUpdater) {
       return;
     }
     if (!DownloadTaskbarProgressUpdater._activeTaskbarProgress) {
       DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, false);
     }
   },
 
@@ -78,39 +87,51 @@ this.DownloadTaskbarProgress =
 
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadTaskbarProgressUpdater Object
 
 var DownloadTaskbarProgressUpdater =
 {
+  /// Whether the taskbar is initialized.
+  _initialized: false,
+
   /// Reference to the taskbar.
   _taskbar: null,
 
   /// Reference to the download manager.
   _dm: null,
 
   /**
    * Initialize and register ourselves as a download progress listener.
    */
   _init: function DTPU_init()
   {
-    if (!(kTaskbarID in Cc)) {
-      // This means that the component isn't available
+    if (this._initialized) {
+      return; // Already initialized
+    }
+    this._initialized = true;
+
+    if (kTaskbarIDWin in Cc) {
+      this._taskbar = Cc[kTaskbarIDWin].getService(Ci.nsIWinTaskbar);
+      if (!this._taskbar.available) {
+        // The Windows version is probably too old
+        DownloadTaskbarProgressUpdater = null;
+        return;
+      }
+    } else if (kTaskbarIDMac in Cc) {
+      this._activeTaskbarProgress = Cc[kTaskbarIDMac].
+                                      getService(Ci.nsITaskbarProgress);
+    } else {
       DownloadTaskbarProgressUpdater = null;
       return;
     }
 
-    this._taskbar = Cc[kTaskbarID].getService(Ci.nsIWinTaskbar);
-    if (!this._taskbar.available) {
-      // The Windows version is probably too old
-      DownloadTaskbarProgressUpdater = null;
-      return;
-    }
+    this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS;
 
     this._dm = Cc["@mozilla.org/download-manager;1"].
                getService(Ci.nsIDownloadManager);
     this._dm.addPrivacyAwareListener(this);
 
     this._os = Cc["@mozilla.org/observer-service;1"].
                getService(Ci.nsIObserverService);
     this._os.addObserver(this, "quit-application-granted", false);
@@ -121,16 +142,18 @@ var DownloadTaskbarProgressUpdater =
   },
 
   /**
    * Unregisters ourselves as a download progress listener.
    */
   _uninit: function DTPU_uninit() {
     this._dm.removeListener(this);
     this._os.removeObserver(this, "quit-application-granted");
+    this._activeTaskbarProgress = null;
+    this._initialized = false;
   },
 
   /**
    * This holds a reference to the taskbar progress for the window we're
    * working with. This window would preferably be download window, but can be
    * another window if it isn't open.
    */
   _activeTaskbarProgress: null,
@@ -145,16 +168,17 @@ var DownloadTaskbarProgressUpdater =
    *
    * @param aWindow
    *        The window to set as active.
    * @param aIsDownloadWindow
    *        Whether this window is a download window.
    */
   _setActiveWindow: function DTPU_setActiveWindow(aWindow, aIsDownloadWindow)
   {
+#ifdef XP_WIN
     // Clear out the taskbar for the old active window. (If there was no active
     // window, this is a no-op.)
     this._clearTaskbar();
 
     this._activeWindowIsDownloadWindow = aIsDownloadWindow;
     if (aWindow) {
       // Get the taskbar progress for this window
       let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
@@ -170,44 +194,54 @@ var DownloadTaskbarProgressUpdater =
       // we've already set this before or not.
       aWindow.addEventListener("unload", function () {
         DownloadTaskbarProgressUpdater._onActiveWindowUnload(taskbarProgress);
       }, false);
     }
     else {
       this._activeTaskbarProgress = null;
     }
+#endif
   },
 
   /// Current state displayed on the active window's taskbar item
-  _taskbarState: Ci.nsITaskbarProgress.STATE_NO_PROGRESS,
+  _taskbarState: null,
   _totalSize: 0,
   _totalTransferred: 0,
 
+  // If the active window is not the download manager window, set the state
+  // only if it is normal or indeterminate.
+  _shouldSetState: function DTPU_shouldSetState()
+  {
+#ifdef XP_WIN
+    return this._activeWindowIsDownloadWindow ||
+           (this._taskbarState == Ci.nsITaskbarProgress.STATE_NORMAL ||
+            this._taskbarState == Ci.nsITaskbarProgress.STATE_INDETERMINATE);
+#else
+    return true;
+#endif
+  },
+
   /**
    * Update the active window's taskbar indicator with the current state. There
    * are two cases here:
    * 1. If the active window is the download window, then we always update
    *    the taskbar indicator.
    * 2. If the active window isn't the download window, then we update only if
    *    the status is normal or indeterminate. i.e. one or more downloads are
    *    currently progressing or in scan mode. If we aren't, then we clear the
    *    indicator.
    */
   _updateTaskbar: function DTPU_updateTaskbar()
   {
     if (!this._activeTaskbarProgress) {
       return;
     }
 
-    // If the active window is not the download manager window, set the state
-    // only if it is normal or indeterminate.
-    if (this._activeWindowIsDownloadWindow ||
-        (this._taskbarState == Ci.nsITaskbarProgress.STATE_NORMAL ||
-         this._taskbarState == Ci.nsITaskbarProgress.STATE_INDETERMINATE)) {
+    if (this._shouldSetState()) {
       this._activeTaskbarProgress.setProgressState(this._taskbarState,
                                                    this._totalTransferred,
                                                    this._totalSize);
     }
     // Clear any state otherwise
     else {
       this._clearTaskbar();
     }
@@ -356,13 +390,8 @@ var DownloadTaskbarProgressUpdater =
   onStateChange: function() { },
 
   observe: function DTPU_observe(aSubject, aTopic, aData) {
     if (aTopic == "quit-application-granted") {
       this._uninit();
     }
   }
 };
-
-////////////////////////////////////////////////////////////////////////////////
-//// Initialization
-
-DownloadTaskbarProgressUpdater._init();
--- a/toolkit/mozapps/downloads/Makefile.in
+++ b/toolkit/mozapps/downloads/Makefile.in
@@ -16,15 +16,11 @@ EXTRA_COMPONENTS = nsHelperAppDlg.manife
 EXTRA_PP_COMPONENTS = nsHelperAppDlg.js
 
 EXTRA_JS_MODULES = \
   DownloadLastDir.jsm \
   DownloadPaths.jsm \
   DownloadUtils.jsm \
   $(NULL)
 
-ifeq ($(OS_ARCH),WINNT)
-EXTRA_JS_MODULES += \
-  DownloadTaskbarProgress.jsm \
-  $(NULL)
-endif
+EXTRA_PP_JS_MODULES = DownloadTaskbarProgress.jsm
 
 include $(topsrcdir)/config/rules.mk
--- a/toolkit/mozapps/downloads/content/downloads.js
+++ b/toolkit/mozapps/downloads/content/downloads.js
@@ -442,21 +442,19 @@ function Startup()
   gSearchBox.addEventListener("keypress", function(e) {
     if (e.keyCode == e.DOM_VK_ESCAPE) {
       // Move focus to the list instead of closing the window
       gDownloadsView.focus();
       e.preventDefault();
     }
   }, false);
 
-#ifdef XP_WIN
-  let tempScope = {};
-  Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", tempScope);
-  tempScope.DownloadTaskbarProgress.onDownloadWindowLoad(window);
-#endif
+  let DownloadTaskbarProgress =
+    Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
+  DownloadTaskbarProgress.onDownloadWindowLoad(window);
 }
 
 function Shutdown()
 {
   gDownloadManager.removeListener(gDownloadListener);
 
   let obs = Cc["@mozilla.org/observer-service;1"].
             getService(Ci.nsIObserverService);
--- a/toolkit/mozapps/downloads/tests/chrome/Makefile.in
+++ b/toolkit/mozapps/downloads/tests/chrome/Makefile.in
@@ -43,16 +43,20 @@ MOCHITEST_CHROME_FILES = \
   utils.js \
   $(NULL)
 ifneq (,$(filter cocoa, $(MOZ_WIDGET_TOOLKIT)))
 MOCHITEST_CHROME_FILES += \
   test_backspace_key_removes.xul \
   $(NULL)
 endif
 
+ifneq (,$(filter WINNT, $(OS_ARCH))$(filter cocoa, $(MOZ_WIDGET_TOOLKIT)))
+MOCHITEST_CHROME_FILES += \
+  test_taskbarprogress_downloadstates.xul \
+  $(NULL)
+endif
 ifeq ($(OS_ARCH),WINNT)
 MOCHITEST_CHROME_FILES += \
-  test_taskbarprogress_downloadstates.xul \
   $(filter disabled-for-very-frequent-orange--bug-630567, test_taskbarprogress_service.xul) \
   $(NULL)
 endif
 
 include $(topsrcdir)/config/rules.mk
--- a/toolkit/mozapps/downloads/tests/chrome/test_taskbarprogress_downloadstates.xul
+++ b/toolkit/mozapps/downloads/tests/chrome/test_taskbarprogress_downloadstates.xul
@@ -161,38 +161,44 @@ function testSetup()
 {
   //Test setup
   let dmui = getDMUI();
   if (!dmui) {
     todo(false, "skip test for toolkit download manager UI");
     return;
   }
 
-  let isWin7OrHigher = false;
-  try {
-    let version = Cc["@mozilla.org/system-info;1"]
-                    .getService(Ci.nsIPropertyBag2)
-                    .getProperty("version");
-    isWin7OrHigher = (parseFloat(version) >= 6.1);
-  } catch (ex) { }
+  let isWin = /Win/.test(navigator.platform);
+  if (isWin) {
+    let isWin7OrHigher = false;
+    try {
+      let version = Cc["@mozilla.org/system-info;1"]
+                      .getService(Ci.nsIPropertyBag2)
+                      .getProperty("version");
+      isWin7OrHigher = (parseFloat(version) >= 6.1);
+    } catch (ex) { }
 
-  if (!isWin7OrHigher) {
-    ok(true, "This test only runs on Windows 7 or higher");
-    return;
+    if (!isWin7OrHigher) {
+      ok(true, "This test only runs on Windows 7 or higher");
+      return;
+    }
   }
 
   let tempScope = {};
   Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", tempScope);
   Cu.import("resource://gre/modules/Services.jsm");
 
   DownloadTaskbarProgress = tempScope.DownloadTaskbarProgress;
-  let TaskbarService =  Cc[kTaskbarID].getService(Ci.nsIWinTaskbar);
+  isnot(DownloadTaskbarProgress, null, "Download taskbar progress service exists");
+  DownloadTaskbarProgress.init();
 
-  isnot(DownloadTaskbarProgress, null, "Download taskbar progress service exists");
-  is(TaskbarService.available, true, "Taskbar Service is available");
+  if (isWin) {
+    let TaskbarService =  Cc[kTaskbarID].getService(Ci.nsIWinTaskbar);
+    is(TaskbarService.available, true, "Taskbar Service is available");
+  }
 
   dm = Cc["@mozilla.org/download-manager;1"].
            getService(Ci.nsIDownloadManager);
 
   // First, we clear out the database
   dm.DBConnection.executeSimpleSQL("DELETE FROM moz_downloads");
 
   // See if the DM is already open, and if it is, close it!
--- a/widget/Makefile.in
+++ b/widget/Makefile.in
@@ -117,16 +117,17 @@ XPIDLSRCS += nsIWinMetroUtils.idl \
 		MetroUIUtils.idl \
 		$(NULL)
 endif
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 XPIDLSRCS +=	nsIMacDockSupport.idl \
 		nsIStandaloneNativeMenu.idl \
 		nsIMacWebAppUtils.idl \
+		nsITaskbarProgress.idl \
 		$(NULL)
 endif
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),os2)
 XPIDLSRCS	+= nsIRwsService.idl
 endif
 
 EXPORTS		:= $(addprefix $(srcdir)/, $(EXPORTS))
--- a/widget/cocoa/nsMacDockSupport.h
+++ b/widget/cocoa/nsMacDockSupport.h
@@ -1,23 +1,40 @@
 /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#import <Carbon/Carbon.h>
+
 #include "nsIMacDockSupport.h"
 #include "nsIStandaloneNativeMenu.h"
+#include "nsITaskbarProgress.h"
+#include "nsITimer.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 
-class nsMacDockSupport : public nsIMacDockSupport
+class nsMacDockSupport : public nsIMacDockSupport, public nsITaskbarProgress
 {
 public:
-  nsMacDockSupport() {}
-  virtual ~nsMacDockSupport() {}
-  
+  nsMacDockSupport();
+  virtual ~nsMacDockSupport();
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMACDOCKSUPPORT
+  NS_DECL_NSITASKBARPROGRESS
 
 protected:
   nsCOMPtr<nsIStandaloneNativeMenu> mDockMenu;
   nsString mBadgeText;
+
+  NSImage *mAppIcon, *mProgressBackground;
+  HIThemeTrackDrawInfo mProgressDrawInfo;
+
+  nsTaskbarProgressState mProgressState;
+  double mProgressFraction;
+  nsCOMPtr<nsITimer> mProgressTimer;
+
+  static void RedrawIconCallback(nsITimer* aTimer, void* aClosure);
+
+  bool InitProgress();
+  nsresult RedrawIcon();
 };
--- a/widget/cocoa/nsMacDockSupport.mm
+++ b/widget/cocoa/nsMacDockSupport.mm
@@ -1,19 +1,45 @@
 /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#import <Carbon/Carbon.h>
 #import <Cocoa/Cocoa.h>
 
 #include "nsMacDockSupport.h"
 #include "nsObjCExceptions.h"
 
-NS_IMPL_ISUPPORTS1(nsMacDockSupport, nsIMacDockSupport)
+NS_IMPL_ISUPPORTS2(nsMacDockSupport, nsIMacDockSupport, nsITaskbarProgress)
+
+nsMacDockSupport::nsMacDockSupport()
+: mAppIcon(nil)
+, mProgressBackground(nil)
+, mProgressState(STATE_NO_PROGRESS)
+, mProgressFraction(0.0)
+{
+  mProgressTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+}
+
+nsMacDockSupport::~nsMacDockSupport()
+{
+  if (mAppIcon) {
+    [mAppIcon release];
+    mAppIcon = nil;
+  }
+  if (mProgressBackground) {
+    [mProgressBackground release];
+    mProgressBackground = nil;
+  }
+  if (mProgressTimer) {
+    mProgressTimer->Cancel();
+    mProgressTimer = nullptr;
+  }
+}
 
 NS_IMETHODIMP
 nsMacDockSupport::GetDockMenu(nsIStandaloneNativeMenu ** aDockMenu)
 {
   *aDockMenu = nullptr;
 
   if (mDockMenu)
     return mDockMenu->QueryInterface(NS_GET_IID(nsIStandaloneNativeMenu),
@@ -57,8 +83,121 @@ nsMacDockSupport::SetBadgeText(const nsA
 }
 
 NS_IMETHODIMP
 nsMacDockSupport::GetBadgeText(nsAString& aBadgeText)
 {
   aBadgeText = mBadgeText;
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsMacDockSupport::SetProgressState(nsTaskbarProgressState aState,
+                                   PRUint64 aCurrentValue,
+                                   PRUint64 aMaxValue)
+{
+  NS_ENSURE_ARG_RANGE(aState, 0, STATE_PAUSED);
+  if (aState == STATE_NO_PROGRESS || aState == STATE_INDETERMINATE) {
+    NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG);
+    NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG);
+  }
+  if (aCurrentValue > aMaxValue) {
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  mProgressState = aState;
+  if (aMaxValue == 0) {
+    mProgressFraction = 0;
+  } else {
+    mProgressFraction = (double)aCurrentValue / aMaxValue;
+  }
+
+  if (mProgressState == STATE_NORMAL || mProgressState == STATE_INDETERMINATE) {
+    int perSecond = 30;
+    mProgressTimer->InitWithFuncCallback(RedrawIconCallback, this, 1000 / perSecond,
+      nsITimer::TYPE_REPEATING_SLACK);
+    return NS_OK;
+  } else {
+    mProgressTimer->Cancel();
+    return RedrawIcon();
+  }
+}
+
+// static
+void nsMacDockSupport::RedrawIconCallback(nsITimer* aTimer, void* aClosure)
+{
+  static_cast<nsMacDockSupport*>(aClosure)->RedrawIcon();
+}
+
+// Return whether to draw progress
+bool nsMacDockSupport::InitProgress()
+{
+  if (mProgressState != STATE_NORMAL && mProgressState != STATE_INDETERMINATE) {
+    return false;
+  }
+
+  if (!mAppIcon) {
+    mProgressTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    mAppIcon = [[NSImage imageNamed:@"NSApplicationIcon"] retain];
+    mProgressBackground = [mAppIcon copyWithZone:nil];
+
+    NSSize sz = [mProgressBackground size];
+    mProgressDrawInfo.version = 0;
+    mProgressDrawInfo.min = 0;
+    mProgressDrawInfo.value = 0;
+    mProgressDrawInfo.max = PR_INT32_MAX;
+    mProgressDrawInfo.bounds = CGRectMake(sz.width * 1/32, sz.height * 3/32,
+                                          sz.width * 30/32, sz.height * 2/32);
+    mProgressDrawInfo.attributes = kThemeTrackHorizontal;
+    mProgressDrawInfo.enableState = kThemeTrackActive;
+    mProgressDrawInfo.kind = kThemeLargeProgressBar;
+    mProgressDrawInfo.trackInfo.progress.phase = 0;
+
+    // Draw a light background, to visually distinguish the progress.
+    HIRect bounds;
+    HIThemeGetTrackBounds(&mProgressDrawInfo, &bounds);
+    // Margins within track, empirically. FIXME: Don't hardcode?
+    int mleft = 3, mtop = 3, mright = 3, mbot = 1;
+    bounds.origin.x += mleft;
+    bounds.origin.y += mbot;
+    bounds.size.width -= mleft + mright;
+    bounds.size.height -= mtop + mbot;
+
+    [mProgressBackground lockFocus];
+    [[NSColor whiteColor] set];
+    NSRectFill(NSRectFromCGRect(bounds));
+    [mProgressBackground unlockFocus];
+  }
+  return true;
+}
+
+nsresult
+nsMacDockSupport::RedrawIcon()
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  if (InitProgress()) {
+    // TODO: - Share code with nsNativeThemeCocoa?
+    //       - Implement ERROR and PAUSED states?
+    NSImage *icon = [mProgressBackground copyWithZone:nil];
+
+    bool isIndeterminate = (mProgressState != STATE_NORMAL);
+    mProgressDrawInfo.value = PR_INT32_MAX * mProgressFraction;
+    mProgressDrawInfo.kind = isIndeterminate ? kThemeLargeIndeterminateBar
+                                             : kThemeLargeProgressBar;
+
+    int stepsPerSecond = isIndeterminate ? 60 : 30;
+    mProgressDrawInfo.trackInfo.progress.phase =
+      PR_IntervalToMilliseconds(PR_IntervalNow()) * stepsPerSecond / 1000 % 32;
+
+    [icon lockFocus];
+    CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+    HIThemeDrawTrack(&mProgressDrawInfo, NULL, ctx, kHIThemeOrientationNormal);
+    [icon unlockFocus];
+    [NSApp setApplicationIconImage:icon];
+    [icon release];
+  } else {
+    [NSApp setApplicationIconImage:mAppIcon];
+  }
+
+  return NS_OK;
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
--- a/widget/nsIMacDockSupport.idl
+++ b/widget/nsIMacDockSupport.idl
@@ -3,16 +3,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIStandaloneNativeMenu;
 
 /**
  * Allow applications to interface with the Mac OS X Dock.
+ *
+ * Applications may indicate progress on their Dock icon. Only one such
+ * progress indicator is available to the entire application.
  */
 
 [scriptable, uuid(8BE66B0C-5F71-4B74-98CF-6C2551B999B1)]
 interface nsIMacDockSupport : nsISupports
 {
   /**
    * Menu to use for application-specific dock menu items.
    */
--- a/widget/tests/Makefile.in
+++ b/widget/tests/Makefile.in
@@ -75,23 +75,24 @@ MOCHITEST_CHROME_FILES += native_menus_w
                test_platform_colors.xul \
                test_standalone_native_menu.xul \
                standalone_native_menu_window.xul \
                test_bug586713.xul \
                bug586713_window.xul \
                test_key_event_counts.xul \
                test_bug596600.xul \
                test_bug673301.xul \
+               test_taskbar_progress.xul \
                $(NULL)
 endif
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
 MOCHITEST_CHROME_FILES  += taskbar_previews.xul \
 		window_state_windows.xul \
-		taskbar_progress.xul \
+		$(warning test_taskbar_progress.xul disabled due to regression, see bug 605813) \
 		test_chrome_context_menus_win.xul \
 		test_plugin_input_event.html \
 		chrome_context_menus_win.xul \
 		test_mouse_scroll.xul \
 		window_mouse_scroll_win.html \
 		$(NULL)
 
 MOCHITEST_FILES +=	test_bug565392.html \
rename from widget/tests/taskbar_progress.xul
rename to widget/tests/test_taskbar_progress.xul
--- a/widget/tests/taskbar_progress.xul
+++ b/widget/tests/test_taskbar_progress.xul
@@ -11,54 +11,60 @@
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script class="testbody" type="application/javascript">
   <![CDATA[
     let Cc = Components.classes;
     let Ci = Components.interfaces;
     let Cu = Components.utils;
     Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-    let taskbar = Cc["@mozilla.org/windows-taskbar;1"].getService(Ci.nsIWinTaskbar);
-
-    function IsWin7OrHigher() {
-      try {
-        var sysInfo = Cc["@mozilla.org/system-info;1"].
-                      getService(Ci.nsIPropertyBag2);
-        var ver = parseFloat(sysInfo.getProperty("version"));
-        if (ver >= 6.1)
-          return true;
-      } catch (ex) { }
-      return false;
-    }
-
-    SimpleTest.waitForExplicitFinish();
-
-    function loaded()
-    {
+    
+    let TP = Ci.nsITaskbarProgress;
+    
+    function winProgress() {
+      let taskbar = Cc["@mozilla.org/windows-taskbar;1"];
+      if (!taskbar)
+        return null;
+      taskbar = taskbar.getService(Ci.nsIWinTaskbar);
       if (!taskbar.available)
-        SimpleTest.finish();
-
+        return null;
+      
       // HACK from mconnor:
       var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
       let win = wm.getMostRecentWindow("navigator:browser");
       let docShell = win.gBrowser.docShell;
 
-      let TP = Ci.nsITaskbarProgress;
-
       let progress = taskbar.getTaskbarProgress(docShell);
       isnot(progress, null, "Progress is not null");
 
       try {
         taskbar.getTaskbarProgress(null);
         ok(false, "Got progress for null docshell");
       } catch (e) {
         ok(true, "Cannot get progress for null docshell");
       }
+      
+      return progress;
+    }
+    
+    function macProgress() {
+      let progress = Cc["@mozilla.org/widget/macdocksupport;1"];
+      if (!progress)
+        return null;
+      return progress.getService(TP);
+    }
 
+    SimpleTest.waitForExplicitFinish();
+
+    function loaded()
+    {
+      let progress = winProgress() || macProgress();
+      if (!TP)
+        SimpleTest.finish();
+      
       function shouldThrow(s,c,m) {
         try {
           progress.setProgressState(s,c,m);
           return false;
         } catch (e) {
           return true;
         }
       }