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.
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

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

@ -14,13 +14,21 @@ this.EXPORTED_SYMBOLS = [
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.
@ -31,6 +39,7 @@ this.DownloadTaskbarProgress =
*/
onBrowserWindowLoad: function DTP_onBrowserWindowLoad(aWindow)
{
this.init();
if (!DownloadTaskbarProgressUpdater) {
return;
}
@ -83,6 +92,9 @@ this.DownloadTaskbarProgress =
var DownloadTaskbarProgressUpdater =
{
/// Whether the taskbar is initialized.
_initialized: false,
/// Reference to the taskbar.
_taskbar: null,
@ -94,18 +106,27 @@ var DownloadTaskbarProgressUpdater =
*/
_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);
@ -126,6 +147,8 @@ var DownloadTaskbarProgressUpdater =
_uninit: function DTPU_uninit() {
this._dm.removeListener(this);
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)
{
#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();
@ -175,13 +199,27 @@ var DownloadTaskbarProgressUpdater =
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:
@ -198,11 +236,7 @@ var DownloadTaskbarProgressUpdater =
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);
@ -361,8 +395,3 @@ var DownloadTaskbarProgressUpdater =
}
}
};
////////////////////////////////////////////////////////////////////////////////
//// Initialization
DownloadTaskbarProgressUpdater._init();

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

@ -21,10 +21,6 @@ EXTRA_JS_MODULES = \
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

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

@ -447,11 +447,9 @@ function Startup()
}
}, 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()

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

@ -48,9 +48,13 @@ MOCHITEST_CHROME_FILES += \
$(NULL)
endif
ifeq ($(OS_ARCH),WINNT)
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 += \
$(filter disabled-for-very-frequent-orange--bug-630567, test_taskbarprogress_service.xul) \
$(NULL)
endif

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

@ -166,17 +166,20 @@ function testSetup()
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 = {};
@ -184,10 +187,13 @@ function testSetup()
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");
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"].
getService(Ci.nsIDownloadManager);

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

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

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

@ -3,21 +3,38 @@
* 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();
};

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

@ -3,12 +3,38 @@
* 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)
@ -62,3 +88,116 @@ 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;
}

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

@ -8,6 +8,9 @@ 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)]

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

@ -80,13 +80,14 @@ MOCHITEST_CHROME_FILES += native_menus_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 \

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

@ -16,34 +16,22 @@
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");
@ -53,7 +41,25 @@
} 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);