mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-11 13:48:23 +02:00
The -*- file variable lines -*- establish per-file settings that Emacs will pick up. This patch makes the following changes to those lines (and touches nothing else): - Never set the buffer's mode. Years ago, Emacs did not have a good JavaScript mode, so it made sense to use Java or C++ mode in .js files. However, Emacs has had js-mode for years now; it's perfectly serviceable, and is available and enabled by default in all major Emacs packagings. Selecting a mode in the -*- file variable line -*- is almost always the wrong thing to do anyway. It overrides Emacs's default choice, which is (now) reasonable; and even worse, it overrides settings the user might have made in their '.emacs' file for that file extension. It's only useful when there's something specific about that particular file that makes a particular mode appropriate. - Correctly propagate settings that establish the correct indentation level for this file: c-basic-offset and js2-basic-offset should be js-indent-level. Whatever value they're given should be preserved; different parts of our tree use different indentation styles. - We don't use tabs in Mozilla JS code. Always set indent-tabs-mode: nil. Remove tab-width: settings, at least in files that don't contain tab characters. - Remove js2-mode settings that belong in the user's .emacs file, like js2-skip-preprocessor-directives.
615 lines
18 KiB
JavaScript
615 lines
18 KiB
JavaScript
/* -*- 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()
|
|
"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: function DB_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: function DB_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: function DB_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: function DB_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: function DB_checkIsVisible(aCallback)
|
|
{
|
|
function DB_CEV_callback() {
|
|
if (!this._placeholder) {
|
|
aCallback(false);
|
|
} else {
|
|
let element = DownloadsIndicatorView.indicator || this._placeholder;
|
|
aCallback(isElementVisible(element.parentNode));
|
|
}
|
|
}
|
|
DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay,
|
|
DB_CEV_callback.bind(this));
|
|
},
|
|
|
|
/**
|
|
* 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: function DB_getAnchor(aCallback)
|
|
{
|
|
// Do not allow anchoring the panel to the element while customizing.
|
|
if (this._customizing) {
|
|
aCallback(null);
|
|
return;
|
|
}
|
|
|
|
function DB_GA_callback() {
|
|
this._anchorRequested = true;
|
|
aCallback(this._getAnchorInternal());
|
|
}
|
|
|
|
DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay,
|
|
DB_GA_callback.bind(this));
|
|
},
|
|
|
|
/**
|
|
* Allows the temporary anchor to be hidden.
|
|
*/
|
|
releaseAnchor: function DB_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");
|
|
}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// 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: function DIV_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: function DIV_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 = false;
|
|
},
|
|
|
|
/**
|
|
* Ensures that the user interface elements required to display the indicator
|
|
* are loaded, then invokes the given callback.
|
|
*/
|
|
_ensureOperational: function DIV_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;
|
|
}
|
|
|
|
function DIV_EO_callback() {
|
|
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();
|
|
}
|
|
}
|
|
|
|
DownloadsOverlayLoader.ensureOverlayLoaded(
|
|
DownloadsButton.kIndicatorOverlay,
|
|
DIV_EO_callback.bind(this));
|
|
},
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//// 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: function DIV_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: function DIV_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(function () {
|
|
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;
|
|
if (aValue) {
|
|
this.indicator.setAttribute("attention", "true");
|
|
} else {
|
|
this.indicator.removeAttribute("attention");
|
|
}
|
|
}
|
|
return aValue;
|
|
},
|
|
_attention: false,
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//// User interface event functions
|
|
|
|
onWindowUnload: function DIV_onWindowUnload()
|
|
{
|
|
// This function is registered as an event listener, we can't use "this".
|
|
DownloadsIndicatorView.ensureTerminated();
|
|
},
|
|
|
|
onCommand: function DIV_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: function DIV_onDragOver(aEvent)
|
|
{
|
|
browserDragAndDrop.dragOver(aEvent);
|
|
},
|
|
|
|
onDrop: function DIV_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 name = {};
|
|
let url = browserDragAndDrop.drop(aEvent, name);
|
|
if (url) {
|
|
if (url.startsWith("about:")) {
|
|
return;
|
|
}
|
|
|
|
let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
|
|
saveURL(url, name.value, null, true, true, null, sourceDoc);
|
|
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: function() {
|
|
this._indicator = null;
|
|
this.__indicatorCounter = null;
|
|
this.__indicatorProgress = null;
|
|
},
|
|
|
|
afterCustomize: function() {
|
|
// 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();
|
|
}
|
|
}
|
|
};
|
|
|