Bug 548763 - Show download progress in OS X app dock icon. r=dao r=josh

--HG--
rename : widget/tests/taskbar_progress.xul => widget/tests/test_taskbar_progress.xul
This commit is contained in:
Dave Vasilevsky 2013-03-03 05:58:00 -05:00
Родитель 092affd5c4
Коммит 107c52d281
12 изменённых файлов: 275 добавлений и 80 удалений

Просмотреть файл

@ -1486,14 +1486,9 @@ var gBrowserInit = {
// downloads will start right away, and getting the service again won't hurt. // downloads will start right away, and getting the service again won't hurt.
setTimeout(function() { setTimeout(function() {
Services.downloads; Services.downloads;
let DownloadTaskbarProgress =
#ifdef XP_WIN Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
if (Win7Features) { DownloadTaskbarProgress.onBrowserWindowLoad(window);
let DownloadTaskbarProgress =
Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
DownloadTaskbarProgress.onBrowserWindowLoad(window);
}
#endif
}, 10000); }, 10000);
// The object handling the downloads indicator is also initialized here in the // The object handling the downloads indicator is also initialized here in the

Просмотреть файл

@ -14,13 +14,21 @@ this.EXPORTED_SYMBOLS = [
const Cc = Components.classes; const Cc = Components.classes;
const Ci = Components.interfaces; 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 //// DownloadTaskbarProgress Object
this.DownloadTaskbarProgress = this.DownloadTaskbarProgress =
{ {
init: function DTP_init()
{
if (DownloadTaskbarProgressUpdater) {
DownloadTaskbarProgressUpdater._init();
}
},
/** /**
* Called when a browser window appears. This has an effect only when we * Called when a browser window appears. This has an effect only when we
* don't already have an active window. * don't already have an active window.
@ -31,6 +39,7 @@ this.DownloadTaskbarProgress =
*/ */
onBrowserWindowLoad: function DTP_onBrowserWindowLoad(aWindow) onBrowserWindowLoad: function DTP_onBrowserWindowLoad(aWindow)
{ {
this.init();
if (!DownloadTaskbarProgressUpdater) { if (!DownloadTaskbarProgressUpdater) {
return; return;
} }
@ -83,6 +92,9 @@ this.DownloadTaskbarProgress =
var DownloadTaskbarProgressUpdater = var DownloadTaskbarProgressUpdater =
{ {
/// Whether the taskbar is initialized.
_initialized: false,
/// Reference to the taskbar. /// Reference to the taskbar.
_taskbar: null, _taskbar: null,
@ -94,18 +106,27 @@ var DownloadTaskbarProgressUpdater =
*/ */
_init: function DTPU_init() _init: function DTPU_init()
{ {
if (!(kTaskbarID in Cc)) { if (this._initialized) {
// This means that the component isn't available 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; DownloadTaskbarProgressUpdater = null;
return; return;
} }
this._taskbar = Cc[kTaskbarID].getService(Ci.nsIWinTaskbar); this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS;
if (!this._taskbar.available) {
// The Windows version is probably too old
DownloadTaskbarProgressUpdater = null;
return;
}
this._dm = Cc["@mozilla.org/download-manager;1"]. this._dm = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager); getService(Ci.nsIDownloadManager);
@ -126,6 +147,8 @@ var DownloadTaskbarProgressUpdater =
_uninit: function DTPU_uninit() { _uninit: function DTPU_uninit() {
this._dm.removeListener(this); this._dm.removeListener(this);
this._os.removeObserver(this, "quit-application-granted"); this._os.removeObserver(this, "quit-application-granted");
this._activeTaskbarProgress = null;
this._initialized = false;
}, },
/** /**
@ -150,6 +173,7 @@ var DownloadTaskbarProgressUpdater =
*/ */
_setActiveWindow: function DTPU_setActiveWindow(aWindow, aIsDownloadWindow) _setActiveWindow: function DTPU_setActiveWindow(aWindow, aIsDownloadWindow)
{ {
#ifdef XP_WIN
// Clear out the taskbar for the old active window. (If there was no active // Clear out the taskbar for the old active window. (If there was no active
// window, this is a no-op.) // window, this is a no-op.)
this._clearTaskbar(); this._clearTaskbar();
@ -175,13 +199,27 @@ var DownloadTaskbarProgressUpdater =
else { else {
this._activeTaskbarProgress = null; this._activeTaskbarProgress = null;
} }
#endif
}, },
/// Current state displayed on the active window's taskbar item /// Current state displayed on the active window's taskbar item
_taskbarState: Ci.nsITaskbarProgress.STATE_NO_PROGRESS, _taskbarState: null,
_totalSize: 0, _totalSize: 0,
_totalTransferred: 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 * Update the active window's taskbar indicator with the current state. There
* are two cases here: * are two cases here:
@ -198,11 +236,7 @@ var DownloadTaskbarProgressUpdater =
return; return;
} }
// If the active window is not the download manager window, set the state if (this._shouldSetState()) {
// only if it is normal or indeterminate.
if (this._activeWindowIsDownloadWindow ||
(this._taskbarState == Ci.nsITaskbarProgress.STATE_NORMAL ||
this._taskbarState == Ci.nsITaskbarProgress.STATE_INDETERMINATE)) {
this._activeTaskbarProgress.setProgressState(this._taskbarState, this._activeTaskbarProgress.setProgressState(this._taskbarState,
this._totalTransferred, this._totalTransferred,
this._totalSize); this._totalSize);
@ -361,8 +395,3 @@ var DownloadTaskbarProgressUpdater =
} }
} }
}; };
////////////////////////////////////////////////////////////////////////////////
//// Initialization
DownloadTaskbarProgressUpdater._init();

Просмотреть файл

@ -21,10 +21,6 @@ EXTRA_JS_MODULES = \
DownloadUtils.jsm \ DownloadUtils.jsm \
$(NULL) $(NULL)
ifeq ($(OS_ARCH),WINNT) EXTRA_PP_JS_MODULES = DownloadTaskbarProgress.jsm
EXTRA_JS_MODULES += \
DownloadTaskbarProgress.jsm \
$(NULL)
endif
include $(topsrcdir)/config/rules.mk include $(topsrcdir)/config/rules.mk

Просмотреть файл

@ -447,11 +447,9 @@ function Startup()
} }
}, false); }, false);
#ifdef XP_WIN let DownloadTaskbarProgress =
let tempScope = {}; Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", tempScope); DownloadTaskbarProgress.onDownloadWindowLoad(window);
tempScope.DownloadTaskbarProgress.onDownloadWindowLoad(window);
#endif
} }
function Shutdown() function Shutdown()

