mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-22 08:27:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
1553
browser/components/downloads/DownloadsCommon.jsm
Normal file
1553
browser/components/downloads/DownloadsCommon.jsm
Normal file
File diff suppressed because it is too large
Load diff
177
browser/components/downloads/DownloadsTaskbar.jsm
Normal file
177
browser/components/downloads/DownloadsTaskbar.jsm
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Handles the download progress indicator in the taskbar.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"DownloadsTaskbar",
|
||||
];
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
|
||||
"resource://gre/modules/Downloads.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
||||
"resource:///modules/RecentWindow.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gWinTaskbar", function () {
|
||||
if (!("@mozilla.org/windows-taskbar;1" in Cc)) {
|
||||
return null;
|
||||
}
|
||||
let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"]
|
||||
.getService(Ci.nsIWinTaskbar);
|
||||
return winTaskbar.available && winTaskbar;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gMacTaskbarProgress", function () {
|
||||
return ("@mozilla.org/widget/macdocksupport;1" in Cc) &&
|
||||
Cc["@mozilla.org/widget/macdocksupport;1"]
|
||||
.getService(Ci.nsITaskbarProgress);
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// DownloadsTaskbar
|
||||
|
||||
/**
|
||||
* Handles the download progress indicator in the taskbar.
|
||||
*/
|
||||
this.DownloadsTaskbar = {
|
||||
/**
|
||||
* Underlying DownloadSummary providing the aggregate download information, or
|
||||
* null if the indicator has never been initialized.
|
||||
*/
|
||||
_summary: null,
|
||||
|
||||
/**
|
||||
* nsITaskbarProgress object to which download information is dispatched.
|
||||
* This can be null if the indicator has never been initialized or if the
|
||||
* indicator is currently hidden on Windows.
|
||||
*/
|
||||
_taskbarProgress: null,
|
||||
|
||||
/**
|
||||
* This method is called after a new browser window is opened, and ensures
|
||||
* that the download progress indicator is displayed in the taskbar.
|
||||
*
|
||||
* On Windows, the indicator is attached to the first browser window that
|
||||
* calls this method. When the window is closed, the indicator is moved to
|
||||
* another browser window, if available, in no particular order. When there
|
||||
* are no browser windows visible, the indicator is hidden.
|
||||
*
|
||||
* On Mac OS X, the indicator is initialized globally when this method is
|
||||
* called for the first time. Subsequent calls have no effect.
|
||||
*
|
||||
* @param aBrowserWindow
|
||||
* nsIDOMWindow object of the newly opened browser window to which the
|
||||
* indicator may be attached.
|
||||
*/
|
||||
registerIndicator(aBrowserWindow) {
|
||||
if (!this._taskbarProgress) {
|
||||
if (gMacTaskbarProgress) {
|
||||
// On Mac OS X, we have to register the global indicator only once.
|
||||
this._taskbarProgress = gMacTaskbarProgress;
|
||||
// Free the XPCOM reference on shutdown, to prevent detecting a leak.
|
||||
Services.obs.addObserver(() => {
|
||||
this._taskbarProgress = null;
|
||||
gMacTaskbarProgress = null;
|
||||
}, "quit-application-granted", false);
|
||||
} else if (gWinTaskbar) {
|
||||
// On Windows, the indicator is currently hidden because we have no
|
||||
// previous browser window, thus we should attach the indicator now.
|
||||
this._attachIndicator(aBrowserWindow);
|
||||
} else {
|
||||
// The taskbar indicator is not available on this platform.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the DownloadSummary object will be created asynchronously.
|
||||
if (!this._summary) {
|
||||
Downloads.getSummary(Downloads.ALL).then(summary => {
|
||||
// In case the method is re-entered, we simply ignore redundant
|
||||
// invocations of the callback, instead of keeping separate state.
|
||||
if (this._summary) {
|
||||
return;
|
||||
}
|
||||
this._summary = summary;
|
||||
return this._summary.addView(this);
|
||||
}).then(null, Cu.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* On Windows, attaches the taskbar indicator to the specified browser window.
|
||||
*/
|
||||
_attachIndicator(aWindow) {
|
||||
// Activate the indicator on the specified window.
|
||||
let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIXULWindow).docShell;
|
||||
this._taskbarProgress = gWinTaskbar.getTaskbarProgress(docShell);
|
||||
|
||||
// If the DownloadSummary object has already been created, we should update
|
||||
// the state of the new indicator, otherwise it will be updated as soon as
|
||||
// the DownloadSummary view is registered.
|
||||
if (this._summary) {
|
||||
this.onSummaryChanged();
|
||||
}
|
||||
|
||||
aWindow.addEventListener("unload", () => {
|
||||
// Locate another browser window, excluding the one being closed.
|
||||
let browserWindow = RecentWindow.getMostRecentBrowserWindow();
|
||||
if (browserWindow) {
|
||||
// Move the progress indicator to the other browser window.
|
||||
this._attachIndicator(browserWindow);
|
||||
} else {
|
||||
// The last browser window has been closed. We remove the reference to
|
||||
// the taskbar progress object so that the indicator will be registered
|
||||
// again on the next browser window that is opened.
|
||||
this._taskbarProgress = null;
|
||||
}
|
||||
}, false);
|
||||
},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// DownloadSummary view
|
||||
|
||||
onSummaryChanged() {
|
||||
// If the last browser window has been closed, we have no indicator any more.
|
||||
if (!this._taskbarProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._summary.allHaveStopped || this._summary.progressTotalBytes == 0) {
|
||||
this._taskbarProgress.setProgressState(
|
||||
Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0);
|
||||
} else {
|
||||
// For a brief moment before completion, some download components may
|
||||
// report more transferred bytes than the total number of bytes. Thus,
|
||||
// ensure that we never break the expectations of the progress indicator.
|
||||
let progressCurrentBytes = Math.min(this._summary.progressTotalBytes,
|
||||
this._summary.progressCurrentBytes);
|
||||
this._taskbarProgress.setProgressState(
|
||||
Ci.nsITaskbarProgress.STATE_NORMAL,
|
||||
progressCurrentBytes,
|
||||
this._summary.progressTotalBytes);
|
||||
}
|
||||
},
|
||||
};
|
||||
395
browser/components/downloads/DownloadsViewUI.jsm
Normal file
395
browser/components/downloads/DownloadsViewUI.jsm
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
/* 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/. */
|
||||
|
||||
/*
|
||||
* This module is imported by code that uses the "download.xml" binding, and
|
||||
* provides prototypes for objects that handle input and display information.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"DownloadsViewUI",
|
||||
];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
|
||||
"resource://gre/modules/Downloads.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
|
||||
"resource://gre/modules/DownloadUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
|
||||
"resource:///modules/DownloadsCommon.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
|
||||
this.DownloadsViewUI = {
|
||||
/**
|
||||
* Returns true if the given string is the name of a command that can be
|
||||
* handled by the Downloads user interface, including standard commands.
|
||||
*/
|
||||
isCommandName(name) {
|
||||
return name.startsWith("cmd_") || name.startsWith("downloadsCmd_");
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A download element shell is responsible for handling the commands and the
|
||||
* displayed data for a single element that uses the "download.xml" binding.
|
||||
*
|
||||
* The information to display is obtained through the associated Download object
|
||||
* from the JavaScript API for downloads, and commands are executed using a
|
||||
* combination of Download methods and DownloadsCommon.jsm helper functions.
|
||||
*
|
||||
* Specialized versions of this shell must be defined, and they are required to
|
||||
* implement the "download" property or getter. Currently these objects are the
|
||||
* HistoryDownloadElementShell and the DownloadsViewItem for the panel. The
|
||||
* history view may use a HistoryDownload object in place of a Download object.
|
||||
*/
|
||||
this.DownloadsViewUI.DownloadElementShell = function () {}
|
||||
|
||||
this.DownloadsViewUI.DownloadElementShell.prototype = {
|
||||
/**
|
||||
* The richlistitem for the download, initialized by the derived object.
|
||||
*/
|
||||
element: null,
|
||||
|
||||
/**
|
||||
* URI string for the file type icon displayed in the download element.
|
||||
*/
|
||||
get image() {
|
||||
if (!this.download.target.path) {
|
||||
// Old history downloads may not have a target path.
|
||||
return "moz-icon://.unknown?size=32";
|
||||
}
|
||||
|
||||
// When a download that was previously in progress finishes successfully, it
|
||||
// means that the target file now exists and we can extract its specific
|
||||
// icon, for example from a Windows executable. To ensure that the icon is
|
||||
// reloaded, however, we must change the URI used by the XUL image element,
|
||||
// for example by adding a query parameter. This only works if we add one of
|
||||
// the parameters explicitly supported by the nsIMozIconURI interface.
|
||||
return "moz-icon://" + this.download.target.path + "?size=32" +
|
||||
(this.download.succeeded ? "&state=normal" : "");
|
||||
},
|
||||
|
||||
/**
|
||||
* The user-facing label for the download. This is normally the leaf name of
|
||||
* the download target file. In case this is a very old history download for
|
||||
* which the target file is unknown, the download source URI is displayed.
|
||||
*/
|
||||
get displayName() {
|
||||
if (!this.download.target.path) {
|
||||
return this.download.source.url;
|
||||
}
|
||||
return OS.Path.basename(this.download.target.path);
|
||||
},
|
||||
|
||||
/**
|
||||
* The progress element for the download, or undefined in case the XBL binding
|
||||
* has not been applied yet.
|
||||
*/
|
||||
get _progressElement() {
|
||||
if (!this.__progressElement) {
|
||||
// If the element is not available now, we will try again the next time.
|
||||
this.__progressElement =
|
||||
this.element.ownerDocument.getAnonymousElementByAttribute(
|
||||
this.element, "anonid",
|
||||
"progressmeter");
|
||||
}
|
||||
return this.__progressElement;
|
||||
},
|
||||
|
||||
/**
|
||||
* Processes a major state change in the user interface, then proceeds with
|
||||
* the normal progress update. This function is not called for every progress
|
||||
* update in order to improve performance.
|
||||
*/
|
||||
_updateState() {
|
||||
this.element.setAttribute("displayName", this.displayName);
|
||||
this.element.setAttribute("image", this.image);
|
||||
this.element.setAttribute("state",
|
||||
DownloadsCommon.stateOfDownload(this.download));
|
||||
|
||||
if (!this.download.succeeded && this.download.error &&
|
||||
this.download.error.becauseBlockedByReputationCheck) {
|
||||
this.element.setAttribute("verdict",
|
||||
this.download.error.reputationCheckVerdict);
|
||||
} else {
|
||||
this.element.removeAttribute("verdict");
|
||||
}
|
||||
|
||||
// Since state changed, reset the time left estimation.
|
||||
this.lastEstimatedSecondsLeft = Infinity;
|
||||
|
||||
this._updateProgress();
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the elements that change regularly for in-progress downloads,
|
||||
* namely the progress bar and the status line.
|
||||
*/
|
||||
_updateProgress() {
|
||||
if (this.download.succeeded) {
|
||||
// We only need to add or remove this attribute for succeeded downloads.
|
||||
if (this.download.target.exists) {
|
||||
this.element.setAttribute("exists", "true");
|
||||
} else {
|
||||
this.element.removeAttribute("exists");
|
||||
}
|
||||
}
|
||||
|
||||
// When a block is confirmed, the removal of blocked data will not trigger a
|
||||
// state change for the download, so this class must be updated here.
|
||||
this.element.classList.toggle("temporary-block",
|
||||
!!this.download.hasBlockedData);
|
||||
|
||||
// The progress bar is only displayed for in-progress downloads.
|
||||
if (this.download.hasProgress) {
|
||||
this.element.setAttribute("progressmode", "normal");
|
||||
this.element.setAttribute("progress", this.download.progress);
|
||||
} else {
|
||||
this.element.setAttribute("progressmode", "undetermined");
|
||||
}
|
||||
|
||||
if (this.download.stopped && this.download.canceled &&
|
||||
this.download.hasPartialData) {
|
||||
this.element.setAttribute("progresspaused", "true");
|
||||
} else {
|
||||
this.element.removeAttribute("progresspaused");
|
||||
}
|
||||
|
||||
// Dispatch the ValueChange event for accessibility, if possible.
|
||||
if (this._progressElement) {
|
||||
let event = this.element.ownerDocument.createEvent("Events");
|
||||
event.initEvent("ValueChange", true, true);
|
||||
this._progressElement.dispatchEvent(event);
|
||||
}
|
||||
|
||||
let status = this.statusTextAndTip;
|
||||
this.element.setAttribute("status", status.text);
|
||||
this.element.setAttribute("statusTip", status.tip);
|
||||
},
|
||||
|
||||
lastEstimatedSecondsLeft: Infinity,
|
||||
|
||||
/**
|
||||
* Returns the text for the status line and the associated tooltip. These are
|
||||
* returned by a single property because they are computed together. The
|
||||
* result may be overridden by derived objects.
|
||||
*/
|
||||
get statusTextAndTip() {
|
||||
return this.rawStatusTextAndTip;
|
||||
},
|
||||
|
||||
/**
|
||||
* Derived objects may call this to get the status text.
|
||||
*/
|
||||
get rawStatusTextAndTip() {
|
||||
let s = DownloadsCommon.strings;
|
||||
|
||||
let text = "";
|
||||
let tip = "";
|
||||
|
||||
if (!this.download.stopped) {
|
||||
let totalBytes = this.download.hasProgress ? this.download.totalBytes
|
||||
: -1;
|
||||
// By default, extended status information including the individual
|
||||
// download rate is displayed in the tooltip. The history view overrides
|
||||
// the getter and displays the datails in the main area instead.
|
||||
[text] = DownloadUtils.getDownloadStatusNoRate(
|
||||
this.download.currentBytes,
|
||||
totalBytes,
|
||||
this.download.speed,
|
||||
this.lastEstimatedSecondsLeft);
|
||||
let newEstimatedSecondsLeft;
|
||||
[tip, newEstimatedSecondsLeft] = DownloadUtils.getDownloadStatus(
|
||||
this.download.currentBytes,
|
||||
totalBytes,
|
||||
this.download.speed,
|
||||
this.lastEstimatedSecondsLeft);
|
||||
this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
|
||||
} else if (this.download.canceled && this.download.hasPartialData) {
|
||||
let totalBytes = this.download.hasProgress ? this.download.totalBytes
|
||||
: -1;
|
||||
let transfer = DownloadUtils.getTransferTotal(this.download.currentBytes,
|
||||
totalBytes);
|
||||
|
||||
// We use the same XUL label to display both the state and the amount
|
||||
// transferred, for example "Paused - 1.1 MB".
|
||||
text = s.statusSeparatorBeforeNumber(s.statePaused, transfer);
|
||||
} else if (!this.download.succeeded && !this.download.canceled &&
|
||||
!this.download.error) {
|
||||
text = s.stateStarting;
|
||||
} else {
|
||||
let stateLabel;
|
||||
|
||||
if (this.download.succeeded) {
|
||||
// For completed downloads, show the file size (e.g. "1.5 MB").
|
||||
if (this.download.target.size !== undefined) {
|
||||
let [size, unit] =
|
||||
DownloadUtils.convertByteUnits(this.download.target.size);
|
||||
stateLabel = s.sizeWithUnits(size, unit);
|
||||
} else {
|
||||
// History downloads may not have a size defined.
|
||||
stateLabel = s.sizeUnknown;
|
||||
}
|
||||
} else if (this.download.canceled) {
|
||||
stateLabel = s.stateCanceled;
|
||||
} else if (this.download.error.becauseBlockedByParentalControls) {
|
||||
stateLabel = s.stateBlockedParentalControls;
|
||||
} else if (this.download.error.becauseBlockedByReputationCheck) {
|
||||
stateLabel = this.rawBlockedTitleAndDetails[0];
|
||||
} else {
|
||||
stateLabel = s.stateFailed;
|
||||
}
|
||||
|
||||
let referrer = this.download.source.referrer || this.download.source.url;
|
||||
let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer);
|
||||
|
||||
let date = new Date(this.download.endTime);
|
||||
let [displayDate, fullDate] = DownloadUtils.getReadableDates(date);
|
||||
|
||||
let firstPart = s.statusSeparator(stateLabel, displayHost);
|
||||
text = s.statusSeparator(firstPart, displayDate);
|
||||
tip = s.statusSeparator(fullHost, fullDate);
|
||||
}
|
||||
|
||||
return { text, tip: tip || text };
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns [title, [details1, details2]] for blocked downloads.
|
||||
*/
|
||||
get rawBlockedTitleAndDetails() {
|
||||
let s = DownloadsCommon.strings;
|
||||
if (!this.download.error ||
|
||||
!this.download.error.becauseBlockedByReputationCheck) {
|
||||
return [null, null];
|
||||
}
|
||||
switch (this.download.error.reputationCheckVerdict) {
|
||||
case Downloads.Error.BLOCK_VERDICT_UNCOMMON:
|
||||
return [s.blockedUncommon2, [s.unblockTypeUncommon2, s.unblockTip2]];
|
||||
case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
|
||||
return [s.blockedPotentiallyUnwanted,
|
||||
[s.unblockTypePotentiallyUnwanted2, s.unblockTip2]];
|
||||
case Downloads.Error.BLOCK_VERDICT_MALWARE:
|
||||
return [s.blockedMalware, [s.unblockTypeMalware, s.unblockTip2]];
|
||||
}
|
||||
throw new Error("Unexpected reputationCheckVerdict: " +
|
||||
this.download.error.reputationCheckVerdict);
|
||||
// return anyway to avoid a JS strict warning.
|
||||
return [null, null];
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the appropriate unblock dialog based on the verdict, and executes the
|
||||
* action selected by the user in the dialog, which may involve unblocking,
|
||||
* opening or removing the file.
|
||||
*
|
||||
* @param window
|
||||
* The window to which the dialog should be anchored.
|
||||
* @param dialogType
|
||||
* Can be "unblock", "chooseUnblock", or "chooseOpen".
|
||||
*/
|
||||
confirmUnblock(window, dialogType) {
|
||||
DownloadsCommon.confirmUnblockDownload({
|
||||
verdict: this.download.error.reputationCheckVerdict,
|
||||
window,
|
||||
dialogType,
|
||||
}).then(action => {
|
||||
if (action == "open") {
|
||||
return this.unblockAndOpenDownload();
|
||||
} else if (action == "unblock") {
|
||||
return this.download.unblock();
|
||||
} else if (action == "confirmBlock") {
|
||||
return this.download.confirmBlock();
|
||||
}
|
||||
}).catch(Cu.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unblocks the downloaded file and opens it.
|
||||
*
|
||||
* @return A promise that's resolved after the file has been opened.
|
||||
*/
|
||||
unblockAndOpenDownload() {
|
||||
return this.download.unblock().then(() => this.downloadsCmd_open());
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the name of the default command to use for the current state of the
|
||||
* download, when there is a double click or another default interaction. If
|
||||
* there is no default command for the current state, returns an empty string.
|
||||
* The commands are implemented as functions on this object or derived ones.
|
||||
*/
|
||||
get currentDefaultCommandName() {
|
||||
switch (DownloadsCommon.stateOfDownload(this.download)) {
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED:
|
||||
return "downloadsCmd_cancel";
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
|
||||
return "downloadsCmd_retry";
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_PAUSED:
|
||||
return "downloadsCmd_pauseResume";
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
|
||||
return "downloadsCmd_open";
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
|
||||
return "downloadsCmd_openReferrer";
|
||||
case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
|
||||
return "downloadsCmd_showBlockedInfo";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the specified command can be invoked on the current item.
|
||||
* The commands are implemented as functions on this object or derived ones.
|
||||
*
|
||||
* @param aCommand
|
||||
* Name of the command to check, for example "downloadsCmd_retry".
|
||||
*/
|
||||
isCommandEnabled(aCommand) {
|
||||
switch (aCommand) {
|
||||
case "downloadsCmd_retry":
|
||||
return this.download.canceled || this.download.error;
|
||||
case "downloadsCmd_pauseResume":
|
||||
return this.download.hasPartialData && !this.download.error;
|
||||
case "downloadsCmd_openReferrer":
|
||||
return !!this.download.source.referrer;
|
||||
case "downloadsCmd_confirmBlock":
|
||||
case "downloadsCmd_chooseUnblock":
|
||||
case "downloadsCmd_chooseOpen":
|
||||
case "downloadsCmd_unblock":
|
||||
case "downloadsCmd_unblockAndOpen":
|
||||
return this.download.hasBlockedData;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
downloadsCmd_cancel() {
|
||||
// This is the correct way to avoid race conditions when cancelling.
|
||||
this.download.cancel().catch(() => {});
|
||||
this.download.removePartialData().catch(Cu.reportError);
|
||||
},
|
||||
|
||||
downloadsCmd_retry() {
|
||||
// Errors when retrying are already reported as download failures.
|
||||
this.download.start().catch(() => {});
|
||||
},
|
||||
|
||||
downloadsCmd_pauseResume() {
|
||||
if (this.download.stopped) {
|
||||
this.download.start();
|
||||
} else {
|
||||
this.download.cancel();
|
||||
}
|
||||
},
|
||||
|
||||
downloadsCmd_confirmBlock() {
|
||||
this.download.confirmBlock().catch(Cu.reportError);
|
||||
},
|
||||
};
|
||||
1439
browser/components/downloads/content/allDownloadsViewOverlay.js
Normal file
1439
browser/components/downloads/content/allDownloadsViewOverlay.js
Normal file
File diff suppressed because it is too large
Load diff
131
browser/components/downloads/content/allDownloadsViewOverlay.xul
Normal file
131
browser/components/downloads/content/allDownloadsViewOverlay.xul
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
# 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/.
|
||||
|
||||
<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/downloads/allDownloadsViewOverlay.css"?>
|
||||
|
||||
<!DOCTYPE overlay [
|
||||
<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
|
||||
%downloadsDTD;
|
||||
]>
|
||||
|
||||
<!-- This overlay provides a downloads view that lists both session downloads,
|
||||
using the DownloadsView API, and history downloads, using places queries.
|
||||
The view also implements a command controller and a context menu for
|
||||
managing the downloads list. In order to use this view:
|
||||
1. Apply this overlay to your window.
|
||||
2. Insert in all the overlay entry-points, namely:
|
||||
<richlistbox id="downloadsRichListBox"/>
|
||||
<commandset id="downloadCommands"/>
|
||||
<menupopup id="downloadsContextMenu"/>
|
||||
3. Make sure your window has the editMenuOverlay overlay applied,
|
||||
because the view implements cmd_copy and cmd_delete.
|
||||
4. Make sure your window has the globalOverlay.js script loaded.
|
||||
5. To initialize the view
|
||||
let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox"));
|
||||
// This is what the Places Library uses. It could be tweaked a bit as long as the
|
||||
// transition-type is set correctly
|
||||
view.place = "place:transition=7&sort=4";
|
||||
-->
|
||||
<overlay id="downloadsViewOverlay"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/downloads/allDownloadsViewOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://global/content/contentAreaUtils.js"/>
|
||||
|
||||
<richlistbox flex="1"
|
||||
seltype="multiple"
|
||||
id="downloadsRichListBox" context="downloadsContextMenu"
|
||||
onscroll="return this._placesView.onScroll();"
|
||||
onkeypress="return this._placesView.onKeyPress(event);"
|
||||
ondblclick="return this._placesView.onDoubleClick(event);"
|
||||
oncontextmenu="return this._placesView.onContextMenu(event);"
|
||||
ondragstart="this._placesView.onDragStart(event);"
|
||||
ondragover="this._placesView.onDragOver(event);"
|
||||
ondrop="this._placesView.onDrop(event);"
|
||||
onfocus="goUpdateDownloadCommands();"
|
||||
onselect="this._placesView.onSelect();"
|
||||
onblur="goUpdateDownloadCommands();"/>
|
||||
|
||||
<commandset id="downloadCommands"
|
||||
commandupdater="true"
|
||||
events="focus,select,contextmenu"
|
||||
oncommandupdate="goUpdateDownloadCommands();">
|
||||
<command id="downloadsCmd_pauseResume"
|
||||
oncommand="goDoCommand('downloadsCmd_pauseResume')"/>
|
||||
<command id="downloadsCmd_cancel"
|
||||
oncommand="goDoCommand('downloadsCmd_cancel')"/>
|
||||
<command id="downloadsCmd_unblock"
|
||||
oncommand="goDoCommand('downloadsCmd_unblock')"/>
|
||||
<command id="downloadsCmd_chooseUnblock"
|
||||
oncommand="goDoCommand('downloadsCmd_chooseUnblock')"/>
|
||||
<command id="downloadsCmd_chooseOpen"
|
||||
oncommand="goDoCommand('downloadsCmd_chooseOpen')"/>
|
||||
<command id="downloadsCmd_confirmBlock"
|
||||
oncommand="goDoCommand('downloadsCmd_confirmBlock')"/>
|
||||
<command id="downloadsCmd_open"
|
||||
oncommand="goDoCommand('downloadsCmd_open')"/>
|
||||
<command id="downloadsCmd_show"
|
||||
oncommand="goDoCommand('downloadsCmd_show')"/>
|
||||
<command id="downloadsCmd_retry"
|
||||
oncommand="goDoCommand('downloadsCmd_retry')"/>
|
||||
<command id="downloadsCmd_openReferrer"
|
||||
oncommand="goDoCommand('downloadsCmd_openReferrer')"/>
|
||||
<command id="downloadsCmd_clearDownloads"
|
||||
oncommand="goDoCommand('downloadsCmd_clearDownloads')"/>
|
||||
</commandset>
|
||||
|
||||
<menupopup id="downloadsContextMenu" class="download-state">
|
||||
<menuitem command="downloadsCmd_pauseResume"
|
||||
class="downloadPauseMenuItem"
|
||||
label="&cmd.pause.label;"
|
||||
accesskey="&cmd.pause.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_pauseResume"
|
||||
class="downloadResumeMenuItem"
|
||||
label="&cmd.resume.label;"
|
||||
accesskey="&cmd.resume.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_cancel"
|
||||
class="downloadCancelMenuItem"
|
||||
label="&cmd.cancel.label;"
|
||||
accesskey="&cmd.cancel.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_unblock"
|
||||
class="downloadUnblockMenuItem"
|
||||
label="&cmd.unblock2.label;"
|
||||
accesskey="&cmd.unblock2.accesskey;"/>
|
||||
<menuitem command="cmd_delete"
|
||||
class="downloadRemoveFromHistoryMenuItem"
|
||||
label="&cmd.removeFromHistory.label;"
|
||||
accesskey="&cmd.removeFromHistory.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_show"
|
||||
class="downloadShowMenuItem"
|
||||
#ifdef XP_MACOSX
|
||||
label="&cmd.showMac.label;"
|
||||
accesskey="&cmd.showMac.accesskey;"
|
||||
#else
|
||||
label="&cmd.show.label;"
|
||||
accesskey="&cmd.show.accesskey;"
|
||||
#endif
|
||||
/>
|
||||
|
||||
<menuseparator class="downloadCommandsSeparator"/>
|
||||
|
||||
<menuitem command="downloadsCmd_openReferrer"
|
||||
label="&cmd.goToDownloadPage.label;"
|
||||
accesskey="&cmd.goToDownloadPage.accesskey;"/>
|
||||
<menuitem command="cmd_copy"
|
||||
label="&cmd.copyDownloadLink.label;"
|
||||
accesskey="&cmd.copyDownloadLink.accesskey;"/>
|
||||
|
||||
<menuseparator/>
|
||||
|
||||
<menuitem command="downloadsCmd_clearDownloads"
|
||||
label="&cmd.clearDownloads.label;"
|
||||
accesskey="&cmd.clearDownloads.accesskey;"/>
|
||||
</menupopup>
|
||||
</overlay>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/* 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/. */
|
||||
|
||||
#downloadsListEmptyDescription {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#downloadsRichListBox:empty + #downloadsListEmptyDescription {
|
||||
display: -moz-box;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/* 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/. */
|
||||
|
||||
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
var ContentAreaDownloadsView = {
|
||||
init() {
|
||||
let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox"));
|
||||
// Do not display the Places downloads in private windows
|
||||
if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
|
||||
view.place = "place:transition=7&sort=4";
|
||||
}
|
||||
// Set focus to Downloads list once it is created
|
||||
document.getElementById("downloadsRichListBox").focus();
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
# 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/.
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/downloads/contentAreaDownloadsView.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/downloads/contentAreaDownloadsView.css"?>
|
||||
|
||||
<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?>
|
||||
|
||||
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
|
||||
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
|
||||
%downloadsDTD;
|
||||
]>
|
||||
|
||||
<window id="contentAreaDownloadsView"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="&downloads.title;"
|
||||
onload="ContentAreaDownloadsView.init();">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/downloads/contentAreaDownloadsView.js"/>
|
||||
|
||||
<commandset id="editMenuCommands"/>
|
||||
|
||||
<keyset id="editMenuKeys">
|
||||
#ifdef XP_MACOSX
|
||||
<key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
|
||||
#endif
|
||||
</keyset>
|
||||
|
||||
<stack flex="1">
|
||||
<richlistbox id="downloadsRichListBox"/>
|
||||
<description id="downloadsListEmptyDescription"
|
||||
value="&downloadsListEmpty.label;"
|
||||
mousethrough="always"/>
|
||||
</stack>
|
||||
<commandset id="downloadCommands"/>
|
||||
<menupopup id="downloadsContextMenu"/>
|
||||
</window>
|
||||
99
browser/components/downloads/content/download.xml
Normal file
99
browser/components/downloads/content/download.xml
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- -->
|
||||
<!-- vim: set ts=2 et sw=2 tw=80: -->
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE bindings SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
|
||||
|
||||
<bindings id="downloadBindings"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="download"
|
||||
extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
|
||||
<content orient="horizontal"
|
||||
onclick="DownloadsView.onDownloadClick(event);">
|
||||
<xul:hbox class="downloadMainArea"
|
||||
flex="1"
|
||||
align="center">
|
||||
<xul:stack>
|
||||
<xul:image class="downloadTypeIcon"
|
||||
validate="always"
|
||||
xbl:inherits="src=image"/>
|
||||
<xul:image class="downloadBlockedBadge" />
|
||||
</xul:stack>
|
||||
<xul:vbox pack="center"
|
||||
flex="1"
|
||||
class="downloadContainer"
|
||||
style="width: &downloadDetails.width;">
|
||||
<!-- We're letting localizers put a min-width in here primarily
|
||||
because of the downloads summary at the bottom of the list of
|
||||
download items. An element in the summary has the same min-width
|
||||
on a description, and we don't want the panel to change size if the
|
||||
summary isn't being displayed, so we ensure that items share the
|
||||
same minimum width.
|
||||
-->
|
||||
<xul:description class="downloadTarget"
|
||||
crop="center"
|
||||
style="min-width: &downloadsSummary.minWidth2;"
|
||||
xbl:inherits="value=displayName,tooltiptext=displayName"/>
|
||||
<xul:progressmeter anonid="progressmeter"
|
||||
class="downloadProgress"
|
||||
min="0"
|
||||
max="100"
|
||||
xbl:inherits="mode=progressmode,value=progress,paused=progresspaused"/>
|
||||
<xul:description class="downloadDetails"
|
||||
crop="end"
|
||||
xbl:inherits="value=status,tooltiptext=statusTip"/>
|
||||
</xul:vbox>
|
||||
</xul:hbox>
|
||||
<xul:toolbarseparator />
|
||||
<xul:stack class="downloadButtonArea">
|
||||
<xul:button class="downloadButton downloadCancel downloadIconCancel"
|
||||
tooltiptext="&cmd.cancel.label;"
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
|
||||
<xul:button class="downloadButton downloadRetry downloadIconRetry"
|
||||
tooltiptext="&cmd.retry.label;"
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
|
||||
<xul:button class="downloadButton downloadShow downloadIconShow"
|
||||
#ifdef XP_MACOSX
|
||||
tooltiptext="&cmd.showMac.label;"
|
||||
#else
|
||||
tooltiptext="&cmd.show.label;"
|
||||
#endif
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
|
||||
<xul:button class="downloadButton downloadConfirmBlock downloadIconCancel"
|
||||
tooltiptext="&cmd.removeFile.label;"
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_confirmBlock');"/>
|
||||
<xul:button class="downloadButton downloadChooseUnblock downloadIconShow"
|
||||
tooltiptext="&cmd.chooseUnblock.label;"
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseUnblock');"/>
|
||||
<xul:button class="downloadButton downloadChooseOpen downloadIconShow"
|
||||
tooltiptext="&cmd.chooseOpen.label;"
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseOpen');"/>
|
||||
<xul:button class="downloadButton downloadShowBlockedInfo"
|
||||
tooltiptext="&cmd.chooseUnblock.label;"
|
||||
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_showBlockedInfo');"/>
|
||||
</xul:stack>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
<binding id="download-toolbarbutton"
|
||||
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged">
|
||||
<content>
|
||||
<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>
|
||||
267
browser/components/downloads/content/downloads.css
Normal file
267
browser/components/downloads/content/downloads.css
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
/* 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/. */
|
||||
|
||||
/*** Downloads Panel ***/
|
||||
|
||||
richlistitem[type="download"] {
|
||||
-moz-binding: url('chrome://browser/content/downloads/download.xml#download');
|
||||
}
|
||||
|
||||
richlistitem[type="download"]:not([selected]) button {
|
||||
/* Only focus buttons in the selected item. */
|
||||
-moz-user-focus: none;
|
||||
}
|
||||
|
||||
.downloadsHideDropmarker > #downloadsFooterButtonsSplitter,
|
||||
.downloadsHideDropmarker > #downloadsFooterDropmarker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
richlistitem[type="download"].download-state[state="1"]:not([exists]) > .downloadButtonArea,
|
||||
richlistitem[type="download"].download-state[state="1"]:not([exists]) > toolbarseparator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
|
||||
#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
|
||||
#downloadsFooter:not([showingsummary]) #downloadsSummary {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#downloadsFooter[showingdropdown] > stack > #downloadsSummary,
|
||||
#downloadsFooter[showingsummary] > stack:hover > #downloadsSummary,
|
||||
#downloadsFooter[showingsummary]:not([showingdropdown]) > stack:not(:hover) > #downloadsFooterButtons {
|
||||
/* If we used "visibility: hidden;" then the mouseenter event of
|
||||
#downloadsHistory wouldn't be triggered immediately, and the hover styling
|
||||
of the button would not apply until the mouse is moved again.
|
||||
|
||||
"-moz-user-focus: ignore;" prevents the elements with "opacity: 0;" from
|
||||
being focused with the keyboard. */
|
||||
opacity: 0;
|
||||
-moz-user-focus: ignore;
|
||||
}
|
||||
|
||||
/*** Downloads View ***/
|
||||
|
||||
/**
|
||||
* The downloads richlistbox may list thousands of items, and it turns out
|
||||
* XBL binding attachment, and even more so detachment, is a performance hog.
|
||||
* This hack makes sure we don't apply any binding to inactive items (inactive
|
||||
* items are history downloads that haven't been in the visible area).
|
||||
* We can do this because the richlistbox implementation does not interact
|
||||
* much with the richlistitem binding. However, this may turn out to have
|
||||
* some side effects (see bug 828111 for the details).
|
||||
*
|
||||
* We might be able to do away with this workaround once bug 653881 is fixed.
|
||||
*/
|
||||
richlistitem.download {
|
||||
-moz-binding: none;
|
||||
}
|
||||
|
||||
richlistitem.download[active] {
|
||||
-moz-binding: url("chrome://browser/content/downloads/download.xml#download");
|
||||
}
|
||||
|
||||
richlistitem.download button {
|
||||
/* These buttons should never get focus, as that would "disable"
|
||||
the downloads view controller (it's only used when the richlistbox
|
||||
is focused). */
|
||||
-moz-user-focus: none;
|
||||
}
|
||||
|
||||
/*** Visibility of controls inside download items ***/
|
||||
.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */
|
||||
[state="8"], /* Blocked (dirty) */
|
||||
[state="9"]) /* Blocked (policy) */)
|
||||
.downloadBlockedBadge,
|
||||
|
||||
.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
|
||||
[state="5"], /* Starting (queued) */
|
||||
[state="0"], /* Downloading */
|
||||
[state="4"], /* Paused */
|
||||
[state="7"]) /* Scanning */)
|
||||
.downloadProgress,
|
||||
|
||||
.download-state:not( [state="0"] /* Downloading */)
|
||||
.downloadPauseMenuItem,
|
||||
|
||||
.download-state:not( [state="4"] /* Paused */)
|
||||
.downloadResumeMenuItem,
|
||||
|
||||
/* Blocked (dirty) downloads that have not been confirmed and
|
||||
have temporary data. */
|
||||
.download-state:not( [state="8"] /* Blocked (dirty) */)
|
||||
.downloadUnblockMenuItem,
|
||||
.download-state[state="8"]:not(.temporary-block)
|
||||
.downloadUnblockMenuItem,
|
||||
|
||||
.download-state:not(:-moz-any([state="2"], /* Failed */
|
||||
[state="4"]) /* Paused */)
|
||||
.downloadCancelMenuItem,
|
||||
|
||||
.download-state:not(:-moz-any([state="1"], /* Finished */
|
||||
[state="2"], /* Failed */
|
||||
[state="3"], /* Canceled */
|
||||
[state="6"], /* Blocked (parental) */
|
||||
[state="8"], /* Blocked (dirty) */
|
||||
[state="9"]) /* Blocked (policy) */)
|
||||
.downloadRemoveFromHistoryMenuItem,
|
||||
|
||||
.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
|
||||
[state="0"], /* Downloading */
|
||||
[state="1"], /* Finished */
|
||||
[state="4"], /* Paused */
|
||||
[state="5"]) /* Starting (queued) */)
|
||||
.downloadShowMenuItem,
|
||||
|
||||
.download-state[state="7"] .downloadCommandsSeparator
|
||||
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*** Visibility of download buttons ***/
|
||||
|
||||
.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
|
||||
[state="5"], /* Starting (queued) */
|
||||
[state="0"], /* Downloading */
|
||||
[state="4"]) /* Paused */)
|
||||
.downloadCancel,
|
||||
|
||||
/* Blocked (dirty) downloads that have not been confirmed and
|
||||
have temporary data, for the Malware case. */
|
||||
.download-state:not( [state="8"] /* Blocked (dirty) */)
|
||||
.downloadConfirmBlock,
|
||||
.download-state[state="8"]:not(.temporary-block)
|
||||
.downloadConfirmBlock,
|
||||
.download-state[state="8"].temporary-block:not([verdict="Malware"])
|
||||
.downloadConfirmBlock,
|
||||
|
||||
/* Blocked (dirty) downloads that have not been confirmed and
|
||||
have temporary data, for the Potentially Unwanted case. */
|
||||
.download-state:not( [state="8"] /* Blocked (dirty) */)
|
||||
.downloadChooseUnblock,
|
||||
.download-state[state="8"]:not(.temporary-block)
|
||||
.downloadChooseUnblock,
|
||||
.download-state[state="8"].temporary-block:not([verdict="PotentiallyUnwanted"])
|
||||
.downloadChooseUnblock,
|
||||
|
||||
/* Blocked (dirty) downloads that have not been confirmed and
|
||||
have temporary data, for the Uncommon case. */
|
||||
.download-state:not( [state="8"] /* Blocked (dirty) */)
|
||||
.downloadChooseOpen,
|
||||
.download-state[state="8"]:not(.temporary-block)
|
||||
.downloadChooseOpen,
|
||||
.download-state[state="8"].temporary-block:not([verdict="Uncommon"])
|
||||
.downloadChooseOpen,
|
||||
|
||||
.download-state:not(:-moz-any([state="2"], /* Failed */
|
||||
[state="3"]) /* Canceled */)
|
||||
.downloadRetry,
|
||||
|
||||
.download-state:not( [state="1"] /* Finished */)
|
||||
.downloadShow,
|
||||
|
||||
.download-state:-moz-any( [state="6"], /* Blocked (parental) */
|
||||
[state="7"], /* Scanning */
|
||||
[state="9"]) /* Blocked (policy) */
|
||||
> toolbarseparator,
|
||||
|
||||
/* The "show blocked info" button is shown only in the downloads panel. */
|
||||
.downloadShowBlockedInfo
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*** Downloads panel ***/
|
||||
|
||||
#downloadsPanel[hasdownloads] #emptyDownloads,
|
||||
#downloadsPanel:not([hasdownloads]) #downloadsListBox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*** Downloads panel multiview (main view and blocked-downloads subview) ***/
|
||||
|
||||
/* Hide all the usual buttons. */
|
||||
#downloadsPanel-mainView .download-state[state="8"] .downloadCancel,
|
||||
#downloadsPanel-mainView .download-state[state="8"] .downloadConfirmBlock,
|
||||
#downloadsPanel-mainView .download-state[state="8"] .downloadChooseUnblock,
|
||||
#downloadsPanel-mainView .download-state[state="8"] .downloadChooseOpen,
|
||||
#downloadsPanel-mainView .download-state[state="8"] .downloadRetry,
|
||||
#downloadsPanel-mainView .download-state[state="8"] .downloadShow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Make the panel wide enough to show the download list items without improperly
|
||||
truncating them. */
|
||||
#downloadsPanel-multiView > .panel-viewcontainer,
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack,
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-mainview {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
/* Show the "show blocked info" button. */
|
||||
#downloadsPanel-mainView .download-state[state="8"] .downloadShowBlockedInfo {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/** When the main view is showing... **/
|
||||
|
||||
/* The subview should be off to the right and not visible at all. */
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews {
|
||||
transform: translateX(101%);
|
||||
transition: transform var(--panelui-subview-transition-duration);
|
||||
}
|
||||
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews:-moz-locale-dir(rtl) {
|
||||
transform: translateX(-101%);
|
||||
}
|
||||
|
||||
/** When the subview is showing... **/
|
||||
|
||||
/* Hide the buttons of all downloads except the one that triggered the
|
||||
subview. */
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state:not([showingsubview]) .downloadButton {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* For the download that triggered the subview, move its button farther to the
|
||||
right by removing padding so that a minimum amount of the main view's right
|
||||
edge needs to be shown. */
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* The main view should slide to the left and its right edge should remain
|
||||
visible. */
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=subview] > .panel-mainview {
|
||||
transform: translateX(calc(-100% + 38px));
|
||||
transition: transform var(--panelui-subview-transition-duration);
|
||||
}
|
||||
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=subview] > .panel-mainview:-moz-locale-dir(rtl) {
|
||||
transform: translateX(calc(100% - 38px));
|
||||
}
|
||||
|
||||
/* The subview should leave the right edge of the main view uncovered. */
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
|
||||
/* Use a margin instead of a transform like above so that the subview's width
|
||||
isn't wider than the panel. */
|
||||
-moz-margin-start: 38px !important;
|
||||
}
|
||||
|
||||
/* Prevent keyboard interaction in the main view by preventing all elements in
|
||||
the main view from being focused... */
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview #downloadsListBox,
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview richlistitem,
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview .downloadButton,
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview .downloadsPanelFooterButton,
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview #downloadsSummary {
|
||||
-moz-user-focus: ignore;
|
||||
}
|
||||
/* ... except for the downloadShowBlockedInfo button in the blocked download.
|
||||
Selecting it with the keyboard should show the main view again. */
|
||||
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadShowBlockedInfo {
|
||||
-moz-user-focus: normal;
|
||||
}
|
||||
1732
browser/components/downloads/content/downloads.js
Normal file
1732
browser/components/downloads/content/downloads.js
Normal file
File diff suppressed because it is too large
Load diff
210
browser/components/downloads/content/downloadsOverlay.xul
Normal file
210
browser/components/downloads/content/downloadsOverlay.xul
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
<?xml version="1.0"?>
|
||||
# -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
# vim: set ts=2 et sw=2 tw=80:
|
||||
# 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/.
|
||||
|
||||
<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?>
|
||||
|
||||
<!DOCTYPE overlay SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
|
||||
|
||||
<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
id="downloadsOverlay">
|
||||
|
||||
<commandset>
|
||||
<command id="downloadsCmd_doDefault"
|
||||
oncommand="goDoCommand('downloadsCmd_doDefault')"/>
|
||||
<command id="downloadsCmd_pauseResume"
|
||||
oncommand="goDoCommand('downloadsCmd_pauseResume')"/>
|
||||
<command id="downloadsCmd_cancel"
|
||||
oncommand="goDoCommand('downloadsCmd_cancel')"/>
|
||||
<command id="downloadsCmd_unblock"
|
||||
oncommand="goDoCommand('downloadsCmd_unblock')"/>
|
||||
<command id="downloadsCmd_chooseUnblock"
|
||||
oncommand="goDoCommand('downloadsCmd_chooseUnblock')"/>
|
||||
<command id="downloadsCmd_unblockAndOpen"
|
||||
oncommand="goDoCommand('downloadsCmd_unblockAndOpen')"/>
|
||||
<command id="downloadsCmd_confirmBlock"
|
||||
oncommand="goDoCommand('downloadsCmd_confirmBlock')"/>
|
||||
<command id="downloadsCmd_open"
|
||||
oncommand="goDoCommand('downloadsCmd_open')"/>
|
||||
<command id="downloadsCmd_show"
|
||||
oncommand="goDoCommand('downloadsCmd_show')"/>
|
||||
<command id="downloadsCmd_retry"
|
||||
oncommand="goDoCommand('downloadsCmd_retry')"/>
|
||||
<command id="downloadsCmd_openReferrer"
|
||||
oncommand="goDoCommand('downloadsCmd_openReferrer')"/>
|
||||
<command id="downloadsCmd_copyLocation"
|
||||
oncommand="goDoCommand('downloadsCmd_copyLocation')"/>
|
||||
<command id="downloadsCmd_clearList"
|
||||
oncommand="goDoCommand('downloadsCmd_clearList')"/>
|
||||
</commandset>
|
||||
|
||||
<popupset id="mainPopupSet">
|
||||
<!-- The panel has level="top" to ensure that it is never hidden by the
|
||||
taskbar on Windows. See bug 672365. For accessibility to screen
|
||||
readers, we use a label on the panel instead of the anchor because the
|
||||
panel can also be displayed without an anchor. -->
|
||||
<panel id="downloadsPanel"
|
||||
aria-label="&downloads.title;"
|
||||
role="group"
|
||||
type="arrow"
|
||||
orient="vertical"
|
||||
level="top"
|
||||
onpopupshown="DownloadsPanel.onPopupShown(event);"
|
||||
onpopuphidden="DownloadsPanel.onPopupHidden(event);">
|
||||
<!-- The following popup menu should be a child of the panel element,
|
||||
otherwise flickering may occur when the cursor is moved over the area
|
||||
of a disabled menu item that overlaps the panel. See bug 492960. -->
|
||||
<menupopup id="downloadsContextMenu"
|
||||
onpopupshown="DownloadsView.onContextPopupShown(event);"
|
||||
onpopuphidden="DownloadsView.onContextPopupHidden(event);"
|
||||
class="download-state">
|
||||
<menuitem command="downloadsCmd_pauseResume"
|
||||
class="downloadPauseMenuItem"
|
||||
label="&cmd.pause.label;"
|
||||
accesskey="&cmd.pause.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_pauseResume"
|
||||
class="downloadResumeMenuItem"
|
||||
label="&cmd.resume.label;"
|
||||
accesskey="&cmd.resume.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_cancel"
|
||||
class="downloadCancelMenuItem"
|
||||
label="&cmd.cancel.label;"
|
||||
accesskey="&cmd.cancel.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_unblock"
|
||||
class="downloadUnblockMenuItem"
|
||||
label="&cmd.unblock2.label;"
|
||||
accesskey="&cmd.unblock2.accesskey;"/>
|
||||
<menuitem command="cmd_delete"
|
||||
class="downloadRemoveFromHistoryMenuItem"
|
||||
label="&cmd.removeFromHistory.label;"
|
||||
accesskey="&cmd.removeFromHistory.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_show"
|
||||
class="downloadShowMenuItem"
|
||||
#ifdef XP_MACOSX
|
||||
label="&cmd.showMac.label;"
|
||||
accesskey="&cmd.showMac.accesskey;"
|
||||
#else
|
||||
label="&cmd.show.label;"
|
||||
accesskey="&cmd.show.accesskey;"
|
||||
#endif
|
||||
/>
|
||||
|
||||
<menuseparator class="downloadCommandsSeparator"/>
|
||||
|
||||
<menuitem command="downloadsCmd_openReferrer"
|
||||
label="&cmd.goToDownloadPage.label;"
|
||||
accesskey="&cmd.goToDownloadPage.accesskey;"/>
|
||||
<menuitem command="downloadsCmd_copyLocation"
|
||||
label="&cmd.copyDownloadLink.label;"
|
||||
accesskey="&cmd.copyDownloadLink.accesskey;"/>
|
||||
|
||||
<menuseparator/>
|
||||
|
||||
<menuitem command="downloadsCmd_clearList"
|
||||
label="&cmd.clearList2.label;"
|
||||
accesskey="&cmd.clearList2.accesskey;"/>
|
||||
</menupopup>
|
||||
|
||||
<panelmultiview id="downloadsPanel-multiView"
|
||||
mainViewId="downloadsPanel-mainView"
|
||||
align="stretch">
|
||||
|
||||
<panelview id="downloadsPanel-mainView"
|
||||
flex="1"
|
||||
align="stretch">
|
||||
<richlistbox id="downloadsListBox"
|
||||
context="downloadsContextMenu"
|
||||
onmouseover="DownloadsView.onDownloadMouseOver(event);"
|
||||
onmouseout="DownloadsView.onDownloadMouseOut(event);"
|
||||
oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
|
||||
ondragstart="DownloadsView.onDownloadDragStart(event);"/>
|
||||
<description id="emptyDownloads"
|
||||
mousethrough="always">
|
||||
&downloadsPanelEmpty.label;
|
||||
</description>
|
||||
<spacer flex="1"/>
|
||||
<vbox id="downloadsFooter"
|
||||
class="downloadsPanelFooter">
|
||||
<stack>
|
||||
<hbox id="downloadsSummary"
|
||||
align="center"
|
||||
orient="horizontal"
|
||||
onkeydown="DownloadsSummary.onKeyDown(event);"
|
||||
onclick="DownloadsSummary.onClick(event);">
|
||||
<image class="downloadTypeIcon" />
|
||||
<vbox pack="center"
|
||||
class="downloadContainer"
|
||||
style="width: &downloadDetails.width;">
|
||||
<description id="downloadsSummaryDescription"
|
||||
style="min-width: &downloadsSummary.minWidth2;"/>
|
||||
<progressmeter id="downloadsSummaryProgress"
|
||||
class="downloadProgress"
|
||||
min="0"
|
||||
max="100"
|
||||
mode="normal" />
|
||||
<description id="downloadsSummaryDetails"
|
||||
crop="end"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<hbox id="downloadsFooterButtons">
|
||||
<button id="downloadsHistory"
|
||||
class="downloadsPanelFooterButton"
|
||||
label="&downloadsHistory.label;"
|
||||
accesskey="&downloadsHistory.accesskey;"
|
||||
flex="1"
|
||||
oncommand="DownloadsPanel.showDownloadsHistory();"/>
|
||||
<toolbarseparator id="downloadsFooterButtonsSplitter"
|
||||
class="downloadsDropmarkerSplitter"/>
|
||||
<button id="downloadsFooterDropmarker"
|
||||
class="downloadsPanelFooterButton downloadsDropmarker"
|
||||
type="menu">
|
||||
<menupopup id="downloadSubPanel"
|
||||
onpopupshowing="DownloadsPanel.onFooterPopupShowing(event);"
|
||||
onpopuphidden="DownloadsPanel.onFooterPopupHidden(event);"
|
||||
position="after_end">
|
||||
<menuitem id="downloadsDropdownItemClearList"
|
||||
command="downloadsCmd_clearList"
|
||||
label="&cmd.clearList2.label;"/>
|
||||
<menuitem id="downloadsDropdownItemOpenDownloadsFolder"
|
||||
oncommand="DownloadsPanel.openDownloadsFolder();"
|
||||
label="&openDownloadsFolder.label;"/>
|
||||
</menupopup>
|
||||
</button>
|
||||
</hbox>
|
||||
</stack>
|
||||
</vbox>
|
||||
</panelview>
|
||||
|
||||
<panelview id="downloadsPanel-blockedSubview"
|
||||
orient="vertical"
|
||||
flex="1">
|
||||
<description id="downloadsPanel-blockedSubview-title"/>
|
||||
<description id="downloadsPanel-blockedSubview-details1"/>
|
||||
<description id="downloadsPanel-blockedSubview-details2"/>
|
||||
<spacer flex="1"/>
|
||||
<hbox id="downloadsPanel-blockedSubview-buttons"
|
||||
class="downloadsPanelFooter"
|
||||
align="stretch">
|
||||
<button id="downloadsPanel-blockedSubview-openButton"
|
||||
class="downloadsPanelFooterButton"
|
||||
command="downloadsCmd_unblockAndOpen"
|
||||
flex="1"/>
|
||||
<toolbarseparator/>
|
||||
<button id="downloadsPanel-blockedSubview-deleteButton"
|
||||
class="downloadsPanelFooterButton"
|
||||
oncommand="DownloadsBlockedSubview.confirmBlock();"
|
||||
default="true"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
</panelview>
|
||||
|
||||
</panelmultiview>
|
||||
|
||||
</panel>
|
||||
</popupset>
|
||||
</overlay>
|
||||
606
browser/components/downloads/content/indicator.js
Normal file
606
browser/components/downloads/content/indicator.js
Normal file
|
|
@ -0,0 +1,606 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Handles the indicator that displays the progress of ongoing downloads, which
|
||||
* is also used as the anchor for the downloads panel.
|
||||
*
|
||||
* This module includes the following constructors and global objects:
|
||||
*
|
||||
* DownloadsButton
|
||||
* Main entry point for the downloads indicator. Depending on how the toolbars
|
||||
* have been customized, this object determines if we should show a fully
|
||||
* functional indicator, a placeholder used during customization and in the
|
||||
* customization palette, or a neutral view as a temporary anchor for the
|
||||
* downloads panel.
|
||||
*
|
||||
* DownloadsIndicatorView
|
||||
* Builds and updates the actual downloads status widget, responding to changes
|
||||
* in the global status data, or provides a neutral view if the indicator is
|
||||
* removed from the toolbars and only used as a temporary anchor. In addition,
|
||||
* handles the user interaction events raised by the widget.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// DownloadsButton
|
||||
|
||||
/**
|
||||
* Main entry point for the downloads indicator. Depending on how the toolbars
|
||||
* have been customized, this object determines if we should show a fully
|
||||
* functional indicator, a placeholder used during customization and in the
|
||||
* customization palette, or a neutral view as a temporary anchor for the
|
||||
* downloads panel.
|
||||
*/
|
||||
const DownloadsButton = {
|
||||
/**
|
||||
* Location of the indicator overlay.
|
||||
*/
|
||||
get kIndicatorOverlay() {
|
||||
return "chrome://browser/content/downloads/indicatorOverlay.xul";
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a reference to the downloads button position placeholder, or null
|
||||
* if not available because it has been removed from the toolbars.
|
||||
*/
|
||||
get _placeholder() {
|
||||
return document.getElementById("downloads-button");
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is called asynchronously just after window initialization.
|
||||
*
|
||||
* NOTE: This function should limit the input/output it performs to improve
|
||||
* startup time.
|
||||
*/
|
||||
initializeIndicator() {
|
||||
DownloadsIndicatorView.ensureInitialized();
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates whether toolbar customization is in progress.
|
||||
*/
|
||||
_customizing: false,
|
||||
|
||||
/**
|
||||
* This function is called when toolbar customization starts.
|
||||
*
|
||||
* During customization, we never show the actual download progress indication
|
||||
* or the event notifications, but we show a neutral placeholder. The neutral
|
||||
* placeholder is an ordinary button defined in the browser window that can be
|
||||
* moved freely between the toolbars and the customization palette.
|
||||
*/
|
||||
customizeStart() {
|
||||
// Prevent the indicator from being displayed as a temporary anchor
|
||||
// during customization, even if requested using the getAnchor method.
|
||||
this._customizing = true;
|
||||
this._anchorRequested = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is called when toolbar customization ends.
|
||||
*/
|
||||
customizeDone() {
|
||||
this._customizing = false;
|
||||
DownloadsIndicatorView.afterCustomize();
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines the position where the indicator should appear, and moves its
|
||||
* associated element to the new position.
|
||||
*
|
||||
* @return Anchor element, or null if the indicator is not visible.
|
||||
*/
|
||||
_getAnchorInternal() {
|
||||
let indicator = DownloadsIndicatorView.indicator;
|
||||
if (!indicator) {
|
||||
// Exit now if the indicator overlay isn't loaded yet, or if the button
|
||||
// is not in the document.
|
||||
return null;
|
||||
}
|
||||
|
||||
indicator.open = this._anchorRequested;
|
||||
|
||||
let widget = CustomizableUI.getWidget("downloads-button")
|
||||
.forWindow(window);
|
||||
// Determine if the indicator is located on an invisible toolbar.
|
||||
if (!isElementVisible(indicator.parentNode) && !widget.overflowed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return DownloadsIndicatorView.indicatorAnchor;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether the indicator is, or will soon be visible in the browser
|
||||
* window.
|
||||
*
|
||||
* @param aCallback
|
||||
* Called once the indicator overlay has loaded. Gets a boolean
|
||||
* argument representing the indicator visibility.
|
||||
*/
|
||||
checkIsVisible(aCallback) {
|
||||
DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay, () => {
|
||||
if (!this._placeholder) {
|
||||
aCallback(false);
|
||||
} else {
|
||||
let element = DownloadsIndicatorView.indicator || this._placeholder;
|
||||
aCallback(isElementVisible(element.parentNode));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates whether we should try and show the indicator temporarily as an
|
||||
* anchor for the panel, even if the indicator would be hidden by default.
|
||||
*/
|
||||
_anchorRequested: false,
|
||||
|
||||
/**
|
||||
* Ensures that there is an anchor available for the panel.
|
||||
*
|
||||
* @param aCallback
|
||||
* Called when the anchor is available, passing the element where the
|
||||
* panel should be anchored, or null if an anchor is not available (for
|
||||
* example because both the tab bar and the navigation bar are hidden).
|
||||
*/
|
||||
getAnchor(aCallback) {
|
||||
// Do not allow anchoring the panel to the element while customizing.
|
||||
if (this._customizing) {
|
||||
aCallback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay, () => {
|
||||
this._anchorRequested = true;
|
||||
aCallback(this._getAnchorInternal());
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Allows the temporary anchor to be hidden.
|
||||
*/
|
||||
releaseAnchor() {
|
||||
this._anchorRequested = false;
|
||||
this._getAnchorInternal();
|
||||
},
|
||||
|
||||
get _tabsToolbar() {
|
||||
delete this._tabsToolbar;
|
||||
return this._tabsToolbar = document.getElementById("TabsToolbar");
|
||||
},
|
||||
|
||||
get _navBar() {
|
||||
delete this._navBar;
|
||||
return this._navBar = document.getElementById("nav-bar");
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(this, "DownloadsButton", {
|
||||
value: DownloadsButton,
|
||||
enumerable: true,
|
||||
writable: false
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// DownloadsIndicatorView
|
||||
|
||||
/**
|
||||
* Builds and updates the actual downloads status widget, responding to changes
|
||||
* in the global status data, or provides a neutral view if the indicator is
|
||||
* removed from the toolbars and only used as a temporary anchor. In addition,
|
||||
* handles the user interaction events raised by the widget.
|
||||
*/
|
||||
const DownloadsIndicatorView = {
|
||||
/**
|
||||
* True when the view is connected with the underlying downloads data.
|
||||
*/
|
||||
_initialized: false,
|
||||
|
||||
/**
|
||||
* True when the user interface elements required to display the indicator
|
||||
* have finished loading in the browser window, and can be referenced.
|
||||
*/
|
||||
_operational: false,
|
||||
|
||||
/**
|
||||
* Prepares the downloads indicator to be displayed.
|
||||
*/
|
||||
ensureInitialized() {
|
||||
if (this._initialized) {
|
||||
return;
|
||||
}
|
||||
this._initialized = true;
|
||||
|
||||
window.addEventListener("unload", this.onWindowUnload, false);
|
||||
DownloadsCommon.getIndicatorData(window).addView(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Frees the internal resources related to the indicator.
|
||||
*/
|
||||
ensureTerminated() {
|
||||
if (!this._initialized) {
|
||||
return;
|
||||
}
|
||||
this._initialized = false;
|
||||
|
||||
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 = 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) {
|
||||
if (aCallback) {
|
||||
aCallback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have a _placeholder, there's no chance that the overlay
|
||||
// will load correctly: bail (and don't set _operational to true!)
|
||||
if (!DownloadsButton._placeholder) {
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadsOverlayLoader.ensureOverlayLoaded(
|
||||
DownloadsButton.kIndicatorOverlay,
|
||||
() => {
|
||||
this._operational = true;
|
||||
|
||||
// If the view is initialized, we need to update the elements now that
|
||||
// they are finally available in the document.
|
||||
if (this._initialized) {
|
||||
DownloadsCommon.getIndicatorData(window).refreshView(this);
|
||||
}
|
||||
|
||||
if (aCallback) {
|
||||
aCallback();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// Direct control functions
|
||||
|
||||
/**
|
||||
* Set while we are waiting for a notification to fade out.
|
||||
*/
|
||||
_notificationTimeout: null,
|
||||
|
||||
/**
|
||||
* Check if the panel containing aNode is open.
|
||||
* @param aNode
|
||||
* the node whose panel we're interested in.
|
||||
*/
|
||||
_isAncestorPanelOpen(aNode) {
|
||||
while (aNode && aNode.localName != "panel") {
|
||||
aNode = aNode.parentNode;
|
||||
}
|
||||
return aNode && aNode.state == "open";
|
||||
},
|
||||
|
||||
/**
|
||||
* If the status indicator is visible in its assigned position, shows for a
|
||||
* brief time a visual notification of a relevant event, like a new download.
|
||||
*
|
||||
* @param aType
|
||||
* Set to "start" for new downloads, "finish" for completed downloads.
|
||||
*/
|
||||
showEventNotification(aType) {
|
||||
if (!this._initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DownloadsCommon.animateNotifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to show visual notification if the panel is visible.
|
||||
if (DownloadsPanel.isPanelShowing) {
|
||||
return;
|
||||
}
|
||||
|
||||
let anchor = DownloadsButton._placeholder;
|
||||
let widgetGroup = CustomizableUI.getWidget("downloads-button");
|
||||
let widget = widgetGroup.forWindow(window);
|
||||
if (widget.overflowed || widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
|
||||
if (anchor && this._isAncestorPanelOpen(anchor)) {
|
||||
// If the containing panel is open, don't do anything, because the
|
||||
// notification would appear under the open panel. See
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=984023
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, try to use the anchor of the panel:
|
||||
anchor = widget.anchor;
|
||||
}
|
||||
if (!anchor || !isElementVisible(anchor.parentNode)) {
|
||||
// Our container isn't visible, so can't show the animation:
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._notificationTimeout) {
|
||||
clearTimeout(this._notificationTimeout);
|
||||
}
|
||||
|
||||
// The notification element is positioned to show in the same location as
|
||||
// the downloads button. It's not in the downloads button itself in order to
|
||||
// be able to anchor the notification elsewhere if required, and to ensure
|
||||
// the notification isn't clipped by overflow properties of the anchor's
|
||||
// container.
|
||||
let notifier = this.notifier;
|
||||
if (notifier.style.transform == '') {
|
||||
let anchorRect = anchor.getBoundingClientRect();
|
||||
let notifierRect = notifier.getBoundingClientRect();
|
||||
let topDiff = anchorRect.top - notifierRect.top;
|
||||
let leftDiff = anchorRect.left - notifierRect.left;
|
||||
let heightDiff = anchorRect.height - notifierRect.height;
|
||||
let widthDiff = anchorRect.width - notifierRect.width;
|
||||
let translateX = (leftDiff + .5 * widthDiff) + "px";
|
||||
let translateY = (topDiff + .5 * heightDiff) + "px";
|
||||
notifier.style.transform = "translate(" + translateX + ", " + translateY + ")";
|
||||
}
|
||||
notifier.setAttribute("notification", aType);
|
||||
this._notificationTimeout = setTimeout(() => {
|
||||
notifier.removeAttribute("notification");
|
||||
notifier.style.transform = '';
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// Callback functions from DownloadsIndicatorData
|
||||
|
||||
/**
|
||||
* Indicates whether the indicator should be shown because there are some
|
||||
* downloads to be displayed.
|
||||
*/
|
||||
set hasDownloads(aValue) {
|
||||
if (this._hasDownloads != aValue || (!this._operational && aValue)) {
|
||||
this._hasDownloads = aValue;
|
||||
|
||||
// If there is at least one download, ensure that the view elements are
|
||||
if (aValue) {
|
||||
this._ensureOperational();
|
||||
}
|
||||
}
|
||||
return aValue;
|
||||
},
|
||||
get hasDownloads() {
|
||||
return this._hasDownloads;
|
||||
},
|
||||
_hasDownloads: false,
|
||||
|
||||
/**
|
||||
* Status text displayed in the indicator. If this is set to an empty value,
|
||||
* then the small downloads icon is displayed instead of the text.
|
||||
*/
|
||||
set counter(aValue) {
|
||||
if (!this._operational) {
|
||||
return this._counter;
|
||||
}
|
||||
|
||||
if (this._counter !== aValue) {
|
||||
this._counter = aValue;
|
||||
if (this._counter)
|
||||
this.indicator.setAttribute("counter", "true");
|
||||
else
|
||||
this.indicator.removeAttribute("counter");
|
||||
// We have to set the attribute instead of using the property because the
|
||||
// XBL binding isn't applied if the element is invisible for any reason.
|
||||
this._indicatorCounter.setAttribute("value", aValue);
|
||||
}
|
||||
return aValue;
|
||||
},
|
||||
_counter: null,
|
||||
|
||||
/**
|
||||
* Progress indication to display, from 0 to 100, or -1 if unknown. The
|
||||
* progress bar is hidden if the current progress is unknown and no status
|
||||
* text is set in the "counter" property.
|
||||
*/
|
||||
set percentComplete(aValue) {
|
||||
if (!this._operational) {
|
||||
return this._percentComplete;
|
||||
}
|
||||
|
||||
if (this._percentComplete !== aValue) {
|
||||
this._percentComplete = aValue;
|
||||
if (this._percentComplete >= 0)
|
||||
this.indicator.setAttribute("progress", "true");
|
||||
else
|
||||
this.indicator.removeAttribute("progress");
|
||||
// We have to set the attribute instead of using the property because the
|
||||
// XBL binding isn't applied if the element is invisible for any reason.
|
||||
this._indicatorProgress.setAttribute("value", Math.max(aValue, 0));
|
||||
}
|
||||
return aValue;
|
||||
},
|
||||
_percentComplete: null,
|
||||
|
||||
/**
|
||||
* Indicates whether the progress won't advance because of a paused state.
|
||||
* Setting this property forces a paused progress bar to be displayed, even if
|
||||
* the current progress information is unavailable.
|
||||
*/
|
||||
set paused(aValue) {
|
||||
if (!this._operational) {
|
||||
return this._paused;
|
||||
}
|
||||
|
||||
if (this._paused != aValue) {
|
||||
this._paused = aValue;
|
||||
if (this._paused) {
|
||||
this.indicator.setAttribute("paused", "true")
|
||||
} else {
|
||||
this.indicator.removeAttribute("paused");
|
||||
}
|
||||
}
|
||||
return aValue;
|
||||
},
|
||||
_paused: false,
|
||||
|
||||
/**
|
||||
* Set when the indicator should draw user attention to itself.
|
||||
*/
|
||||
set attention(aValue) {
|
||||
if (!this._operational) {
|
||||
return this._attention;
|
||||
}
|
||||
|
||||
if (this._attention != aValue) {
|
||||
this._attention = aValue;
|
||||
|
||||
// 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.setAttribute("attention", aValue);
|
||||
if (inMenu) {
|
||||
let badgeClass = "download-" + aValue;
|
||||
gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, badgeClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
return aValue;
|
||||
},
|
||||
_attention: DownloadsCommon.ATTENTION_NONE,
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//// User interface event functions
|
||||
|
||||
onWindowUnload() {
|
||||
// This function is registered as an event listener, we can't use "this".
|
||||
DownloadsIndicatorView.ensureTerminated();
|
||||
},
|
||||
|
||||
onCommand(aEvent) {
|
||||
// If the downloads button is in the menu panel, open the Library
|
||||
let widgetGroup = CustomizableUI.getWidget("downloads-button");
|
||||
if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
|
||||
DownloadsPanel.showDownloadsHistory();
|
||||
} else {
|
||||
DownloadsPanel.showPanel();
|
||||
}
|
||||
|
||||
aEvent.stopPropagation();
|
||||
},
|
||||
|
||||
onDragOver(aEvent) {
|
||||
browserDragAndDrop.dragOver(aEvent);
|
||||
},
|
||||
|
||||
onDrop(aEvent) {
|
||||
let dt = aEvent.dataTransfer;
|
||||
// If dragged item is from our source, do not try to
|
||||
// redownload already downloaded file.
|
||||
if (dt.mozGetDataAt("application/x-moz-file", 0))
|
||||
return;
|
||||
|
||||
let links = browserDragAndDrop.dropLinks(aEvent);
|
||||
if (!links.length)
|
||||
return;
|
||||
let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
|
||||
let handled = false;
|
||||
for (let link of links) {
|
||||
if (link.url.startsWith("about:"))
|
||||
continue;
|
||||
saveURL(link.url, link.name, null, true, true, null, sourceDoc);
|
||||
handled = true;
|
||||
}
|
||||
if (handled) {
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
_indicator: null,
|
||||
__indicatorCounter: null,
|
||||
__indicatorProgress: null,
|
||||
|
||||
/**
|
||||
* Returns a reference to the main indicator element, or null if the element
|
||||
* is not present in the browser window yet.
|
||||
*/
|
||||
get indicator() {
|
||||
if (this._indicator) {
|
||||
return this._indicator;
|
||||
}
|
||||
|
||||
let indicator = document.getElementById("downloads-button");
|
||||
if (!indicator || indicator.getAttribute("indicator") != "true") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._indicator = indicator;
|
||||
},
|
||||
|
||||
get indicatorAnchor() {
|
||||
let widget = CustomizableUI.getWidget("downloads-button")
|
||||
.forWindow(window);
|
||||
if (widget.overflowed) {
|
||||
return widget.anchor;
|
||||
}
|
||||
return document.getElementById("downloads-indicator-anchor");
|
||||
},
|
||||
|
||||
get _indicatorCounter() {
|
||||
return this.__indicatorCounter ||
|
||||
(this.__indicatorCounter = document.getElementById("downloads-indicator-counter"));
|
||||
},
|
||||
|
||||
get _indicatorProgress() {
|
||||
return this.__indicatorProgress ||
|
||||
(this.__indicatorProgress = document.getElementById("downloads-indicator-progress"));
|
||||
},
|
||||
|
||||
get notifier() {
|
||||
return this._notifier ||
|
||||
(this._notifier = document.getElementById("downloads-notification-anchor"));
|
||||
},
|
||||
|
||||
_onCustomizedAway() {
|
||||
this._indicator = null;
|
||||
this.__indicatorCounter = null;
|
||||
this.__indicatorProgress = null;
|
||||
},
|
||||
|
||||
afterCustomize() {
|
||||
// If the cached indicator is not the one currently in the document,
|
||||
// invalidate our references
|
||||
if (this._indicator != document.getElementById("downloads-button")) {
|
||||
this._onCustomizedAway();
|
||||
this._operational = false;
|
||||
this.ensureTerminated();
|
||||
this.ensureInitialized();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Object.defineProperty(this, "DownloadsIndicatorView", {
|
||||
value: DownloadsIndicatorView,
|
||||
enumerable: true,
|
||||
writable: false
|
||||
});
|
||||
36
browser/components/downloads/content/indicatorOverlay.xul
Normal file
36
browser/components/downloads/content/indicatorOverlay.xul
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- -->
|
||||
<!-- vim: set ts=2 et sw=2 tw=80: -->
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<!DOCTYPE overlay [
|
||||
<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
|
||||
%browserDTD;
|
||||
]>
|
||||
|
||||
<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
id="indicatorOverlay">
|
||||
|
||||
<!-- 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"
|
||||
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>
|
||||
</toolbarbutton>
|
||||
</overlay>
|
||||
16
browser/components/downloads/jar.mn
Normal file
16
browser/components/downloads/jar.mn
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# 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/.
|
||||
|
||||
browser.jar:
|
||||
* content/browser/downloads/download.xml (content/download.xml)
|
||||
content/browser/downloads/downloads.css (content/downloads.css)
|
||||
content/browser/downloads/downloads.js (content/downloads.js)
|
||||
* content/browser/downloads/downloadsOverlay.xul (content/downloadsOverlay.xul)
|
||||
content/browser/downloads/indicator.js (content/indicator.js)
|
||||
content/browser/downloads/indicatorOverlay.xul (content/indicatorOverlay.xul)
|
||||
* content/browser/downloads/allDownloadsViewOverlay.xul (content/allDownloadsViewOverlay.xul)
|
||||
content/browser/downloads/allDownloadsViewOverlay.js (content/allDownloadsViewOverlay.js)
|
||||
* content/browser/downloads/contentAreaDownloadsView.xul (content/contentAreaDownloadsView.xul)
|
||||
content/browser/downloads/contentAreaDownloadsView.js (content/contentAreaDownloadsView.js)
|
||||
content/browser/downloads/contentAreaDownloadsView.css (content/contentAreaDownloadsView.css)
|
||||
22
browser/components/downloads/moz.build
Normal file
22
browser/components/downloads/moz.build
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
with Files('*'):
|
||||
BUG_COMPONENT = ('Firefox', 'Downloads Panel')
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'DownloadsCommon.jsm',
|
||||
'DownloadsTaskbar.jsm',
|
||||
'DownloadsViewUI.jsm',
|
||||
]
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Firefox', 'Downloads Panel')
|
||||
7
browser/components/downloads/test/browser/.eslintrc.js
Normal file
7
browser/components/downloads/test/browser/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/mochitest/browser.eslintrc.js"
|
||||
]
|
||||
};
|
||||
15
browser/components/downloads/test/browser/browser.ini
Normal file
15
browser/components/downloads/test/browser/browser.ini
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[DEFAULT]
|
||||
support-files = head.js
|
||||
|
||||
[browser_basic_functionality.js]
|
||||
[browser_first_download_panel.js]
|
||||
skip-if = os == "linux" # Bug 949434
|
||||
[browser_overflow_anchor.js]
|
||||
skip-if = os == "linux" # Bug 952422
|
||||
[browser_confirm_unblock_download.js]
|
||||
[browser_iframe_gone_mid_download.js]
|
||||
[browser_indicatorDrop.js]
|
||||
[browser_libraryDrop.js]
|
||||
[browser_downloads_panel_block.js]
|
||||
[browser_downloads_panel_footer.js]
|
||||
[browser_downloads_panel_height.js]
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
registerCleanupFunction(function*() {
|
||||
yield task_resetState();
|
||||
});
|
||||
|
||||
/**
|
||||
* Make sure the downloads panel can display items in the right order and
|
||||
* contains the expected data.
|
||||
*/
|
||||
add_task(function* test_basic_functionality() {
|
||||
// Display one of each download state.
|
||||
const DownloadData = [
|
||||
{ state: nsIDM.DOWNLOAD_NOTSTARTED },
|
||||
{ state: nsIDM.DOWNLOAD_PAUSED },
|
||||
{ state: nsIDM.DOWNLOAD_FINISHED },
|
||||
{ state: nsIDM.DOWNLOAD_FAILED },
|
||||
{ state: nsIDM.DOWNLOAD_CANCELED },
|
||||
];
|
||||
|
||||
// Wait for focus first
|
||||
yield promiseFocus();
|
||||
|
||||
// Ensure that state is reset in case previous tests didn't finish.
|
||||
yield task_resetState();
|
||||
|
||||
// For testing purposes, show all the download items at once.
|
||||
var originalCountLimit = DownloadsView.kItemCountLimit;
|
||||
DownloadsView.kItemCountLimit = DownloadData.length;
|
||||
registerCleanupFunction(function () {
|
||||
DownloadsView.kItemCountLimit = originalCountLimit;
|
||||
});
|
||||
|
||||
// Populate the downloads database with the data required by this test.
|
||||
yield task_addDownloads(DownloadData);
|
||||
|
||||
// Open the user interface and wait for data to be fully loaded.
|
||||
yield task_openPanel();
|
||||
|
||||
// Test item data and count. This also tests the ordering of the display.
|
||||
let richlistbox = document.getElementById("downloadsListBox");
|
||||
/* disabled for failing intermittently (bug 767828)
|
||||
is(richlistbox.children.length, DownloadData.length,
|
||||
"There is the correct number of richlistitems");
|
||||
*/
|
||||
let itemCount = richlistbox.children.length;
|
||||
for (let i = 0; i < itemCount; i++) {
|
||||
let element = richlistbox.children[itemCount - i - 1];
|
||||
let download = DownloadsView.itemForElement(element).download;
|
||||
is(DownloadsCommon.stateOfDownload(download), DownloadData[i].state,
|
||||
"Download states match up");
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests the dialog which allows the user to unblock a downloaded file.
|
||||
|
||||
registerCleanupFunction(() => {});
|
||||
|
||||
function* assertDialogResult({ args, buttonToClick, expectedResult }) {
|
||||
promiseAlertDialogOpen(buttonToClick);
|
||||
is(yield DownloadsCommon.confirmUnblockDownload(args), expectedResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the "unblock" dialog, for each of the possible verdicts.
|
||||
*/
|
||||
add_task(function* test_unblock_dialog_unblock() {
|
||||
for (let verdict of [Downloads.Error.BLOCK_VERDICT_MALWARE,
|
||||
Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
|
||||
Downloads.Error.BLOCK_VERDICT_UNCOMMON]) {
|
||||
let args = { verdict, window, dialogType: "unblock" };
|
||||
|
||||
// Test both buttons.
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "accept",
|
||||
expectedResult: "unblock",
|
||||
});
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "cancel",
|
||||
expectedResult: "cancel",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests the "chooseUnblock" dialog for potentially unwanted downloads.
|
||||
*/
|
||||
add_task(function* test_chooseUnblock_dialog() {
|
||||
let args = {
|
||||
verdict: Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
|
||||
window,
|
||||
dialogType: "chooseUnblock",
|
||||
};
|
||||
|
||||
// Test each of the three buttons.
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "accept",
|
||||
expectedResult: "unblock",
|
||||
});
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "cancel",
|
||||
expectedResult: "cancel",
|
||||
});
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "extra1",
|
||||
expectedResult: "confirmBlock",
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests the "chooseOpen" dialog for uncommon downloads.
|
||||
*/
|
||||
add_task(function* test_chooseOpen_dialog() {
|
||||
let args = {
|
||||
verdict: Downloads.Error.BLOCK_VERDICT_UNCOMMON,
|
||||
window,
|
||||
dialogType: "chooseOpen",
|
||||
};
|
||||
|
||||
// Test each of the three buttons.
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "accept",
|
||||
expectedResult: "open",
|
||||
});
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "cancel",
|
||||
expectedResult: "cancel",
|
||||
});
|
||||
yield assertDialogResult({
|
||||
args,
|
||||
buttonToClick: "extra1",
|
||||
expectedResult: "confirmBlock",
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
"use strict";
|
||||
|
||||
add_task(function* mainTest() {
|
||||
yield task_resetState();
|
||||
|
||||
let verdicts = [
|
||||
Downloads.Error.BLOCK_VERDICT_UNCOMMON,
|
||||
Downloads.Error.BLOCK_VERDICT_MALWARE,
|
||||
Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
|
||||
];
|
||||
yield task_addDownloads(verdicts.map(v => makeDownload(v)));
|
||||
|
||||
// Check that the richlistitem for each download is correct.
|
||||
for (let i = 0; i < verdicts.length; i++) {
|
||||
yield openPanel();
|
||||
|
||||
// The current item is always the first one in the listbox since each
|
||||
// iteration of this loop removes the item at the end.
|
||||
let item = DownloadsView.richListBox.firstChild;
|
||||
|
||||
// Open the panel and click the item to show the subview.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, item);
|
||||
yield promiseSubviewShown(true);
|
||||
|
||||
// Items are listed in newest-to-oldest order, so e.g. the first item's
|
||||
// verdict is the last element in the verdicts array.
|
||||
Assert.ok(DownloadsBlockedSubview.subview.getAttribute("verdict"),
|
||||
verdicts[verdicts.count - i - 1]);
|
||||
|
||||
// Click the sliver of the main view that's still showing on the left to go
|
||||
// back to it.
|
||||
EventUtils.synthesizeMouse(DownloadsPanel.panel, 10, 10, {}, window);
|
||||
yield promiseSubviewShown(false);
|
||||
|
||||
// Show the subview again.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, item);
|
||||
yield promiseSubviewShown(true);
|
||||
|
||||
// Click the Open button. The download should be unblocked and then opened,
|
||||
// i.e., unblockAndOpenDownload() should be called on the item. The panel
|
||||
// should also be closed as a result, so wait for that too.
|
||||
let unblockOpenPromise = promiseUnblockAndOpenDownloadCalled(item);
|
||||
let hidePromise = promisePanelHidden();
|
||||
EventUtils.synthesizeMouse(DownloadsBlockedSubview.elements.openButton,
|
||||
10, 10, {}, window);
|
||||
yield unblockOpenPromise;
|
||||
yield hidePromise;
|
||||
|
||||
window.focus();
|
||||
yield SimpleTest.promiseFocus(window);
|
||||
|
||||
// Reopen the panel and show the subview again.
|
||||
yield openPanel();
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, item);
|
||||
yield promiseSubviewShown(true);
|
||||
|
||||
// Click the Remove button. The panel should close and the item should be
|
||||
// removed from it.
|
||||
EventUtils.synthesizeMouse(DownloadsBlockedSubview.elements.deleteButton,
|
||||
10, 10, {}, window);
|
||||
yield promisePanelHidden();
|
||||
yield openPanel();
|
||||
|
||||
Assert.ok(!item.parentNode);
|
||||
DownloadsPanel.hidePanel();
|
||||
yield promisePanelHidden();
|
||||
}
|
||||
|
||||
yield task_resetState();
|
||||
});
|
||||
|
||||
function* openPanel() {
|
||||
// This function is insane but something intermittently causes the panel to be
|
||||
// closed as soon as it's opening on Linux ASAN. Maybe it would also happen
|
||||
// on other build machines if the test ran often enough. Not only is the
|
||||
// panel closed, it's closed while it's opening, leaving DownloadsPanel._state
|
||||
// such that when you try to open the panel again, it thinks it's already
|
||||
// open, but it's not. The result is that the test times out.
|
||||
//
|
||||
// What this does is call DownloadsPanel.showPanel over and over again until
|
||||
// the panel is really open. There are a few wrinkles:
|
||||
//
|
||||
// (1) When panel.state is "open", check four more times (for a total of five)
|
||||
// before returning to make the panel stays open.
|
||||
// (2) If the panel is not open, check the _state. It should be either
|
||||
// kStateUninitialized or kStateHidden. If it's not, then the panel is in the
|
||||
// process of opening -- or maybe it's stuck in that process -- so reset the
|
||||
// _state to kStateHidden.
|
||||
// (3) If the _state is not kStateUninitialized or kStateHidden, then it may
|
||||
// actually be properly opening and not stuck at all. To avoid always closing
|
||||
// the panel while it's properly opening, use an exponential backoff mechanism
|
||||
// for retries.
|
||||
//
|
||||
// If all that fails, then the test will time out, but it would have timed out
|
||||
// anyway.
|
||||
|
||||
yield promiseFocus();
|
||||
yield new Promise(resolve => {
|
||||
let verifyCount = 5;
|
||||
let backoff = 0;
|
||||
let iBackoff = 0;
|
||||
let interval = setInterval(() => {
|
||||
if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") {
|
||||
if (verifyCount > 0) {
|
||||
verifyCount--;
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
} else {
|
||||
if (iBackoff < backoff) {
|
||||
// Keep backing off before trying again.
|
||||
iBackoff++;
|
||||
} else {
|
||||
// Try (or retry) opening the panel.
|
||||
verifyCount = 5;
|
||||
backoff = Math.max(1, 2 * backoff);
|
||||
iBackoff = 0;
|
||||
if (DownloadsPanel._state != DownloadsPanel.kStateUninitialized) {
|
||||
DownloadsPanel._state = DownloadsPanel.kStateHidden;
|
||||
}
|
||||
DownloadsPanel.showPanel();
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
function promisePanelHidden() {
|
||||
return new Promise(resolve => {
|
||||
if (!DownloadsPanel.panel || DownloadsPanel.panel.state == "closed") {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
DownloadsPanel.panel.addEventListener("popuphidden", function onHidden() {
|
||||
DownloadsPanel.panel.removeEventListener("popuphidden", onHidden);
|
||||
setTimeout(resolve, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function makeDownload(verdict) {
|
||||
return {
|
||||
state: nsIDM.DOWNLOAD_DIRTY,
|
||||
hasBlockedData: true,
|
||||
errorObj: {
|
||||
result: Components.results.NS_ERROR_FAILURE,
|
||||
message: "Download blocked.",
|
||||
becauseBlocked: true,
|
||||
becauseBlockedByReputationCheck: true,
|
||||
reputationCheckVerdict: verdict,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function promiseSubviewShown(shown) {
|
||||
// More terribleness, but I'm tired of fighting intermittent timeouts on try.
|
||||
// Just poll for the subview and wait a second before resolving the promise.
|
||||
return new Promise(resolve => {
|
||||
let interval = setInterval(() => {
|
||||
if (shown == DownloadsBlockedSubview.view.showingSubView &&
|
||||
!DownloadsBlockedSubview.view._transitioning) {
|
||||
clearInterval(interval);
|
||||
setTimeout(resolve, 1000);
|
||||
return;
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function promiseUnblockAndOpenDownloadCalled(item) {
|
||||
return new Promise(resolve => {
|
||||
let realFn = item._shell.unblockAndOpenDownload;
|
||||
item._shell.unblockAndOpenDownload = () => {
|
||||
item._shell.unblockAndOpenDownload = realFn;
|
||||
resolve();
|
||||
// unblockAndOpenDownload returns a promise (that's resolved when the file
|
||||
// is opened).
|
||||
return Promise.resolve();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
"use strict";
|
||||
|
||||
function *task_openDownloadsSubPanel() {
|
||||
let downloadSubPanel = document.getElementById("downloadSubPanel");
|
||||
let popupShownPromise = BrowserTestUtils.waitForEvent(downloadSubPanel, "popupshown");
|
||||
|
||||
let downloadsDropmarker = document.getElementById("downloadsFooterDropmarker");
|
||||
EventUtils.synthesizeMouseAtCenter(downloadsDropmarker, {}, window);
|
||||
|
||||
yield popupShownPromise;
|
||||
}
|
||||
|
||||
add_task(function* test_openDownloadsFolder() {
|
||||
yield SpecialPowers.pushPrefEnv({"set": [["browser.download.showPanelDropmarker", true]]});
|
||||
yield task_openPanel();
|
||||
|
||||
yield task_openDownloadsSubPanel();
|
||||
|
||||
yield new Promise(resolve => {
|
||||
sinon.stub(DownloadsCommon, "showDirectory", file => {
|
||||
resolve(Downloads.getPreferredDownloadsDirectory().then(downloadsPath => {
|
||||
is(file.path, downloadsPath, "Check the download folder path.");
|
||||
}));
|
||||
});
|
||||
|
||||
let itemOpenDownloadsFolder =
|
||||
document.getElementById("downloadsDropdownItemOpenDownloadsFolder");
|
||||
EventUtils.synthesizeMouseAtCenter(itemOpenDownloadsFolder, {}, window);
|
||||
});
|
||||
|
||||
yield task_resetState();
|
||||
});
|
||||
|
||||
add_task(function* test_clearList() {
|
||||
const kTestCases = [{
|
||||
downloads: [
|
||||
{ state: nsIDM.DOWNLOAD_NOTSTARTED },
|
||||
{ state: nsIDM.DOWNLOAD_FINISHED },
|
||||
{ state: nsIDM.DOWNLOAD_FAILED },
|
||||
{ state: nsIDM.DOWNLOAD_CANCELED },
|
||||
],
|
||||
expectClearListShown: true,
|
||||
expectedItemNumber: 0,
|
||||
},{
|
||||
downloads: [
|
||||
{ state: nsIDM.DOWNLOAD_NOTSTARTED },
|
||||
{ state: nsIDM.DOWNLOAD_FINISHED },
|
||||
{ state: nsIDM.DOWNLOAD_FAILED },
|
||||
{ state: nsIDM.DOWNLOAD_PAUSED },
|
||||
{ state: nsIDM.DOWNLOAD_CANCELED },
|
||||
],
|
||||
expectClearListShown: true,
|
||||
expectedItemNumber: 1,
|
||||
},{
|
||||
downloads: [
|
||||
{ state: nsIDM.DOWNLOAD_PAUSED },
|
||||
],
|
||||
expectClearListShown: false,
|
||||
expectedItemNumber: 1,
|
||||
}];
|
||||
|
||||
for (let testCase of kTestCases) {
|
||||
yield verify_clearList(testCase);
|
||||
}
|
||||
});
|
||||
|
||||
function *verify_clearList(testCase) {
|
||||
let downloads = testCase.downloads;
|
||||
yield task_addDownloads(downloads);
|
||||
|
||||
yield task_openPanel();
|
||||
is(DownloadsView._downloads.length, downloads.length,
|
||||
"Expect the number of download items");
|
||||
|
||||
yield task_openDownloadsSubPanel();
|
||||
|
||||
let itemClearList = document.getElementById("downloadsDropdownItemClearList");
|
||||
let itemNumberPromise = BrowserTestUtils.waitForCondition(() => {
|
||||
return DownloadsView._downloads.length === testCase.expectedItemNumber;
|
||||
});
|
||||
if (testCase.expectClearListShown) {
|
||||
isnot("true", itemClearList.getAttribute("hidden"),
|
||||
"Should show Clear Preview Panel button");
|
||||
EventUtils.synthesizeMouseAtCenter(itemClearList, {}, window);
|
||||
} else {
|
||||
is("true", itemClearList.getAttribute("hidden"),
|
||||
"Should not show Clear Preview Panel button");
|
||||
}
|
||||
|
||||
yield itemNumberPromise;
|
||||
is(DownloadsView._downloads.length, testCase.expectedItemNumber,
|
||||
"Download items remained.");
|
||||
|
||||
yield task_resetState();
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This test exists because we use a <panelmultiview> element and it handles
|
||||
* some of the height changes for us. We need to verify that the height is
|
||||
* updated correctly if downloads are removed while the panel is hidden.
|
||||
*/
|
||||
add_task(function* test_height_reduced_after_removal() {
|
||||
yield task_addDownloads([
|
||||
{ state: nsIDM.DOWNLOAD_FINISHED },
|
||||
]);
|
||||
|
||||
yield task_openPanel();
|
||||
let panel = document.getElementById("downloadsPanel");
|
||||
let heightBeforeRemoval = panel.getBoundingClientRect().height;
|
||||
|
||||
// We want to close the panel before we remove the download from the list.
|
||||
DownloadsPanel.hidePanel();
|
||||
yield task_resetState();
|
||||
|
||||
yield task_openPanel();
|
||||
let heightAfterRemoval = panel.getBoundingClientRect().height;
|
||||
Assert.greater(heightBeforeRemoval, heightAfterRemoval);
|
||||
|
||||
yield task_resetState();
|
||||
});
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Make sure the downloads panel only opens automatically on the first
|
||||
* download it notices. All subsequent downloads, even across sessions, should
|
||||
* not open the panel automatically.
|
||||
*/
|
||||
add_task(function* test_first_download_panel() {
|
||||
// Clear the download panel has shown preference first as this test is used to
|
||||
// verify this preference's behaviour.
|
||||
let oldPrefValue = Services.prefs.getBoolPref("browser.download.panel.shown");
|
||||
Services.prefs.setBoolPref("browser.download.panel.shown", false);
|
||||
|
||||
registerCleanupFunction(function*() {
|
||||
// Clean up when the test finishes.
|
||||
yield task_resetState();
|
||||
|
||||
// Set the preference instead of clearing it afterwards to ensure the
|
||||
// right value is used no matter what the default was. This ensures the
|
||||
// panel doesn't appear and affect other tests.
|
||||
Services.prefs.setBoolPref("browser.download.panel.shown", oldPrefValue);
|
||||
});
|
||||
|
||||
// Ensure that state is reset in case previous tests didn't finish.
|
||||
yield task_resetState();
|
||||
|
||||
// With this set to false, we should automatically open the panel the first
|
||||
// time a download is started.
|
||||
DownloadsCommon.getData(window).panelHasShownBefore = false;
|
||||
|
||||
let promise = promisePanelOpened();
|
||||
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
|
||||
yield promise;
|
||||
|
||||
// If we got here, that means the panel opened.
|
||||
DownloadsPanel.hidePanel();
|
||||
|
||||
ok(DownloadsCommon.getData(window).panelHasShownBefore,
|
||||
"Should have recorded that the panel was opened on a download.")
|
||||
|
||||
// Next, make sure that if we start another download, we don't open the
|
||||
// panel automatically.
|
||||
let originalOnPopupShown = DownloadsPanel.onPopupShown;
|
||||
DownloadsPanel.onPopupShown = function () {
|
||||
originalOnPopupShown.apply(this, arguments);
|
||||
ok(false, "Should not have opened the downloads panel.");
|
||||
};
|
||||
|
||||
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
|
||||
|
||||
// Wait 2 seconds to ensure that the panel does not open.
|
||||
yield new Promise(resolve => setTimeout(resolve, 2000));
|
||||
DownloadsPanel.onPopupShown = originalOnPopupShown;
|
||||
});
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite";
|
||||
|
||||
function test_deleted_iframe(perSitePref, windowOptions={}) {
|
||||
return function*() {
|
||||
Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, perSitePref);
|
||||
let {DownloadLastDir} = Cu.import("resource://gre/modules/DownloadLastDir.jsm", {});
|
||||
|
||||
let win = yield promiseOpenAndLoadWindow(windowOptions);
|
||||
let tab = win.gBrowser.addTab();
|
||||
yield promiseTabLoadEvent(tab, "about:mozilla");
|
||||
|
||||
let doc = tab.linkedBrowser.contentDocument;
|
||||
let iframe = doc.createElement("iframe");
|
||||
doc.body.appendChild(iframe);
|
||||
|
||||
ok(iframe.contentWindow, "iframe should have a window");
|
||||
let gDownloadLastDir = new DownloadLastDir(iframe.contentWindow);
|
||||
let cw = iframe.contentWindow;
|
||||
let promiseIframeWindowGone = new Promise((resolve, reject) => {
|
||||
Services.obs.addObserver(function obs(subject, topic) {
|
||||
if (subject == cw) {
|
||||
Services.obs.removeObserver(obs, topic);
|
||||
resolve();
|
||||
}
|
||||
}, "dom-window-destroyed", false);
|
||||
});
|
||||
iframe.remove();
|
||||
yield promiseIframeWindowGone;
|
||||
cw = null;
|
||||
ok(!iframe.contentWindow, "Managed to destroy iframe");
|
||||
|
||||
let someDir = "blah";
|
||||
try {
|
||||
someDir = yield new Promise((resolve, reject) => {
|
||||
gDownloadLastDir.getFileAsync("http://www.mozilla.org/", function(dir) {
|
||||
resolve(dir);
|
||||
});
|
||||
});
|
||||
} catch (ex) {
|
||||
ok(false, "Got an exception trying to get the directory where things should be saved.");
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
// NB: someDir can legitimately be null here when set, hence the 'blah' workaround:
|
||||
isnot(someDir, "blah", "Should get a file even after the window was destroyed.");
|
||||
|
||||
try {
|
||||
gDownloadLastDir.setFile("http://www.mozilla.org/", null);
|
||||
} catch (ex) {
|
||||
ok(false, "Got an exception trying to set the directory where things should be saved.");
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
|
||||
yield promiseWindowClosed(win);
|
||||
Services.prefs.clearUserPref(SAVE_PER_SITE_PREF);
|
||||
};
|
||||
}
|
||||
|
||||
add_task(test_deleted_iframe(false));
|
||||
add_task(test_deleted_iframe(false));
|
||||
add_task(test_deleted_iframe(true, {private: true}));
|
||||
add_task(test_deleted_iframe(true, {private: true}));
|
||||
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
|
||||
"resource://testing-common/httpd.js");
|
||||
|
||||
registerCleanupFunction(function*() {
|
||||
yield task_resetState();
|
||||
yield task_clearHistory();
|
||||
});
|
||||
|
||||
add_task(function* test_indicatorDrop() {
|
||||
let downloadButton = document.getElementById("downloads-button");
|
||||
ok(downloadButton, "download button present");
|
||||
|
||||
let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
|
||||
getService(Ci.mozIJSSubScriptLoader);
|
||||
let EventUtils = {};
|
||||
scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
|
||||
function* task_drop(urls) {
|
||||
let dragData = [[{type: "text/plain", data: urls.join("\n")}]];
|
||||
|
||||
let list = yield Downloads.getList(Downloads.ALL);
|
||||
|
||||
let added = new Set();
|
||||
let succeeded = new Set();
|
||||
yield new Promise(function(resolve) {
|
||||
let view = {
|
||||
onDownloadAdded: function(download) {
|
||||
added.add(download.source.url);
|
||||
},
|
||||
onDownloadChanged: function(download) {
|
||||
if (!added.has(download.source.url))
|
||||
return;
|
||||
if (!download.succeeded)
|
||||
return;
|
||||
succeeded.add(download.source.url);
|
||||
if (succeeded.size == urls.length) {
|
||||
list.removeView(view).then(resolve);
|
||||
}
|
||||
}
|
||||
};
|
||||
list.addView(view).then(function() {
|
||||
EventUtils.synthesizeDrop(downloadButton, downloadButton, dragData, "link", window);
|
||||
});
|
||||
});
|
||||
|
||||
for (let url of urls) {
|
||||
ok(added.has(url), url + " is added to download");
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that state is reset in case previous tests didn't finish.
|
||||
yield task_resetState();
|
||||
|
||||
yield setDownloadDir();
|
||||
|
||||
startServer();
|
||||
|
||||
yield* task_drop([httpUrl("file1.txt")]);
|
||||
yield* task_drop([httpUrl("file1.txt"),
|
||||
httpUrl("file2.txt"),
|
||||
httpUrl("file3.txt")]);
|
||||
});
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
|
||||
"resource://testing-common/httpd.js");
|
||||
|
||||
registerCleanupFunction(function*() {
|
||||
yield task_resetState();
|
||||
yield task_clearHistory();
|
||||
});
|
||||
|
||||
add_task(function* test_indicatorDrop() {
|
||||
let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
|
||||
getService(Ci.mozIJSSubScriptLoader);
|
||||
let EventUtils = {};
|
||||
scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
|
||||
function task_drop(win, urls) {
|
||||
let dragData = [[{type: "text/plain", data: urls.join("\n")}]];
|
||||
|
||||
let listBox = win.document.getElementById("downloadsRichListBox");
|
||||
ok(listBox, "download list box present");
|
||||
|
||||
let list = yield Downloads.getList(Downloads.ALL);
|
||||
|
||||
let added = new Set();
|
||||
let succeeded = new Set();
|
||||
yield new Promise(function(resolve) {
|
||||
let view = {
|
||||
onDownloadAdded: function(download) {
|
||||
added.add(download.source.url);
|
||||
},
|
||||
onDownloadChanged: function(download) {
|
||||
if (!added.has(download.source.url))
|
||||
return;
|
||||
if (!download.succeeded)
|
||||
return;
|
||||
succeeded.add(download.source.url);
|
||||
if (succeeded.size == urls.length) {
|
||||
list.removeView(view).then(resolve);
|
||||
}
|
||||
}
|
||||
};
|
||||
list.addView(view).then(function() {
|
||||
EventUtils.synthesizeDrop(listBox, listBox, dragData, "link", win);
|
||||
});
|
||||
});
|
||||
|
||||
for (let url of urls) {
|
||||
ok(added.has(url), url + " is added to download");
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that state is reset in case previous tests didn't finish.
|
||||
yield task_resetState();
|
||||
|
||||
setDownloadDir();
|
||||
|
||||
startServer();
|
||||
|
||||
let win = yield openLibrary("Downloads");
|
||||
registerCleanupFunction(function() {
|
||||
win.close();
|
||||
});
|
||||
|
||||
yield* task_drop(win, [httpUrl("file1.txt")]);
|
||||
yield* task_drop(win, [httpUrl("file1.txt"),
|
||||
httpUrl("file2.txt"),
|
||||
httpUrl("file3.txt")]);
|
||||
});
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
registerCleanupFunction(function*() {
|
||||
// Clean up when the test finishes.
|
||||
yield task_resetState();
|
||||
});
|
||||
|
||||
/**
|
||||
* Make sure the downloads button and indicator overflows into the nav-bar
|
||||
* chevron properly, and then when those buttons are clicked in the overflow
|
||||
* panel that the downloads panel anchors to the chevron.
|
||||
*/
|
||||
add_task(function* test_overflow_anchor() {
|
||||
// Ensure that state is reset in case previous tests didn't finish.
|
||||
yield task_resetState();
|
||||
|
||||
// Record the original width of the window so we can put it back when
|
||||
// this test finishes.
|
||||
let oldWidth = window.outerWidth;
|
||||
|
||||
// The downloads button should not be overflowed to begin with.
|
||||
let button = CustomizableUI.getWidget("downloads-button")
|
||||
.forWindow(window);
|
||||
ok(!button.overflowed, "Downloads button should not be overflowed.");
|
||||
|
||||
// Hack - we lock the size of the default flex-y items in the nav-bar,
|
||||
// namely, the URL and search inputs. That way we can resize the
|
||||
// window without worrying about them flexing.
|
||||
const kFlexyItems = ["urlbar-container", "search-container"];
|
||||
registerCleanupFunction(() => unlockWidth(kFlexyItems));
|
||||
lockWidth(kFlexyItems);
|
||||
|
||||
// Resize the window to half of its original size. That should
|
||||
// be enough to overflow the downloads button.
|
||||
window.resizeTo(oldWidth / 2, window.outerHeight);
|
||||
yield waitForOverflowed(button, true);
|
||||
|
||||
let promise = promisePanelOpened();
|
||||
button.node.doCommand();
|
||||
yield promise;
|
||||
|
||||
let panel = DownloadsPanel.panel;
|
||||
let chevron = document.getElementById("nav-bar-overflow-button");
|
||||
is(panel.anchorNode, chevron, "Panel should be anchored to the chevron.");
|
||||
|
||||
DownloadsPanel.hidePanel();
|
||||
|
||||
// Unlock the widths on the flex-y items.
|
||||
unlockWidth(kFlexyItems);
|
||||
|
||||
// Put the window back to its original dimensions.
|
||||
window.resizeTo(oldWidth, window.outerHeight);
|
||||
|
||||
// The downloads button should eventually be un-overflowed.
|
||||
yield waitForOverflowed(button, false);
|
||||
|
||||
// Now try opening the panel again.
|
||||
promise = promisePanelOpened();
|
||||
button.node.doCommand();
|
||||
yield promise;
|
||||
|
||||
is(panel.anchorNode.id, "downloads-indicator-anchor");
|
||||
|
||||
DownloadsPanel.hidePanel();
|
||||
});
|
||||
|
||||
/**
|
||||
* For some node IDs, finds the nodes and sets their min-width's to their
|
||||
* current width, preventing them from flex-shrinking.
|
||||
*
|
||||
* @param aItemIDs an array of item IDs to set min-width on.
|
||||
*/
|
||||
function lockWidth(aItemIDs) {
|
||||
for (let itemID of aItemIDs) {
|
||||
let item = document.getElementById(itemID);
|
||||
let curWidth = item.getBoundingClientRect().width + "px";
|
||||
item.style.minWidth = curWidth;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the min-width's set on a set of IDs by lockWidth.
|
||||
*
|
||||
* @param aItemIDs an array of ItemIDs to remove min-width on.
|
||||
*/
|
||||
function unlockWidth(aItemIDs) {
|
||||
for (let itemID of aItemIDs) {
|
||||
let item = document.getElementById(itemID);
|
||||
item.style.minWidth = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a node to enter or exit the overflowed state.
|
||||
*
|
||||
* @param aItem the node to wait for.
|
||||
* @param aIsOverflowed if we're waiting for the item to be overflowed.
|
||||
*/
|
||||
function waitForOverflowed(aItem, aIsOverflowed) {
|
||||
let deferOverflow = Promise.defer();
|
||||
if (aItem.overflowed == aIsOverflowed) {
|
||||
return deferOverflow.resolve();
|
||||
}
|
||||
|
||||
let observer = new MutationObserver(function(aMutations) {
|
||||
if (aItem.overflowed == aIsOverflowed) {
|
||||
observer.disconnect();
|
||||
deferOverflow.resolve();
|
||||
}
|
||||
});
|
||||
observer.observe(aItem.node, {attributes: true});
|
||||
|
||||
return deferOverflow.promise;
|
||||
}
|
||||
300
browser/components/downloads/test/browser/head.js
Normal file
300
browser/components/downloads/test/browser/head.js
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Provides infrastructure for automated download components tests.
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
|
||||
"resource://gre/modules/Downloads.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
|
||||
"resource:///modules/DownloadsCommon.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
const nsIDM = Ci.nsIDownloadManager;
|
||||
|
||||
var gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
|
||||
gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
|
||||
|
||||
// Load mocking/stubbing library, sinon
|
||||
// docs: http://sinonjs.org/docs/
|
||||
Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
gTestTargetFile.remove(false);
|
||||
|
||||
delete window.sinon;
|
||||
delete window.setImmediate;
|
||||
delete window.clearImmediate;
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Asynchronous support subroutines
|
||||
|
||||
function promiseOpenAndLoadWindow(aOptions)
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
let win = OpenBrowserWindow(aOptions);
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad);
|
||||
resolve(win);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a load (or custom) event to finish in a given tab. If provided
|
||||
* load an uri into the tab.
|
||||
*
|
||||
* @param tab
|
||||
* The tab to load into.
|
||||
* @param [optional] url
|
||||
* The url to load, or the current url.
|
||||
* @param [optional] event
|
||||
* The load event type to wait for. Defaults to "load".
|
||||
* @return {Promise} resolved when the event is handled.
|
||||
* @resolves to the received event
|
||||
* @rejects if a valid load event is not received within a meaningful interval
|
||||
*/
|
||||
function promiseTabLoadEvent(tab, url, eventType="load")
|
||||
{
|
||||
let deferred = Promise.defer();
|
||||
info("Wait tab event: " + eventType);
|
||||
|
||||
function handle(event) {
|
||||
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
|
||||
event.target.location.href == "about:blank" ||
|
||||
(url && event.target.location.href != url)) {
|
||||
info("Skipping spurious '" + eventType + "'' event" +
|
||||
" for " + event.target.location.href);
|
||||
return;
|
||||
}
|
||||
// Remove reference to tab from the cleanup function:
|
||||
realCleanup = () => {};
|
||||
tab.linkedBrowser.removeEventListener(eventType, handle, true);
|
||||
info("Tab event received: " + eventType);
|
||||
deferred.resolve(event);
|
||||
}
|
||||
|
||||
// Juggle a bit to avoid leaks:
|
||||
let realCleanup = () => tab.linkedBrowser.removeEventListener(eventType, handle, true);
|
||||
registerCleanupFunction(() => realCleanup());
|
||||
|
||||
tab.linkedBrowser.addEventListener(eventType, handle, true, true);
|
||||
if (url)
|
||||
tab.linkedBrowser.loadURI(url);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function promiseWindowClosed(win)
|
||||
{
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
Services.obs.addObserver(function obs(subject, topic) {
|
||||
if (subject == win) {
|
||||
Services.obs.removeObserver(obs, topic);
|
||||
resolve();
|
||||
}
|
||||
}, "domwindowclosed", false);
|
||||
});
|
||||
win.close();
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
function promiseFocus()
|
||||
{
|
||||
let deferred = Promise.defer();
|
||||
waitForFocus(deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function promisePanelOpened()
|
||||
{
|
||||
let deferred = Promise.defer();
|
||||
|
||||
if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") {
|
||||
return deferred.resolve();
|
||||
}
|
||||
|
||||
// Hook to wait until the panel is shown.
|
||||
let originalOnPopupShown = DownloadsPanel.onPopupShown;
|
||||
DownloadsPanel.onPopupShown = function () {
|
||||
DownloadsPanel.onPopupShown = originalOnPopupShown;
|
||||
originalOnPopupShown.apply(this, arguments);
|
||||
|
||||
// Defer to the next tick of the event loop so that we don't continue
|
||||
// processing during the DOM event handler itself.
|
||||
setTimeout(deferred.resolve, 0);
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function* task_resetState()
|
||||
{
|
||||
// Remove all downloads.
|
||||
let publicList = yield Downloads.getList(Downloads.PUBLIC);
|
||||
let downloads = yield publicList.getAll();
|
||||
for (let download of downloads) {
|
||||
publicList.remove(download);
|
||||
yield download.finalize(true);
|
||||
}
|
||||
|
||||
DownloadsPanel.hidePanel();
|
||||
|
||||
yield promiseFocus();
|
||||
}
|
||||
|
||||
function* task_addDownloads(aItems)
|
||||
{
|
||||
let startTimeMs = Date.now();
|
||||
|
||||
let publicList = yield Downloads.getList(Downloads.PUBLIC);
|
||||
for (let item of aItems) {
|
||||
let download = {
|
||||
source: {
|
||||
url: "http://www.example.com/test-download.txt",
|
||||
},
|
||||
target: {
|
||||
path: gTestTargetFile.path,
|
||||
},
|
||||
succeeded: item.state == nsIDM.DOWNLOAD_FINISHED,
|
||||
canceled: item.state == nsIDM.DOWNLOAD_CANCELED ||
|
||||
item.state == nsIDM.DOWNLOAD_PAUSED,
|
||||
error: item.state == nsIDM.DOWNLOAD_FAILED ? new Error("Failed.") : null,
|
||||
hasPartialData: item.state == nsIDM.DOWNLOAD_PAUSED,
|
||||
hasBlockedData: item.hasBlockedData || false,
|
||||
startTime: new Date(startTimeMs++),
|
||||
};
|
||||
// `"errorObj" in download` must be false when there's no error.
|
||||
if (item.errorObj) {
|
||||
download.errorObj = item.errorObj;
|
||||
}
|
||||
yield publicList.add(yield Downloads.createDownload(download));
|
||||
}
|
||||
}
|
||||
|
||||
function* task_openPanel()
|
||||
{
|
||||
yield promiseFocus();
|
||||
|
||||
let promise = promisePanelOpened();
|
||||
DownloadsPanel.showPanel();
|
||||
yield promise;
|
||||
}
|
||||
|
||||
function* setDownloadDir() {
|
||||
let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
tmpDir.append("testsavedir");
|
||||
if (!tmpDir.exists()) {
|
||||
tmpDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
||||
registerCleanupFunction(function () {
|
||||
try {
|
||||
tmpDir.remove(true);
|
||||
} catch (e) {
|
||||
// On Windows debug build this may fail.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
yield new Promise(resolve => {
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["browser.download.folderList", 2],
|
||||
["browser.download.dir", tmpDir, Ci.nsIFile],
|
||||
]}, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
let gHttpServer = null;
|
||||
function startServer() {
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
registerCleanupFunction(function*() {
|
||||
yield new Promise(function(resolve) {
|
||||
gHttpServer.stop(resolve);
|
||||
});
|
||||
});
|
||||
|
||||
gHttpServer.registerPathHandler("/file1.txt", (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write("file1");
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
gHttpServer.registerPathHandler("/file2.txt", (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write("file2");
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
gHttpServer.registerPathHandler("/file3.txt", (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write("file3");
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
}
|
||||
|
||||
function httpUrl(aFileName) {
|
||||
return "http://localhost:" + gHttpServer.identity.primaryPort + "/" +
|
||||
aFileName;
|
||||
}
|
||||
|
||||
function task_clearHistory() {
|
||||
return new Promise(function(resolve) {
|
||||
Services.obs.addObserver(function observeCH(aSubject, aTopic, aData) {
|
||||
Services.obs.removeObserver(observeCH, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
|
||||
resolve();
|
||||
}, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
|
||||
PlacesUtils.history.clear();
|
||||
});
|
||||
}
|
||||
|
||||
function openLibrary(aLeftPaneRoot) {
|
||||
let library = window.openDialog("chrome://browser/content/places/places.xul",
|
||||
"", "chrome,toolbar=yes,dialog=no,resizable",
|
||||
aLeftPaneRoot);
|
||||
|
||||
return new Promise(resolve => {
|
||||
waitForFocus(resolve, library);
|
||||
});
|
||||
}
|
||||
|
||||
function promiseAlertDialogOpen(buttonAction) {
|
||||
return new Promise(resolve => {
|
||||
Services.ww.registerNotification(function onOpen(subj, topic, data) {
|
||||
if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
|
||||
// The test listens for the "load" event which guarantees that the alert
|
||||
// class has already been added (it is added when "DOMContentLoaded" is
|
||||
// fired).
|
||||
subj.addEventListener("load", function onLoad() {
|
||||
subj.removeEventListener("load", onLoad);
|
||||
if (subj.document.documentURI ==
|
||||
"chrome://global/content/commonDialog.xul") {
|
||||
Services.ww.unregisterNotification(onOpen);
|
||||
|
||||
let dialog = subj.document.getElementById("commonDialog");
|
||||
ok(dialog.classList.contains("alert-dialog"),
|
||||
"The dialog element should contain an alert class.");
|
||||
|
||||
let doc = subj.document.documentElement;
|
||||
doc.getButton(buttonAction).click();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
7
browser/components/downloads/test/unit/.eslintrc.js
Normal file
7
browser/components/downloads/test/unit/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/xpcshell/xpcshell.eslintrc.js"
|
||||
]
|
||||
};
|
||||
18
browser/components/downloads/test/unit/head.js
Normal file
18
browser/components/downloads/test/unit/head.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Provides infrastructure for automated download components tests.
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
var Cr = Components.results;
|
||||
|
||||
Cu.import("resource:///modules/DownloadsCommon.jsm");
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests for the functions located directly in the "DownloadsCommon" object.
|
||||
*/
|
||||
|
||||
function testFormatTimeLeft(aSeconds, aExpectedValue, aExpectedUnitString)
|
||||
{
|
||||
let expected = "";
|
||||
if (aExpectedValue) {
|
||||
// Format the expected result based on the current language.
|
||||
expected = DownloadsCommon.strings[aExpectedUnitString](aExpectedValue);
|
||||
}
|
||||
do_check_eq(DownloadsCommon.formatTimeLeft(aSeconds), expected);
|
||||
}
|
||||
|
||||
function run_test()
|
||||
{
|
||||
testFormatTimeLeft( 0, "", "");
|
||||
testFormatTimeLeft( 1, "1", "shortTimeLeftSeconds");
|
||||
testFormatTimeLeft( 29, "29", "shortTimeLeftSeconds");
|
||||
testFormatTimeLeft( 30, "30", "shortTimeLeftSeconds");
|
||||
testFormatTimeLeft( 31, "1", "shortTimeLeftMinutes");
|
||||
testFormatTimeLeft( 60, "1", "shortTimeLeftMinutes");
|
||||
testFormatTimeLeft( 89, "1", "shortTimeLeftMinutes");
|
||||
testFormatTimeLeft( 90, "2", "shortTimeLeftMinutes");
|
||||
testFormatTimeLeft( 91, "2", "shortTimeLeftMinutes");
|
||||
testFormatTimeLeft( 3600, "1", "shortTimeLeftHours");
|
||||
testFormatTimeLeft( 86400, "24", "shortTimeLeftHours");
|
||||
testFormatTimeLeft( 169200, "47", "shortTimeLeftHours");
|
||||
testFormatTimeLeft( 172800, "2", "shortTimeLeftDays");
|
||||
testFormatTimeLeft(8553600, "99", "shortTimeLeftDays");
|
||||
testFormatTimeLeft(8640000, "99", "shortTimeLeftDays");
|
||||
}
|
||||
7
browser/components/downloads/test/unit/xpcshell.ini
Normal file
7
browser/components/downloads/test/unit/xpcshell.ini
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[DEFAULT]
|
||||
head = head.js
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_DownloadsCommon.js]
|
||||
Loading…
Add table
Add a link
Reference in a new issue