Просмотреть файл

@ -48,9 +48,13 @@ MOCHITEST_CHROME_FILES += \
$(NULL) $(NULL)
endif endif
ifeq ($(OS_ARCH),WINNT) ifneq (,$(filter WINNT, $(OS_ARCH))$(filter cocoa, $(MOZ_WIDGET_TOOLKIT)))
MOCHITEST_CHROME_FILES += \ MOCHITEST_CHROME_FILES += \
test_taskbarprogress_downloadstates.xul \ test_taskbarprogress_downloadstates.xul \
$(NULL)
endif
ifeq ($(OS_ARCH),WINNT)
MOCHITEST_CHROME_FILES += \
$(filter disabled-for-very-frequent-orange--bug-630567, test_taskbarprogress_service.xul) \ $(filter disabled-for-very-frequent-orange--bug-630567, test_taskbarprogress_service.xul) \
$(NULL) $(NULL)
endif endif

Просмотреть файл

@ -166,17 +166,20 @@ function testSetup()
return; return;
} }
let isWin7OrHigher = false; let isWin = /Win/.test(navigator.platform);
try { if (isWin) {
let version = Cc["@mozilla.org/system-info;1"] let isWin7OrHigher = false;
.getService(Ci.nsIPropertyBag2) try {
.getProperty("version"); let version = Cc["@mozilla.org/system-info;1"]
isWin7OrHigher = (parseFloat(version) >= 6.1); .getService(Ci.nsIPropertyBag2)
} catch (ex) { } .getProperty("version");
isWin7OrHigher = (parseFloat(version) >= 6.1);
} catch (ex) { }
if (!isWin7OrHigher) { if (!isWin7OrHigher) {
ok(true, "This test only runs on Windows 7 or higher"); ok(true, "This test only runs on Windows 7 or higher");
return; return;
}
} }
let tempScope = {}; let tempScope = {};
@ -184,10 +187,13 @@ function testSetup()
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
DownloadTaskbarProgress = tempScope.DownloadTaskbarProgress; DownloadTaskbarProgress = tempScope.DownloadTaskbarProgress;
let TaskbarService = Cc[kTaskbarID].getService(Ci.nsIWinTaskbar);
isnot(DownloadTaskbarProgress, null, "Download taskbar progress service exists"); isnot(DownloadTaskbarProgress, null, "Download taskbar progress service exists");
is(TaskbarService.available, true, "Taskbar Service is available"); DownloadTaskbarProgress.init();
if (isWin) {
let TaskbarService = Cc[kTaskbarID].getService(Ci.nsIWinTaskbar);
is(TaskbarService.available, true, "Taskbar Service is available");
}
dm = Cc["@mozilla.org/download-manager;1"]. dm = Cc["@mozilla.org/download-manager;1"].
getService(Ci.nsIDownloadManager); getService(Ci.nsIDownloadManager);

Просмотреть файл

@ -122,6 +122,7 @@ ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
XPIDLSRCS += nsIMacDockSupport.idl \ XPIDLSRCS += nsIMacDockSupport.idl \
nsIStandaloneNativeMenu.idl \ nsIStandaloneNativeMenu.idl \
nsIMacWebAppUtils.idl \ nsIMacWebAppUtils.idl \
nsITaskbarProgress.idl \
$(NULL) $(NULL)
endif endif

Просмотреть файл

@ -3,21 +3,38 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#import <Carbon/Carbon.h>
#include "nsIMacDockSupport.h" #include "nsIMacDockSupport.h"
#include "nsIStandaloneNativeMenu.h" #include "nsIStandaloneNativeMenu.h"
#include "nsITaskbarProgress.h"
#include "nsITimer.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsString.h" #include "nsString.h"
class nsMacDockSupport : public nsIMacDockSupport class nsMacDockSupport : public nsIMacDockSupport, public nsITaskbarProgress
{ {
public: public:
nsMacDockSupport() {} nsMacDockSupport();
virtual ~nsMacDockSupport() {} virtual ~nsMacDockSupport();
NS_DECL_ISUPPORTS NS_DECL_ISUPPORTS
NS_DECL_NSIMACDOCKSUPPORT NS_DECL_NSIMACDOCKSUPPORT
NS_DECL_NSITASKBARPROGRESS
protected: protected:
nsCOMPtr<nsIStandaloneNativeMenu> mDockMenu; nsCOMPtr<nsIStandaloneNativeMenu> mDockMenu;
nsString mBadgeText; 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();
}; };

Просмотреть файл

@ -3,12 +3,38 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#import <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#include "nsMacDockSupport.h" #include "nsMacDockSupport.h"
#include "nsObjCExceptions.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 NS_IMETHODIMP
nsMacDockSupport::GetDockMenu(nsIStandaloneNativeMenu ** aDockMenu) nsMacDockSupport::GetDockMenu(nsIStandaloneNativeMenu ** aDockMenu)
@ -62,3 +88,116 @@ nsMacDockSupport::GetBadgeText(nsAString& aBadgeText)
aBadgeText = mBadgeText; aBadgeText = mBadgeText;
return NS_OK; 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;
}

Просмотреть файл

@ -8,6 +8,9 @@ interface nsIStandaloneNativeMenu;
/** /**
* Allow applications to interface with the Mac OS X Dock. * 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)] [scriptable, uuid(8BE66B0C-5F71-4B74-98CF-6C2551B999B1)]

Просмотреть файл

@ -80,13 +80,14 @@ MOCHITEST_CHROME_FILES += native_menus_window.xul \
test_key_event_counts.xul \ test_key_event_counts.xul \
test_bug596600.xul \ test_bug596600.xul \
test_bug673301.xul \ test_bug673301.xul \
test_taskbar_progress.xul \
$(NULL) $(NULL)
endif endif
ifeq ($(MOZ_WIDGET_TOOLKIT),windows) ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
MOCHITEST_CHROME_FILES += taskbar_previews.xul \ MOCHITEST_CHROME_FILES += taskbar_previews.xul \
window_state_windows.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_chrome_context_menus_win.xul \
test_plugin_input_event.html \ test_plugin_input_event.html \
chrome_context_menus_win.xul \ chrome_context_menus_win.xul \

Просмотреть файл

@ -17,33 +17,21 @@
let Cu = Components.utils; let Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let taskbar = Cc["@mozilla.org/windows-taskbar;1"].getService(Ci.nsIWinTaskbar); let TP = Ci.nsITaskbarProgress;
function IsWin7OrHigher() { function winProgress() {
try { let taskbar = Cc["@mozilla.org/windows-taskbar;1"];
var sysInfo = Cc["@mozilla.org/system-info;1"]. if (!taskbar)
getService(Ci.nsIPropertyBag2); return null;
var ver = parseFloat(sysInfo.getProperty("version")); taskbar = taskbar.getService(Ci.nsIWinTaskbar);
if (ver >= 6.1)
return true;
} catch (ex) { }
return false;
}
SimpleTest.waitForExplicitFinish();
function loaded()
{
if (!taskbar.available) if (!taskbar.available)
SimpleTest.finish(); return null;
// HACK from mconnor: // HACK from mconnor:
var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator); var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
let win = wm.getMostRecentWindow("navigator:browser"); let win = wm.getMostRecentWindow("navigator:browser");
let docShell = win.gBrowser.docShell; let docShell = win.gBrowser.docShell;
let TP = Ci.nsITaskbarProgress;
let progress = taskbar.getTaskbarProgress(docShell); let progress = taskbar.getTaskbarProgress(docShell);
isnot(progress, null, "Progress is not null"); isnot(progress, null, "Progress is not null");
@ -54,6 +42,24 @@
ok(true, "Cannot get progress for null docshell"); 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) { function shouldThrow(s,c,m) {
try { try {
progress.setProgressState(s,c,m); progress.setProgressState(s,c,m);