diff options
author | Thomas Groman <tgroman@nuegia.net> | 2020-04-20 20:49:37 -0700 |
---|---|---|
committer | Thomas Groman <tgroman@nuegia.net> | 2020-04-20 20:49:37 -0700 |
commit | f9cab004186edb425a9b88ad649726605080a17c (patch) | |
tree | e2dae51d3144e83d097a12e7a1499e3ea93f90be /components/downloads/content | |
parent | f428692de8b59ab89a66502c079e1823dfda8aeb (diff) | |
download | webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.gz webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.lz webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.xz webbrowser-f9cab004186edb425a9b88ad649726605080a17c.zip |
move browser to webbrowser/
Diffstat (limited to 'components/downloads/content')
-rw-r--r-- | components/downloads/content/allDownloadsViewOverlay.css | 56 | ||||
-rw-r--r-- | components/downloads/content/allDownloadsViewOverlay.js | 1399 | ||||
-rw-r--r-- | components/downloads/content/allDownloadsViewOverlay.xul | 119 | ||||
-rw-r--r-- | components/downloads/content/contentAreaDownloadsView.css | 11 | ||||
-rw-r--r-- | components/downloads/content/contentAreaDownloadsView.js | 15 | ||||
-rw-r--r-- | components/downloads/content/contentAreaDownloadsView.xul | 45 | ||||
-rw-r--r-- | components/downloads/content/download.css | 45 | ||||
-rw-r--r-- | components/downloads/content/download.xml | 188 | ||||
-rw-r--r-- | components/downloads/content/downloads.css | 132 | ||||
-rw-r--r-- | components/downloads/content/downloads.js | 1614 | ||||
-rw-r--r-- | components/downloads/content/downloadsOverlay.xul | 142 | ||||
-rw-r--r-- | components/downloads/content/indicator.js | 609 | ||||
-rw-r--r-- | components/downloads/content/indicatorOverlay.xul | 60 |
13 files changed, 0 insertions, 4435 deletions
diff --git a/components/downloads/content/allDownloadsViewOverlay.css b/components/downloads/content/allDownloadsViewOverlay.css deleted file mode 100644 index c062ae4..0000000 --- a/components/downloads/content/allDownloadsViewOverlay.css +++ /dev/null @@ -1,56 +0,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/. */ - -/** - * 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-full-ui'); -} - -richlistitem.download[active]:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="5"], /* Starting (queued) */ - [state="7"]) /* Scanning */ -{ - -moz-binding: url('chrome://browser/content/downloads/download.xml#download-in-progress-full-ui'); -} - -.download-state:not( [state="0"] /* Downloading */) - .downloadPauseMenuItem, -.download-state:not( [state="4"] /* Paused */) - .downloadResumeMenuItem, -.download-state:not(:-moz-any([state="2"], /* Failed */ - [state="4"]) /* Paused */) - .downloadCancelMenuItem, -.download-state[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"] /* Scanning */ .downloadCommandsSeparator -{ - display: none; -} diff --git a/components/downloads/content/allDownloadsViewOverlay.js b/components/downloads/content/allDownloadsViewOverlay.js deleted file mode 100644 index 4830f21..0000000 --- a/components/downloads/content/allDownloadsViewOverlay.js +++ /dev/null @@ -1,1399 +0,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/. */ - -var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", - "resource://gre/modules/DownloadUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", - "resource:///modules/DownloadsCommon.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI", - "resource:///modules/DownloadsViewUI.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", - "resource://gre/modules/PlacesUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/Promise.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", - "resource:///modules/RecentWindow.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Services", - "resource://gre/modules/Services.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); - -const nsIDM = Ci.nsIDownloadManager; - -const DESTINATION_FILE_URI_ANNO = "downloads/destinationFileURI"; -const DOWNLOAD_META_DATA_ANNO = "downloads/metaData"; - -const DOWNLOAD_VIEW_SUPPORTED_COMMANDS = - ["cmd_delete", "cmd_copy", "cmd_paste", "cmd_selectAll", - "downloadsCmd_pauseResume", "downloadsCmd_cancel", - "downloadsCmd_open", "downloadsCmd_show", "downloadsCmd_retry", - "downloadsCmd_openReferrer", "downloadsCmd_clearDownloads"]; - -/** - * Represents a download from the browser history. It implements part of the - * interface of the Download object. - * - * @param aPlacesNode - * The Places node from which the history download should be initialized. - */ -function HistoryDownload(aPlacesNode) { - // TODO (bug 829201): history downloads should get the referrer from Places. - this.source = { - url: aPlacesNode.uri, - }; - this.target = { - path: undefined, - exists: false, - size: undefined, - }; - - // In case this download cannot obtain its end time from the Places metadata, - // use the time from the Places node, that is the start time of the download. - this.endTime = aPlacesNode.time / 1000; -} - -HistoryDownload.prototype = { - /** - * Pushes information from Places metadata into this object. - */ - updateFromMetaData(metaData) { - try { - this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"] - .getService(Ci.nsIFileProtocolHandler) - .getFileFromURLSpec(metaData.targetFileSpec).path; - } catch (ex) { - this.target.path = undefined; - } - - if ("state" in metaData) { - this.succeeded = metaData.state == nsIDM.DOWNLOAD_FINISHED; - this.error = metaData.state == nsIDM.DOWNLOAD_FAILED - ? { message: "History download failed." } - : metaData.state == nsIDM.DOWNLOAD_BLOCKED_PARENTAL - ? { becauseBlockedByParentalControls: true } - : metaData.state == nsIDM.DOWNLOAD_DIRTY - ? { becauseBlockedByReputationCheck: true } - : null; - this.canceled = metaData.state == nsIDM.DOWNLOAD_CANCELED || - metaData.state == nsIDM.DOWNLOAD_PAUSED; - this.endTime = metaData.endTime; - - // Normal history downloads are assumed to exist until the user interface - // is refreshed, at which point these values may be updated. - this.target.exists = true; - this.target.size = metaData.fileSize; - } else { - // Metadata might be missing from a download that has started but hasn't - // stopped already. Normally, this state is overridden with the one from - // the corresponding in-progress session download. But if the browser is - // terminated abruptly and additionally the file with information about - // in-progress downloads is lost, we may end up using this state. We use - // the failed state to allow the download to be restarted. - // - // On the other hand, if the download is missing the target file - // annotation as well, it is just a very old one, and we can assume it - // succeeded. - this.succeeded = !this.target.path; - this.error = this.target.path ? { message: "Unstarted download." } : null; - this.canceled = false; - - // These properties may be updated if the user interface is refreshed. - this.target.exists = false; - this.target.size = undefined; - } - }, - - /** - * History downloads are never in progress. - */ - stopped: true, - - /** - * No percentage indication is shown for history downloads. - */ - hasProgress: false, - - /** - * History downloads cannot be restarted using their partial data, even if - * they are indicated as paused in their Places metadata. The only way is to - * use the information from a persisted session download, that will be shown - * instead of the history download. In case this session download is not - * available, we show the history download as canceled, not paused. - */ - hasPartialData: false, - - /** - * This method mimicks the "start" method of session downloads, and is called - * when the user retries a history download. - * - * At present, we always ask the user for a new target path when retrying a - * history download. In the future we may consider reusing the known target - * path if the folder still exists and the file name is not already used, - * except when the user preferences indicate that the target path should be - * requested every time a new download is started. - */ - start() { - let browserWin = RecentWindow.getMostRecentBrowserWindow(); - let initiatingDoc = browserWin ? browserWin.document : document; - - // Do not suggest a file name if we don't know the original target. - let leafName = this.target.path ? OS.Path.basename(this.target.path) : null; - DownloadURL(this.source.url, leafName, initiatingDoc); - - return Promise.resolve(); - }, - - /** - * This method mimicks the "refresh" method of session downloads, except that - * it cannot notify that the data changed to the Downloads View. - */ - refresh: Task.async(function* () { - try { - this.target.size = (yield OS.File.stat(this.target.path)).size; - this.target.exists = true; - } catch (ex) { - // We keep the known file size from the metadata, if any. - this.target.exists = false; - } - }), -}; - -/** - * A download element shell is responsible for handling the commands and the - * displayed data for a single download view element. - * - * The shell may contain a session download, a history download, or both. When - * both a history and a session download are present, the session download gets - * priority and its information is displayed. - * - * On construction, a new richlistitem is created, and can be accessed through - * the |element| getter. The shell doesn't insert the item in a richlistbox, the - * caller must do it and remove the element when it's no longer needed. - * - * The caller is also responsible for forwarding status notifications for - * session downloads, calling the onStateChanged and onChanged methods. - * - * @param [optional] aSessionDownload - * The session download, required if aHistoryDownload is not set. - * @param [optional] aHistoryDownload - * The history download, required if aSessionDownload is not set. - */ -function HistoryDownloadElementShell(aSessionDownload, aHistoryDownload) { - this.element = document.createElement("richlistitem"); - this.element._shell = this; - - this.element.classList.add("download"); - this.element.classList.add("download-state"); - - if (aSessionDownload) { - this.sessionDownload = aSessionDownload; - } - if (aHistoryDownload) { - this.historyDownload = aHistoryDownload; - } -} - -HistoryDownloadElementShell.prototype = { - __proto__: DownloadsViewUI.DownloadElementShell.prototype, - - /** - * Manages the "active" state of the shell. By default all the shells without - * a session download are inactive, thus their UI is not updated. They must - * be activated when entering the visible area. Session downloads are always - * active. - */ - ensureActive: function DES_ensureActive() { - if (!this._active) { - this._active = true; - this.element.setAttribute("active", true); - this._updateUI(); - } - }, - get active() !!this._active, - - /** - * Overrides the base getter to return the Download or HistoryDownload object - * for displaying information and executing commands in the user interface. - */ - get download() this._sessionDownload || this._historyDownload, - - _sessionDownload: null, - get sessionDownload() this._sessionDownload, - set sessionDownload(aValue) { - if (this._sessionDownload != aValue) { - if (!aValue && !this._historyDownload) { - throw new Error("Should always have either a Download or a HistoryDownload"); - } - - this._sessionDownload = aValue; - - this.ensureActive(); - this._updateUI(); - } - return aValue; - }, - - _historyDownload: null, - get historyDownload() this._historyDownload, - set historyDownload(aValue) { - if (this._historyDownload != aValue) { - if (!aValue && !this._sessionDownload) { - throw new Error("Should always have either a Download or a HistoryDownload"); - } - - this._historyDownload = aValue; - - // We don't need to update the UI if we had a session data item, because - // the places information isn't used in this case. - if (!this._sessionDownload) { - this._updateUI(); - } - } - return aValue; - }, - - _updateUI() { - // There is nothing to do if the item has always been invisible. - if (!this.active) { - return; - } - - // Since the state changed, we may need to check the target file again. - this._targetFileChecked = false; - - this._updateState(); - }, - - get statusTextAndTip() { - let status = this.rawStatusTextAndTip; - - // The base object would show extended progress information in the tooltip, - // but we move this to the main view and never display a tooltip. - if (!this.download.stopped) { - status.text = status.tip; - } - status.tip = ""; - - return status; - }, - - onStateChanged() { - this.element.setAttribute("image", this.image); - this.element.setAttribute("state", - DownloadsCommon.stateOfDownload(this.download)); - - if (this.element.selected) { - goUpdateDownloadCommands(); - } else { - goUpdateCommand("downloadsCmd_clearDownloads"); - } - }, - - onChanged() { - this._updateProgress(); - }, - - /* nsIController */ - isCommandEnabled: function DES_isCommandEnabled(aCommand) { - // The only valid command for inactive elements is cmd_delete. - if (!this.active && aCommand != "cmd_delete") - return false; - switch (aCommand) { - case "downloadsCmd_open": - // This property is false if the download did not succeed. - return this.download.target.exists; - case "downloadsCmd_show": - // TODO: Bug 827010 - Handle part-file asynchronously. - if (this._sessionDownload && this.download.target.partFilePath) { - let partFile = new FileUtils.File(this.download.target.partFilePath); - if (partFile.exists()) { - return true; - } - } - - // This property is false if the download did not succeed. - return this.download.target.exists; - case "downloadsCmd_pauseResume": - return this.download.hasPartialData && !this.download.error; - case "downloadsCmd_retry": - return this.download.canceled || this.download.error; - case "downloadsCmd_openReferrer": - return !!this.download.source.referrer; - case "cmd_delete": - // We don't want in-progress downloads to be removed accidentally. - return this.download.stopped; - case "downloadsCmd_cancel": - return !!this._sessionDownload; - } - return false; - }, - - /* nsIController */ - doCommand: function DES_doCommand(aCommand) { - switch (aCommand) { - case "downloadsCmd_open": { - let file = new FileUtils.File(this.download.target.path); - DownloadsCommon.openDownloadedFile(file, null, window); - break; - } - case "downloadsCmd_show": { - let file = new FileUtils.File(this.download.target.path); - DownloadsCommon.showDownloadedFile(file); - break; - } - case "downloadsCmd_openReferrer": { - openURL(this.download.source.referrer); - break; - } - case "downloadsCmd_cancel": { - this.download.cancel().catch(() => {}); - this.download.removePartialData().catch(Cu.reportError); - break; - } - case "cmd_delete": { - if (this._sessionDownload) { - DownloadsCommon.removeAndFinalizeDownload(this.download); - } - if (this._historyDownload) { - let uri = NetUtil.newURI(this.download.source.url); - PlacesUtils.bhistory.removePage(uri); - } - break; - } - case "downloadsCmd_retry": { - // Errors when retrying are already reported as download failures. - this.download.start().catch(() => {}); - break; - } - case "downloadsCmd_pauseResume": { - // This command is only enabled for session downloads. - if (this.download.stopped) { - this.download.start(); - } else { - this.download.cancel(); - } - break; - } - } - }, - - // Returns whether or not the download handled by this shell should - // show up in the search results for the given term. Both the display - // name for the download and the url are searched. - matchesSearchTerm: function DES_matchesSearchTerm(aTerm) { - if (!aTerm) - return true; - aTerm = aTerm.toLowerCase(); - return this.displayName.toLowerCase().contains(aTerm) || - this.download.source.url.toLowerCase().contains(aTerm); - }, - - // Handles return keypress on the element (the keypress listener is - // set in the DownloadsPlacesView object). - doDefaultCommand: function DES_doDefaultCommand() { - function getDefaultCommandForState(aState) { - switch (aState) { - case nsIDM.DOWNLOAD_FINISHED: - return "downloadsCmd_open"; - case nsIDM.DOWNLOAD_PAUSED: - return "downloadsCmd_pauseResume"; - case nsIDM.DOWNLOAD_NOTSTARTED: - case nsIDM.DOWNLOAD_QUEUED: - return "downloadsCmd_cancel"; - case nsIDM.DOWNLOAD_FAILED: - case nsIDM.DOWNLOAD_CANCELED: - return "downloadsCmd_retry"; - case nsIDM.DOWNLOAD_SCANNING: - return "downloadsCmd_show"; - case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: - case nsIDM.DOWNLOAD_DIRTY: - case nsIDM.DOWNLOAD_BLOCKED_POLICY: - return "downloadsCmd_openReferrer"; - } - return ""; - } - let state = DownloadsCommon.stateOfDownload(this.download); - let command = getDefaultCommandForState(state); - if (command && this.isCommandEnabled(command)) - this.doCommand(command); - }, - - /** - * This method is called by the outer download view, after the controller - * commands have already been updated. In case we did not check for the - * existence of the target file already, we can do it now and then update - * the commands as needed. - */ - onSelect: function DES_onSelect() { - if (!this.active) - return; - - // If this is a history download for which no target file information is - // available, we cannot retrieve information about the target file. - if (!this.download.target.path) { - return; - } - - // Start checking for existence. This may be done twice if onSelect is - // called again before the information is collected. - if (!this._targetFileChecked) { - this._checkTargetFileOnSelect().catch(Cu.reportError); - } - }, - - _checkTargetFileOnSelect: Task.async(function* () { - try { - yield this.download.refresh(); - } finally { - // Do not try to check for existence again if this failed once. - this._targetFileChecked = true; - } - - // Update the commands only if the element is still selected. - if (this.element.selected) { - goUpdateDownloadCommands(); - } - - // Ensure the interface has been updated based on the new values. We need to - // do this because history downloads can't trigger update notifications. - this._updateProgress(); - }), -}; - -/** - * A Downloads Places View is a places view designed to show a places query - * for history downloads alongside the session downloads. - * - * As we don't use the places controller, some methods implemented by other - * places views are not implemented by this view. - * - * A richlistitem in this view can represent either a past download or a session - * download, or both. Session downloads are shown first in the view, and as long - * as they exist they "collapses" their history "counterpart" (So we don't show two - * items for every download). - */ -function DownloadsPlacesView(aRichListBox, aActive = true) { - this._richlistbox = aRichListBox; - this._richlistbox._placesView = this; - window.controllers.insertControllerAt(0, this); - - // Map download URLs to download element shells regardless of their type - this._downloadElementsShellsForURI = new Map(); - - // Map download data items to their element shells. - this._viewItemsForDownloads = new WeakMap(); - - // Points to the last session download element. We keep track of this - // in order to keep all session downloads above past downloads. - this._lastSessionDownloadElement = null; - - this._searchTerm = ""; - - this._active = aActive; - - // Register as a downloads view. The places data will be initialized by - // the places setter. - this._initiallySelectedElement = null; - this._downloadsData = DownloadsCommon.getData(window.opener || window); - this._downloadsData.addView(this); - - // Get the Download button out of the attention state since we're about to - // view all downloads. - DownloadsCommon.getIndicatorData(window).attention = false; - - // Make sure to unregister the view if the window is closed. - window.addEventListener("unload", function() { - window.controllers.removeController(this); - this._downloadsData.removeView(this); - this.result = null; - }.bind(this), true); - // Resizing the window may change items visibility. - window.addEventListener("resize", function() { - this._ensureVisibleElementsAreActive(); - }.bind(this), true); -} - -DownloadsPlacesView.prototype = { - get associatedElement() this._richlistbox, - - get active() this._active, - set active(val) { - this._active = val; - if (this._active) - this._ensureVisibleElementsAreActive(); - return this._active; - }, - - /** - * This cache exists in order to optimize the load of the Downloads View, when - * Places annotations for history downloads must be read. In fact, annotations - * are stored in a single table, and reading all of them at once is much more - * efficient than an individual query. - * - * When this property is first requested, it reads the annotations for all the - * history downloads and stores them indefinitely. - * - * The historical annotations are not expected to change for the duration of - * the session, except in the case where a session download is running for the - * same URI as a history download. To ensure we don't use stale data, URIs - * corresponding to session downloads are permanently removed from the cache. - * This is a very small mumber compared to history downloads. - * - * This property returns a Map from each download source URI found in Places - * annotations to an object with the format: - * - * { targetFileSpec, state, endTime, fileSize, ... } - * - * The targetFileSpec property is the value of "downloads/destinationFileURI", - * while the other properties are taken from "downloads/metaData". Any of the - * properties may be missing from the object. - */ - get _cachedPlacesMetaData() { - if (!this.__cachedPlacesMetaData) { - this.__cachedPlacesMetaData = new Map(); - - // Read the metadata annotations first, but ignore invalid JSON. - for (let result of PlacesUtils.annotations.getAnnotationsWithName( - DOWNLOAD_META_DATA_ANNO)) { - try { - this.__cachedPlacesMetaData.set(result.uri.spec, - JSON.parse(result.annotationValue)); - } catch (ex) {} - } - - // Add the target file annotations to the metadata. - for (let result of PlacesUtils.annotations.getAnnotationsWithName( - DESTINATION_FILE_URI_ANNO)) { - let metaData = this.__cachedPlacesMetaData.get(result.uri.spec); - if (!metaData) { - metaData = {}; - this.__cachedPlacesMetaData.set(result.uri.spec, metaData); - } - metaData.targetFileSpec = result.annotationValue; - } - } - - return this.__cachedPlacesMetaData; - }, - __cachedPlacesMetaData: null, - - /** - * Reads current metadata from Places annotations for the specified URI, and - * returns an object with the format: - * - * { targetFileSpec, state, endTime, fileSize, ... } - * - * The targetFileSpec property is the value of "downloads/destinationFileURI", - * while the other properties are taken from "downloads/metaData". Any of the - * properties may be missing from the object. - */ - _getPlacesMetaDataFor(spec) { - let metaData = {}; - - try { - let uri = NetUtil.newURI(spec); - try { - metaData = JSON.parse(PlacesUtils.annotations.getPageAnnotation( - uri, DOWNLOAD_META_DATA_ANNO)); - } catch (ex) {} - metaData.targetFileSpec = PlacesUtils.annotations.getPageAnnotation( - uri, DESTINATION_FILE_URI_ANNO); - } catch (ex) {} - - return metaData; - }, - - /** - * Given a data item for a session download, or a places node for a past - * download, updates the view as necessary. - * 1. If the given data is a places node, we check whether there are any - * elements for the same download url. If there are, then we just reset - * their places node. Otherwise we add a new download element. - * 2. If the given data is a data item, we first check if there's a history - * download in the list that is not associated with a data item. If we - * found one, we use it for the data item as well and reposition it - * alongside the other session downloads. If we don't, then we go ahead - * and create a new element for the download. - * - * @param [optional] sessionDownload - * A Download object, or null for history downloads. - * @param [optional] aPlacesNode - * The Places node for a history download, or null for session downloads. - * @param [optional] aNewest - * @see onDownloadAdded. Ignored for history downloads. - * @param [optional] aDocumentFragment - * To speed up the appending of multiple elements to the end of the - * list which are coming in a single batch (i.e. invalidateContainer), - * a document fragment may be passed to which the new elements would - * be appended. It's the caller's job to ensure the fragment is merged - * to the richlistbox at the end. - */ - _addDownloadData(sessionDownload, aPlacesNode, aNewest = false, - aDocumentFragment = null) { - let downloadURI = aPlacesNode ? aPlacesNode.uri - : sessionDownload.source.url; - let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI); - if (!shellsForURI) { - shellsForURI = new Set(); - this._downloadElementsShellsForURI.set(downloadURI, shellsForURI); - } - - // When a session download is attached to a shell, we ensure not to keep - // stale metadata around for the corresponding history download. This - // prevents stale state from being used if the view is rebuilt. - // - // Note that we will eagerly load the data in the cache at this point, even - // if we have seen no history download. The case where no history download - // will appear at all is rare enough in normal usage, so we can apply this - // simpler solution rather than keeping a list of cache items to ignore. - if (sessionDownload) { - this._cachedPlacesMetaData.delete(sessionDownload.source.url); - } - - let newOrUpdatedShell = null; - - // Trivial: if there are no shells for this download URI, we always - // need to create one. - let shouldCreateShell = shellsForURI.size == 0; - - // However, if we do have shells for this download uri, there are - // few options: - // 1) There's only one shell and it's for a history download (it has - // no data item). In this case, we update this shell and move it - // if necessary - // 2) There are multiple shells, indicating multiple downloads for - // the same download uri are running. In this case we create - // another shell for the download (so we have one shell for each data - // item). - // - // Note: If a cancelled session download is already in the list, and the - // download is retried, onDownloadAdded is called again for the same - // data item. Thus, we also check that we make sure we don't have a view item - // already. - if (!shouldCreateShell && - sessionDownload && !this._viewItemsForDownloads.has(sessionDownload)) { - // If there's a past-download-only shell for this download-uri with no - // associated data item, use it for the new data item. Otherwise, go ahead - // and create another shell. - shouldCreateShell = true; - for (let shell of shellsForURI) { - if (!shell.sessionDownload) { - shouldCreateShell = false; - shell.sessionDownload = sessionDownload; - newOrUpdatedShell = shell; - this._viewItemsForDownloads.set(sessionDownload, shell); - break; - } - } - } - - if (shouldCreateShell) { - // If we are adding a new history download here, it means there is no - // associated session download, thus we must read the Places metadata, - // because it will not be obscured by the session download. - let historyDownload = null; - if (aPlacesNode) { - let metaData = this._cachedPlacesMetaData.get(aPlacesNode.uri) || - this._getPlacesMetaDataFor(aPlacesNode.uri); - historyDownload = new HistoryDownload(aPlacesNode); - historyDownload.updateFromMetaData(metaData); - } - let shell = new HistoryDownloadElementShell(sessionDownload, - historyDownload); - shell.element._placesNode = aPlacesNode; - newOrUpdatedShell = shell; - shellsForURI.add(shell); - if (sessionDownload) { - this._viewItemsForDownloads.set(sessionDownload, shell); - } - } - else if (aPlacesNode) { - // We are updating information for a history download for which we have - // at least one download element shell already. There are two cases: - // 1) There are one or more download element shells for this source URI, - // each with an associated session download. We update the Places node - // because we may need it later, but we don't need to read the Places - // metadata until the last session download is removed. - // 2) Occasionally, we may receive a duplicate notification for a history - // download with no associated session download. We have exactly one - // download element shell in this case, but the metdata cannot have - // changed, just the reference to the Places node object is different. - // So, we update all the node references and keep the metadata intact. - for (let shell of shellsForURI) { - if (!shell.historyDownload) { - // Create the element to host the metadata when needed. - shell.historyDownload = new HistoryDownload(aPlacesNode); - } - shell.element._placesNode = aPlacesNode; - } - } - - if (newOrUpdatedShell) { - if (aNewest) { - this._richlistbox.insertBefore(newOrUpdatedShell.element, - this._richlistbox.firstChild); - if (!this._lastSessionDownloadElement) { - this._lastSessionDownloadElement = newOrUpdatedShell.element; - } - // Some operations like retrying an history download move an element to - // the top of the richlistbox, along with other session downloads. - // More generally, if a new download is added, should be made visible. - this._richlistbox.ensureElementIsVisible(newOrUpdatedShell.element); - } else if (sessionDownload) { - let before = this._lastSessionDownloadElement ? - this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild; - this._richlistbox.insertBefore(newOrUpdatedShell.element, before); - this._lastSessionDownloadElement = newOrUpdatedShell.element; - } - else { - let appendTo = aDocumentFragment || this._richlistbox; - appendTo.appendChild(newOrUpdatedShell.element); - } - - if (this.searchTerm) { - newOrUpdatedShell.element.hidden = - !newOrUpdatedShell.element._shell.matchesSearchTerm(this.searchTerm); - } - } - - // If aDocumentFragment is defined this is a batch change, so it's up to - // the caller to append the fragment and activate the visible shells. - if (!aDocumentFragment) { - this._ensureVisibleElementsAreActive(); - goUpdateCommand("downloadsCmd_clearDownloads"); - } - }, - - _removeElement: function DPV__removeElement(aElement) { - // If the element was selected exclusively, select its next - // sibling first, if not, try for previous sibling, if any. - if ((aElement.nextSibling || aElement.previousSibling) && - this._richlistbox.selectedItems && - this._richlistbox.selectedItems.length == 1 && - this._richlistbox.selectedItems[0] == aElement) { - this._richlistbox.selectItem(aElement.nextSibling || - aElement.previousSibling); - } - - if (this._lastSessionDownloadElement == aElement) - this._lastSessionDownloadElement = aElement.previousSibling; - - this._richlistbox.removeItemFromSelection(aElement); - this._richlistbox.removeChild(aElement); - this._ensureVisibleElementsAreActive(); - goUpdateCommand("downloadsCmd_clearDownloads"); - }, - - _removeHistoryDownloadFromView: - function DPV__removeHistoryDownloadFromView(aPlacesNode) { - let downloadURI = aPlacesNode.uri; - let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI); - if (shellsForURI) { - for (let shell of shellsForURI) { - if (shell.sessionDownload) { - shell.historyDownload = null; - } - else { - this._removeElement(shell.element); - shellsForURI.delete(shell); - if (shellsForURI.size == 0) - this._downloadElementsShellsForURI.delete(downloadURI); - } - } - } - }, - - _removeSessionDownloadFromView(download) { - let shells = this._downloadElementsShellsForURI - .get(download.source.url); - if (shells.size == 0) - throw new Error("Should have had at leaat one shell for this uri"); - - let shell = this._viewItemsForDownloads.get(download); - if (!shells.has(shell)) - throw new Error("Missing download element shell in shells list for url"); - - // If there's more than one item for this download uri, we can let the - // view item for this this particular data item go away. - // If there's only one item for this download uri, we should only - // keep it if it is associated with a history download. - if (shells.size > 1 || !shell.historyDownload) { - this._removeElement(shell.element); - shells.delete(shell); - if (shells.size == 0) - this._downloadElementsShellsForURI.delete(download.source.url); - } - else { - // We have one download element shell containing both a session download - // and a history download, and we are now removing the session download. - // Previously, we did not use the Places metadata because it was obscured - // by the session download. Since this is no longer the case, we have to - // read the latest metadata before removing the session download. - let url = shell.historyDownload.source.url; - let metaData = this._getPlacesMetaDataFor(url); - shell.historyDownload.updateFromMetaData(metaData); - shell.sessionDownload = null; - // Move it below the session-download items; - if (this._lastSessionDownloadElement == shell.element) { - this._lastSessionDownloadElement = shell.element.previousSibling; - } - else { - let before = this._lastSessionDownloadElement ? - this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild; - this._richlistbox.insertBefore(shell.element, before); - } - } - }, - - _ensureVisibleElementsAreActive: - function DPV__ensureVisibleElementsAreActive() { - if (!this.active || this._ensureVisibleTimer || !this._richlistbox.firstChild) - return; - - this._ensureVisibleTimer = setTimeout(function() { - delete this._ensureVisibleTimer; - if (!this._richlistbox.firstChild) - return; - - let rlbRect = this._richlistbox.getBoundingClientRect(); - let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let nodes = winUtils.nodesFromRect(rlbRect.left, rlbRect.top, - 0, rlbRect.width, rlbRect.height, 0, - true, false); - // nodesFromRect returns nodes in z-index order, and for the same z-index - // sorts them in inverted DOM order, thus starting from the one that would - // be on top. - let firstVisibleNode, lastVisibleNode; - for (let node of nodes) { - if (node.localName === "richlistitem" && node._shell) { - node._shell.ensureActive(); - // The first visible node is the last match. - firstVisibleNode = node; - // While the last visible node is the first match. - if (!lastVisibleNode) - lastVisibleNode = node; - } - } - - // Also activate the first invisible nodes in both boundaries (that is, - // above and below the visible area) to ensure proper keyboard navigation - // in both directions. - let nodeBelowVisibleArea = lastVisibleNode && lastVisibleNode.nextSibling; - if (nodeBelowVisibleArea && nodeBelowVisibleArea._shell) - nodeBelowVisibleArea._shell.ensureActive(); - - let nodeABoveVisibleArea = - firstVisibleNode && firstVisibleNode.previousSibling; - if (nodeABoveVisibleArea && nodeABoveVisibleArea._shell) - nodeABoveVisibleArea._shell.ensureActive(); - }.bind(this), 10); - }, - - _place: "", - get place() this._place, - set place(val) { - // Don't reload everything if we don't have to. - if (this._place == val) { - // XXXmano: places.js relies on this behavior (see Bug 822203). - this.searchTerm = ""; - return val; - } - - this._place = val; - - let history = PlacesUtils.history; - let queries = { }, options = { }; - history.queryStringToQueries(val, queries, { }, options); - if (!queries.value.length) - queries.value = [history.getNewQuery()]; - - let result = history.executeQueries(queries.value, queries.value.length, - options.value); - result.addObserver(this, false); - return val; - }, - - _result: null, - get result() this._result, - set result(val) { - if (this._result == val) - return val; - - if (this._result) { - this._result.removeObserver(this); - this._resultNode.containerOpen = false; - } - - if (val) { - this._result = val; - this._resultNode = val.root; - this._resultNode.containerOpen = true; - this._ensureInitialSelection(); - } - else { - delete this._resultNode; - delete this._result; - } - - return val; - }, - - get selectedNodes() { - return [for (element of this._richlistbox.selectedItems) - if (element._placesNode) - element._placesNode]; - }, - - get selectedNode() { - let selectedNodes = this.selectedNodes; - return selectedNodes.length == 1 ? selectedNodes[0] : null; - }, - - get hasSelection() this.selectedNodes.length > 0, - - containerStateChanged: - function DPV_containerStateChanged(aNode, aOldState, aNewState) { - this.invalidateContainer(aNode) - }, - - invalidateContainer: - function DPV_invalidateContainer(aContainer) { - if (aContainer != this._resultNode) - throw new Error("Unexpected container node"); - if (!aContainer.containerOpen) - throw new Error("Root container for the downloads query cannot be closed"); - - let suppressOnSelect = this._richlistbox.suppressOnSelect; - this._richlistbox.suppressOnSelect = true; - try { - // Remove the invalidated history downloads from the list and unset the - // places node for data downloads. - // Loop backwards since _removeHistoryDownloadFromView may removeChild(). - for (let i = this._richlistbox.childNodes.length - 1; i >= 0; --i) { - let element = this._richlistbox.childNodes[i]; - if (element._placesNode) { - this._removeHistoryDownloadFromView(element._placesNode); - } - } - } - finally { - this._richlistbox.suppressOnSelect = suppressOnSelect; - } - - if (aContainer.childCount > 0) { - let elementsToAppendFragment = document.createDocumentFragment(); - for (let i = 0; i < aContainer.childCount; i++) { - try { - this._addDownloadData(null, aContainer.getChild(i), false, - elementsToAppendFragment); - } - catch(ex) { - Cu.reportError(ex); - } - } - - // _addDownloadData may not add new elements if there were already - // data items in place. - if (elementsToAppendFragment.firstChild) { - this._appendDownloadsFragment(elementsToAppendFragment); - this._ensureVisibleElementsAreActive(); - } - } - - goUpdateDownloadCommands(); - }, - - _appendDownloadsFragment: function DPV__appendDownloadsFragment(aDOMFragment) { - // Workaround multiple reflows hang by removing the richlistbox - // and adding it back when we're done. - - // Hack for bug 836283: reset xbl fields to their old values after the - // binding is reattached to avoid breaking the selection state - let xblFields = new Map(); - for (let [key, value] in Iterator(this._richlistbox)) { - xblFields.set(key, value); - } - - let parentNode = this._richlistbox.parentNode; - let nextSibling = this._richlistbox.nextSibling; - parentNode.removeChild(this._richlistbox); - this._richlistbox.appendChild(aDOMFragment); - parentNode.insertBefore(this._richlistbox, nextSibling); - - for (let [key, value] of xblFields) { - this._richlistbox[key] = value; - } - }, - - nodeInserted: function DPV_nodeInserted(aParent, aPlacesNode) { - this._addDownloadData(null, aPlacesNode); - }, - - nodeRemoved: function DPV_nodeRemoved(aParent, aPlacesNode, aOldIndex) { - this._removeHistoryDownloadFromView(aPlacesNode); - }, - - nodeAnnotationChanged() {}, - nodeIconChanged() {}, - nodeTitleChanged() {}, - nodeKeywordChanged: function() {}, - nodeDateAddedChanged: function() {}, - nodeLastModifiedChanged: function() {}, - nodeHistoryDetailsChanged: function() {}, - nodeTagsChanged: function() {}, - sortingChanged: function() {}, - nodeMoved: function() {}, - nodeURIChanged: function() {}, - batching: function() {}, - - get controller() this._richlistbox.controller, - - get searchTerm() this._searchTerm, - set searchTerm(aValue) { - if (this._searchTerm != aValue) { - for (let element of this._richlistbox.childNodes) { - element.hidden = !element._shell.matchesSearchTerm(aValue); - } - this._ensureVisibleElementsAreActive(); - } - return this._searchTerm = aValue; - }, - - /** - * When the view loads, we want to select the first item. - * However, because session downloads, for which the data is loaded - * asynchronously, always come first in the list, and because the list - * may (or may not) already contain history downloads at that point, it - * turns out that by the time we can select the first item, the user may - * have already started using the view. - * To make things even more complicated, in other cases, the places data - * may be loaded after the session downloads data. Thus we cannot rely on - * the order in which the data comes in. - * We work around this by attempting to select the first element twice, - * once after the places data is loaded and once when the session downloads - * data is done loading. However, if the selection has changed in-between, - * we assume the user has already started using the view and give up. - */ - _ensureInitialSelection: function DPV__ensureInitialSelection() { - // Either they're both null, or the selection has not changed in between. - if (this._richlistbox.selectedItem == this._initiallySelectedElement) { - let firstDownloadElement = this._richlistbox.firstChild; - if (firstDownloadElement != this._initiallySelectedElement) { - // We may be called before _ensureVisibleElementsAreActive, - // or before the download binding is attached. Therefore, ensure the - // first item is activated, and pass the item to the richlistbox - // setters only at a point we know for sure the binding is attached. - firstDownloadElement._shell.ensureActive(); - Services.tm.mainThread.dispatch(function() { - this._richlistbox.selectedItem = firstDownloadElement; - this._richlistbox.currentItem = firstDownloadElement; - this._initiallySelectedElement = firstDownloadElement; - }.bind(this), Ci.nsIThread.DISPATCH_NORMAL); - } - } - }, - - onDataLoadStarting: function() { }, - onDataLoadCompleted: function DPV_onDataLoadCompleted() { - this._ensureInitialSelection(); - }, - - onDownloadAdded(download, newest) { - this._addDownloadData(download, null, newest); - }, - - onDownloadStateChanged(download) { - this._viewItemsForDownloads.get(download).onStateChanged(); - }, - - onDownloadChanged(download) { - this._viewItemsForDownloads.get(download).onChanged(); - }, - - onDownloadRemoved(download) { - this._removeSessionDownloadFromView(download); - }, - - supportsCommand: function DPV_supportsCommand(aCommand) { - if (DOWNLOAD_VIEW_SUPPORTED_COMMANDS.indexOf(aCommand) != -1) { - // The clear-downloads command may be performed by the toolbar-button, - // which can be focused on OS X. Thus enable this command even if the - // richlistbox is not focused. - // For other commands, be prudent and disable them unless the richlistview - // is focused. It's important to make the decision here rather than in - // isCommandEnabled. Otherwise our controller may "steal" commands from - // other controls in the window (see goUpdateCommand & - // getControllerForCommand). - if (document.activeElement == this._richlistbox || - aCommand == "downloadsCmd_clearDownloads") { - return true; - } - } - return false; - }, - - isCommandEnabled: function DPV_isCommandEnabled(aCommand) { - switch (aCommand) { - case "cmd_copy": - return this._richlistbox.selectedItems.length > 0; - case "cmd_selectAll": - return true; - case "cmd_paste": - return this._canDownloadClipboardURL(); - case "downloadsCmd_clearDownloads": - return this._canClearDownloads(); - default: - return Array.every(this._richlistbox.selectedItems, function(element) { - return element._shell.isCommandEnabled(aCommand); - }); - } - }, - - _canClearDownloads: function DPV__canClearDownloads() { - // Downloads can be cleared if there's at least one removable download in - // the list (either a history download or a completed session download). - // Because history downloads are always removable and are listed after the - // session downloads, check from bottom to top. - for (let elt = this._richlistbox.lastChild; elt; elt = elt.previousSibling) { - // Stopped, paused, and failed downloads with partial data are removed. - let download = elt._shell.download; - if (download.stopped && !(download.canceled && download.hasPartialData)) { - return true; - } - } - return false; - }, - - _copySelectedDownloadsToClipboard: - function DPV__copySelectedDownloadsToClipboard() { - let urls = [for (element of this._richlistbox.selectedItems) - element._shell.download.source.url]; - - Cc["@mozilla.org/widget/clipboardhelper;1"] - .getService(Ci.nsIClipboardHelper) - .copyString(urls.join("\n"), document); - }, - - _getURLFromClipboardData: function DPV__getURLFromClipboardData() { - let trans = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - trans.init(null); - - let flavors = ["text/x-moz-url", "text/unicode"]; - flavors.forEach(trans.addDataFlavor); - - Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard); - - // Getting the data or creating the nsIURI might fail. - try { - let data = {}; - trans.getAnyTransferData({}, data, {}); - let [url, name] = data.value.QueryInterface(Ci.nsISupportsString) - .data.split("\n"); - if (url) - return [NetUtil.newURI(url, null, null).spec, name]; - } - catch(ex) { } - - return ["", ""]; - }, - - _canDownloadClipboardURL: function DPV__canDownloadClipboardURL() { - let [url, name] = this._getURLFromClipboardData(); - return url != ""; - }, - - _downloadURLFromClipboard: function DPV__downloadURLFromClipboard() { - let [url, name] = this._getURLFromClipboardData(); - let browserWin = RecentWindow.getMostRecentBrowserWindow(); - let initiatingDoc = browserWin ? browserWin.document : document; - DownloadURL(url, name, initiatingDoc); - }, - - doCommand: function DPV_doCommand(aCommand) { - // Commands may be invoked with keyboard shortcuts even if disabled. - if (!this.isCommandEnabled(aCommand)) { - return; - } - switch (aCommand) { - case "cmd_copy": - this._copySelectedDownloadsToClipboard(); - break; - case "cmd_selectAll": - this._richlistbox.selectAll(); - break; - case "cmd_paste": - this._downloadURLFromClipboard(); - break; - case "downloadsCmd_clearDownloads": - this._downloadsData.removeFinished(); - if (this.result) { - Cc["@mozilla.org/browser/download-history;1"] - .getService(Ci.nsIDownloadHistory) - .removeAllDownloads(); - } - // There may be no selection or focus change as a result - // of these change, and we want the command updated immediately. - goUpdateCommand("downloadsCmd_clearDownloads"); - break; - default: { - // Cloning the nodelist into an array to get a frozen list of selected items. - // Otherwise, the selectedItems nodelist is live and doCommand may alter the - // selection while we are trying to do one particular action, like removing - // items from history. - let selectedElements = [...this._richlistbox.selectedItems]; - for (let element of selectedElements) { - element._shell.doCommand(aCommand); - } - } - } - }, - - onEvent: function() { }, - - onContextMenu: function DPV_onContextMenu(aEvent) - { - let element = this._richlistbox.selectedItem; - if (!element || !element._shell) - return false; - - // Set the state attribute so that only the appropriate items are displayed. - let contextMenu = document.getElementById("downloadsContextMenu"); - let download = element._shell.download; - contextMenu.setAttribute("state", - DownloadsCommon.stateOfDownload(download)); - - if (!download.stopped) { - // The hasPartialData property of a download may change at any time after - // it has started, so ensure we update the related command now. - goUpdateCommand("downloadsCmd_pauseResume"); - } - return true; - }, - - onKeyPress: function DPV_onKeyPress(aEvent) { - let selectedElements = this._richlistbox.selectedItems; - if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) { - // In the content tree, opening bookmarks by pressing return is only - // supported when a single item is selected. To be consistent, do the - // same here. - if (selectedElements.length == 1) { - let element = selectedElements[0]; - if (element._shell) - element._shell.doDefaultCommand(); - } - } - else if (aEvent.charCode == " ".charCodeAt(0)) { - // Pause/Resume every selected download - for (let element of selectedElements) { - if (element._shell.isCommandEnabled("downloadsCmd_pauseResume")) - element._shell.doCommand("downloadsCmd_pauseResume"); - } - } - }, - - onDoubleClick: function DPV_onDoubleClick(aEvent) { - if (aEvent.button != 0) - return; - - let selectedElements = this._richlistbox.selectedItems; - if (selectedElements.length != 1) - return; - - let element = selectedElements[0]; - if (element._shell) - element._shell.doDefaultCommand(); - }, - - onScroll: function DPV_onScroll() { - this._ensureVisibleElementsAreActive(); - }, - - onSelect: function DPV_onSelect() { - goUpdateDownloadCommands(); - - let selectedElements = this._richlistbox.selectedItems; - for (let elt of selectedElements) { - if (elt._shell) - elt._shell.onSelect(); - } - }, - - onDragStart: function DPV_onDragStart(aEvent) { - // TODO Bug 831358: Support d&d for multiple selection. - // For now, we just drag the first element. - let selectedItem = this._richlistbox.selectedItem; - if (!selectedItem) - return; - - let targetPath = selectedItem._shell.download.target.path; - if (!targetPath) { - return; - } - - // We must check for existence synchronously because this is a DOM event. - let file = new FileUtils.File(targetPath); - if (!file.exists()) - return; - - let dt = aEvent.dataTransfer; - dt.mozSetDataAt("application/x-moz-file", file, 0); - let url = Services.io.newFileURI(file).spec; - dt.setData("text/uri-list", url); - dt.setData("text/plain", url); - dt.effectAllowed = "copyMove"; - dt.addElement(selectedItem); - }, - - onDragOver: function DPV_onDragOver(aEvent) { - let types = aEvent.dataTransfer.types; - if (types.contains("text/uri-list") || - types.contains("text/x-moz-url") || - types.contains("text/plain")) { - aEvent.preventDefault(); - } - }, - - onDrop: function DPV_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 = Services.droppedLinkHandler.dropLinks(aEvent); - if (!links.length) - return; - let browserWin = RecentWindow.getMostRecentBrowserWindow(); - let initiatingDoc = browserWin ? browserWin.document : document; - for (let link of links) { - if (link.url.startsWith("about:")) - continue; - DownloadURL(link.url, link.name, initiatingDoc); - } - } -}; - -for (let methodName of ["load", "applyFilter", "selectNode", "selectItems"]) { - DownloadsPlacesView.prototype[methodName] = function() { - throw new Error("|" + methodName + "| is not implemented by the downloads view."); - } -} - -function goUpdateDownloadCommands() { - for (let command of DOWNLOAD_VIEW_SUPPORTED_COMMANDS) { - goUpdateCommand(command); - } -} diff --git a/components/downloads/content/allDownloadsViewOverlay.xul b/components/downloads/content/allDownloadsViewOverlay.xul deleted file mode 100644 index 4e9bfd1..0000000 --- a/components/downloads/content/allDownloadsViewOverlay.xul +++ /dev/null @@ -1,119 +0,0 @@ -<?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/allDownloadsViewOverlay.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_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="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> diff --git a/components/downloads/content/contentAreaDownloadsView.css b/components/downloads/content/contentAreaDownloadsView.css deleted file mode 100644 index abaae1f..0000000 --- a/components/downloads/content/contentAreaDownloadsView.css +++ /dev/null @@ -1,11 +0,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/. */ - -#downloadsListEmptyDescription { - display: none; -} - -#downloadsRichListBox:empty + #downloadsListEmptyDescription { - display: -moz-box; -} diff --git a/components/downloads/content/contentAreaDownloadsView.js b/components/downloads/content/contentAreaDownloadsView.js deleted file mode 100644 index fbb18ab..0000000 --- a/components/downloads/content/contentAreaDownloadsView.js +++ /dev/null @@ -1,15 +0,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/. */ - -Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); - -var ContentAreaDownloadsView = { - init: function CADV_init() { - let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox")); - // Do not display the Places downloads in private windows - if (!PrivateBrowsingUtils.isWindowPrivate(window)) { - view.place = "place:transition=7&sort=4"; - } - } -}; diff --git a/components/downloads/content/contentAreaDownloadsView.xul b/components/downloads/content/contentAreaDownloadsView.xul deleted file mode 100644 index a91de1e..0000000 --- a/components/downloads/content/contentAreaDownloadsView.xul +++ /dev/null @@ -1,45 +0,0 @@ -<?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;"/> - </stack> - <commandset id="downloadCommands"/> - <menupopup id="downloadsContextMenu"/> -</window> diff --git a/components/downloads/content/download.css b/components/downloads/content/download.css deleted file mode 100644 index 7412fa7..0000000 --- a/components/downloads/content/download.css +++ /dev/null @@ -1,45 +0,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/. */ - -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:-moz-any( [state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */ - > .downloadTypeIcon:not(.blockedIcon), - -.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */) - > .downloadTypeIcon.blockedIcon, - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="5"], /* Starting (queued) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="7"]) /* Scanning */) - > vbox > .downloadProgress, - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="5"], /* Starting (queued) */ - [state="0"], /* Downloading */ - [state="4"]) /* Paused */) - > .downloadCancel, - -.download-state[state]:not(:-moz-any([state="2"], /* Failed */ - [state="3"]) /* Canceled */) - > .downloadRetry, - -.download-state:not( [state="1"] /* Finished */) - > .downloadShow -{ - display: none; -} diff --git a/components/downloads/content/download.xml b/components/downloads/content/download.xml deleted file mode 100644 index 542901b..0000000 --- a/components/downloads/content/download.xml +++ /dev/null @@ -1,188 +0,0 @@ -<?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" - align="center" - onclick="DownloadsView.onDownloadClick(event);"> - <xul:image class="downloadTypeIcon" - validate="always" - xbl:inherits="src=image"/> - <xul:image class="downloadTypeIcon blockedIcon"/> - <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="downloadDisplayName" - 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"/> - <xul:description class="downloadDetails" - crop="end" - xbl:inherits="value=status,tooltiptext=statusTip"/> - </xul:vbox> - <xul:stack> - <xul:button class="downloadButton downloadCancel" - tooltiptext="&cmd.cancel.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/> - <xul:button class="downloadButton downloadRetry" - tooltiptext="&cmd.retry.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/> - <xul:button class="downloadButton downloadShow" -#ifdef XP_MACOSX - tooltiptext="&cmd.showMac.label;" -#else - tooltiptext="&cmd.show.label;" -#endif - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/> - </xul:stack> - </content> - </binding> - - <binding id="download-in-progress" - extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> - <content orient="horizontal" - align="center" - onclick="DownloadsView.onDownloadClick(event);"> - <xul:image class="downloadTypeIcon" - validate="always" - xbl:inherits="src=image"/> - <xul:image class="downloadTypeIcon blockedIcon"/> - <xul:vbox pack="center" - flex="1" - class="downloadContainer" - style="width: &downloadDetails.width;"> - <xul:description class="downloadDisplayName" - crop="center" - style="min-width: &downloadsSummary.minWidth2;" - xbl:inherits="value=displayName,tooltiptext=extendedDisplayNameTip"/> - <xul:progressmeter anonid="progressmeter" - class="downloadProgress" - min="0" - max="100" - xbl:inherits="mode=progressmode,value=progress"/> - <xul:description class="downloadDetails" - crop="end" - xbl:inherits="value=status,tooltiptext=statusTip"/> - </xul:vbox> - <xul:stack> - <xul:button class="downloadButton downloadCancel" - tooltiptext="&cmd.cancel.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/> - <xul:button class="downloadButton downloadRetry" - tooltiptext="&cmd.retry.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/> - <xul:button class="downloadButton downloadShow" - tooltiptext="&cmd.show.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/> - </xul:stack> - </content> - </binding> - - <binding id="download-full-ui" - extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> - <resources> - <stylesheet src="chrome://browser/content/downloads/download.css"/> - </resources> - - <content orient="horizontal" align="center"> - <xul:image class="downloadTypeIcon" - validate="always" - xbl:inherits="src=image"/> - <xul:image class="downloadTypeIcon blockedIcon"/> - <xul:vbox pack="center" flex="1"> - <xul:description class="downloadDisplayName" - crop="center" - xbl:inherits="value=displayName,tooltiptext=displayName"/> - <xul:progressmeter anonid="progressmeter" - class="downloadProgress" - min="0" - max="100" - xbl:inherits="mode=progressmode,value=progress"/> - <xul:description class="downloadDetails" - style="width: &downloadDetails.width;" - crop="end" - xbl:inherits="value=status,tooltiptext=statusTip"/> - </xul:vbox> - - <xul:button class="downloadButton downloadCancel" - tooltiptext="&cmd.cancel.label;" - oncommand="goDoCommand('downloadsCmd_cancel')"/> - <xul:button class="downloadButton downloadRetry" - tooltiptext="&cmd.retry.label;" - oncommand="goDoCommand('downloadsCmd_retry')"/> - <xul:button class="downloadButton downloadShow" -#ifdef XP_MACOSX - tooltiptext="&cmd.showMac.label;" -#else - tooltiptext="&cmd.show.label;" -#endif - oncommand="goDoCommand('downloadsCmd_show')"/> - - </content> - </binding> - - <binding id="download-in-progress-full-ui" - extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> - <resources> - <stylesheet src="chrome://browser/content/downloads/download.css"/> - </resources> - - <content orient="horizontal" align="center"> - <xul:image class="downloadTypeIcon" - validate="always" - xbl:inherits="src=image"/> - <xul:image class="downloadTypeIcon blockedIcon"/> - <xul:vbox pack="center" flex="1"> - <xul:description class="downloadDisplayName" - crop="end" - xbl:inherits="value=extendedDisplayName,tooltiptext=extendedDisplayNameTip"/> - <xul:progressmeter anonid="progressmeter" - class="downloadProgress" - min="0" - max="100" - xbl:inherits="mode=progressmode,value=progress"/> - <xul:description class="downloadDetails" - style="width: &downloadDetails.width;" - crop="end" - xbl:inherits="value=status,tooltiptext=statusTip"/> - </xul:vbox> - - <xul:button class="downloadButton downloadCancel" - tooltiptext="&cmd.cancel.label;" - oncommand="goDoCommand('downloadsCmd_cancel')"/> - <xul:button class="downloadButton downloadRetry" - tooltiptext="&cmd.retry.label;" - oncommand="goDoCommand('downloadsCmd_retry')"/> - <xul:button class="downloadButton downloadShow" - tooltiptext="&cmd.show.label;" - oncommand="goDoCommand('downloadsCmd_show')"/> - - </content> - </binding> -</bindings> diff --git a/components/downloads/content/downloads.css b/components/downloads/content/downloads.css deleted file mode 100644 index 825db68..0000000 --- a/components/downloads/content/downloads.css +++ /dev/null @@ -1,132 +0,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/. */ - -/*** Download items ***/ - -richlistitem[type="download"] { - -moz-binding: url('chrome://browser/content/downloads/download.xml#download'); -} - -richlistitem[type="download"]:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="5"], /* Starting (queued) */ - [state="7"]) /* Scanning */ -{ - -moz-binding: url('chrome://browser/content/downloads/download.xml#download-in-progress'); -} - -richlistitem[type="download"]:not([selected]) button { - /* Only focus buttons in the selected item. */ - -moz-user-focus: none; -} - -/*** Visibility of controls inside download items ***/ - -.download-state:-moz-any( [state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */ - .downloadTypeIcon:not(.blockedIcon), - -.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */) - .downloadTypeIcon.blockedIcon, - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="5"], /* Starting (queued) */ - [state="7"]) /* Scanning */) - .downloadProgress, - -.download-state:not( [state="0"] /* Downloading */) - .downloadPauseMenuItem, - -.download-state:not( [state="4"] /* Paused */) - .downloadResumeMenuItem, - -.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"] /* Scanning */ .downloadCommandsSeparator - -{ - display: none; -} - -/*** Visibility of download buttons and indicator controls. ***/ - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="5"]) /* Starting (queued) */) - .downloadCancel, - -.download-state:not(:-moz-any([state="2"], /* Failed */ - [state="3"]) /* Canceled */) - .downloadRetry, - -.download-state:not( [state="1"] /* Finished */) - .downloadShow, - -#downloads-indicator:-moz-any([progress], - [counter], - [paused]) #downloads-indicator-icon, - -#downloads-indicator:not(:-moz-any([progress], - [counter], - [paused])) - #downloads-indicator-progress-area - -{ - visibility: hidden; -} - -.download-state[state="1"]:not([exists]) .downloadShow -{ - display: none; -} - -#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress, -#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails, -#downloadsFooter[showingsummary] > #downloadsHistory, -#downloadsFooter:not([showingsummary]) > #downloadsSummary -{ - display: none; -} - -/* Hacks for toolbar full and text modes, until bug 573329 removes them */ - -toolbar[mode="text"] > #downloads-indicator { - display: -moz-box; - -moz-box-orient: vertical; - -moz-box-pack: center; -} - -toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-text { - -moz-box-ordinal-group: 1; -} - -toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-icon { - display: -moz-box; - -moz-box-ordinal-group: 2; - visibility: collapse; -} diff --git a/components/downloads/content/downloads.js b/components/downloads/content/downloads.js deleted file mode 100644 index ee1c690..0000000 --- a/components/downloads/content/downloads.js +++ /dev/null @@ -1,1614 +0,0 @@ -/* -*- Mode: C++; 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/. */ - -var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", - "resource:///modules/DownloadsCommon.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI", - "resource:///modules/DownloadsViewUI.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", - "resource://gre/modules/PlacesUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Services", - "resource://gre/modules/Services.jsm"); - -/** - * Handles the Downloads panel user interface for each browser window. - * - * This file includes the following constructors and global objects: - * - * DownloadsPanel - * Main entry point for the downloads panel interface. - * - * DownloadsOverlayLoader - * Allows loading the downloads panel and the status indicator interfaces on - * demand, to improve startup performance. - * - * DownloadsView - * Builds and updates the downloads list widget, responding to changes in the - * download state and real-time data. In addition, handles part of the user - * interaction events raised by the downloads list widget. - * - * DownloadsViewItem - * Builds and updates a single item in the downloads list widget, responding to - * changes in the download state and real-time data. - * - * DownloadsViewController - * Handles part of the user interaction events raised by the downloads list - * widget, in particular the "commands" that apply to multiple items, and - * dispatches the commands that apply to individual items. - * - * DownloadsViewItemController - * Handles all the user interaction events, in particular the "commands", - * related to a single item in the downloads list widgets. - */ - -/** - * A few words on focus and focusrings - * - * We do quite a few hacks in the Downloads Panel for focusrings. In fact, we - * basically suppress most if not all XUL-level focusrings, and style/draw - * them ourselves (using :focus instead of -moz-focusring). There are a few - * reasons for this: - * - * 1) Richlists on OSX don't have focusrings; instead, they are shown as - * selected. This makes for some ambiguity when we have a focused/selected - * item in the list, and the mouse is hovering a completed download (which - * highlights). - * 2) Windows doesn't show focusrings until after the first time that tab is - * pressed (and by then you're focusing the second item in the panel). - * 3) Richlistbox sets -moz-focusring even when we select it with a mouse. - * - * In general, the desired behaviour is to focus the first item after pressing - * tab/down, and show that focus with a ring. Then, if the mouse moves over - * the panel, to hide that focus ring; essentially resetting us to the state - * before pressing the key. - * - * We end up capturing the tab/down key events, and preventing their default - * behaviour. We then set a "keyfocus" attribute on the panel, which allows - * us to draw a ring around the currently focused element. If the panel is - * closed or the mouse moves over the panel, we remove the attribute. - */ - -"use strict"; - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsPanel - -/** - * Main entry point for the downloads panel interface. - */ -const DownloadsPanel = { - ////////////////////////////////////////////////////////////////////////////// - //// Initialization and termination - - /** - * Internal state of the downloads panel, based on one of the kState - * constants. This is not the same state as the XUL panel element. - */ - _state: 0, - - /** The panel is not linked to downloads data yet. */ - get kStateUninitialized() 0, - /** This object is linked to data, but the panel is invisible. */ - get kStateHidden() 1, - /** The panel will be shown as soon as possible. */ - get kStateWaitingData() 2, - /** The panel is almost shown - we're just waiting to get a handle on the - anchor. */ - get kStateWaitingAnchor() 3, - /** The panel is open. */ - get kStateShown() 4, - - /** - * Location of the panel overlay. - */ - get kDownloadsOverlay() - "chrome://browser/content/downloads/downloadsOverlay.xul", - - /** - * Starts loading the download data in background, without opening the panel. - * Use showPanel instead to load the data and open the panel at the same time. - * - * @param aCallback - * Called when initialization is complete. - */ - initialize: function DP_initialize(aCallback) - { - DownloadsCommon.log("Attempting to initialize DownloadsPanel for a window."); - if (this._state != this.kStateUninitialized) { - DownloadsCommon.log("DownloadsPanel is already initialized."); - DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay, - aCallback); - return; - } - this._state = this.kStateHidden; - - window.addEventListener("unload", this.onWindowUnload, false); - - // Ensure that the Download Manager service is running. This resumes - // active downloads if required. If there are downloads to be shown in the - // panel, starting the service will make us load their data asynchronously. - DownloadsCommon.initializeAllDataLinks(); - - - // Now that data loading has eventually started, load the required XUL - // elements and initialize our views. - DownloadsCommon.log("Ensuring DownloadsPanel overlay loaded."); - DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay, - function DP_I_callback() { - DownloadsViewController.initialize(); - DownloadsCommon.log("Attaching DownloadsView..."); - DownloadsCommon.getData(window).addView(DownloadsView); - DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit) - .addView(DownloadsSummary); - DownloadsCommon.log("DownloadsView attached - the panel for this window", - "should now see download items come in."); - DownloadsPanel._attachEventListeners(); - DownloadsCommon.log("DownloadsPanel initialized."); - aCallback(); - }); - }, - - /** - * Closes the downloads panel and frees the internal resources related to the - * downloads. The downloads panel can be reopened later, even after this - * function has been called. - */ - terminate: function DP_terminate() - { - DownloadsCommon.log("Attempting to terminate DownloadsPanel for a window."); - if (this._state == this.kStateUninitialized) { - DownloadsCommon.log("DownloadsPanel was never initialized. Nothing to do."); - return; - } - - window.removeEventListener("unload", this.onWindowUnload, false); - - // Ensure that the panel is closed before shutting down. - this.hidePanel(); - - DownloadsViewController.terminate(); - DownloadsCommon.getData(window).removeView(DownloadsView); - DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit) - .removeView(DownloadsSummary); - this._unattachEventListeners(); - - this._state = this.kStateUninitialized; - - DownloadsSummary.active = false; - DownloadsCommon.log("DownloadsPanel terminated."); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Panel interface - - /** - * Main panel element in the browser window, or null if the panel overlay - * hasn't been loaded yet. - */ - get panel() - { - // If the downloads panel overlay hasn't loaded yet, just return null - // without resetting this.panel. - let downloadsPanel = document.getElementById("downloadsPanel"); - if (!downloadsPanel) - return null; - - delete this.panel; - return this.panel = downloadsPanel; - }, - - /** - * Starts opening the downloads panel interface, anchored to the downloads - * button of the browser window. The list of downloads to display is - * initialized the first time this method is called, and the panel is shown - * only when data is ready. - */ - showPanel: function DP_showPanel() - { - DownloadsCommon.log("Opening the downloads panel."); - - if (this.isPanelShowing) { - DownloadsCommon.log("Panel is already showing - focusing instead."); - this._focusPanel(); - return; - } - - this.initialize(function DP_SP_callback() { - // Delay displaying the panel because this function will sometimes be - // called while another window is closing (like the window for selecting - // whether to save or open the file), and that would cause the panel to - // close immediately. - setTimeout(function () DownloadsPanel._openPopupIfDataReady(), 0); - }.bind(this)); - - DownloadsCommon.log("Waiting for the downloads panel to appear."); - this._state = this.kStateWaitingData; - }, - - /** - * Hides the downloads panel, if visible, but keeps the internal state so that - * the panel can be reopened quickly if required. - */ - hidePanel: function DP_hidePanel() - { - DownloadsCommon.log("Closing the downloads panel."); - - if (!this.isPanelShowing) { - DownloadsCommon.log("Downloads panel is not showing - nothing to do."); - return; - } - - this.panel.hidePopup(); - - // Ensure that we allow the panel to be reopened. Note that, if the popup - // was open, then the onPopupHidden event handler has already updated the - // current state, otherwise we must update the state ourselves. - this._state = this.kStateHidden; - DownloadsCommon.log("Downloads panel is now closed."); - }, - - /** - * Indicates whether the panel is shown or will be shown. - */ - get isPanelShowing() - { - return this._state == this.kStateWaitingData || - this._state == this.kStateWaitingAnchor || - this._state == this.kStateShown; - }, - - /** - * Returns whether the user has started keyboard navigation. - */ - get keyFocusing() - { - return this.panel.hasAttribute("keyfocus"); - }, - - /** - * Set to true if the user has started keyboard navigation, and we should be - * showing focusrings in the panel. Also adds a mousemove event handler to - * the panel which disables keyFocusing. - */ - set keyFocusing(aValue) - { - if (aValue) { - this.panel.setAttribute("keyfocus", "true"); - this.panel.addEventListener("mousemove", this); - } else { - this.panel.removeAttribute("keyfocus"); - this.panel.removeEventListener("mousemove", this); - } - return aValue; - }, - - /** - * Handles the mousemove event for the panel, which disables focusring - * visualization. - */ - handleEvent: function DP_handleEvent(aEvent) - { - if (aEvent.type == "mousemove") { - this.keyFocusing = false; - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Callback functions from DownloadsView - - /** - * Called after data loading finished. - */ - onViewLoadCompleted: function DP_onViewLoadCompleted() - { - this._openPopupIfDataReady(); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// User interface event functions - - onWindowUnload: function DP_onWindowUnload() - { - // This function is registered as an event listener, we can't use "this". - DownloadsPanel.terminate(); - }, - - onPopupShown: function DP_onPopupShown(aEvent) - { - // Ignore events raised by nested popups. - if (aEvent.target != aEvent.currentTarget) { - return; - } - - DownloadsCommon.log("Downloads panel has shown."); - this._state = this.kStateShown; - - // Since at most one popup is open at any given time, we can set globally. - DownloadsCommon.getIndicatorData(window).attentionSuppressed = true; - - // Ensure that the first item is selected when the panel is focused. - if (DownloadsView.richListBox.itemCount > 0 && - DownloadsView.richListBox.selectedIndex == -1) { - DownloadsView.richListBox.selectedIndex = 0; - } - - this._focusPanel(); - }, - - onPopupHidden: function DP_onPopupHidden(aEvent) - { - // Ignore events raised by nested popups. - if (aEvent.target != aEvent.currentTarget) { - return; - } - - DownloadsCommon.log("Downloads panel has hidden."); - - // Removes the keyfocus attribute so that we stop handling keyboard - // navigation. - this.keyFocusing = false; - - // Since at most one popup is open at any given time, we can set globally. - DownloadsCommon.getIndicatorData(window).attentionSuppressed = false; - - // Allow the anchor to be hidden. - DownloadsButton.releaseAnchor(); - - // Allow the panel to be reopened. - this._state = this.kStateHidden; - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Related operations - - /** - * Shows or focuses the user interface dedicated to downloads history. - */ - showDownloadsHistory: function DP_showDownloadsHistory() - { - DownloadsCommon.log("Showing download history."); - // Hide the panel before showing another window, otherwise focus will return - // to the browser window when the panel closes automatically. - this.hidePanel(); - - BrowserDownloadsUI(); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Internal functions - - /** - * Attach event listeners to a panel element. These listeners should be - * removed in _unattachEventListeners. This is called automatically after the - * panel has successfully loaded. - */ - _attachEventListeners: function DP__attachEventListeners() - { - // Handle keydown to support accel-V. - this.panel.addEventListener("keydown", this._onKeyDown.bind(this), false); - // Handle keypress to be able to preventDefault() events before they reach - // the richlistbox, for keyboard navigation. - this.panel.addEventListener("keypress", this._onKeyPress.bind(this), false); - }, - - /** - * Unattach event listeners that were added in _attachEventListeners. This - * is called automatically on panel termination. - */ - _unattachEventListeners: function DP__unattachEventListeners() - { - this.panel.removeEventListener("keydown", this._onKeyDown.bind(this), - false); - this.panel.removeEventListener("keypress", this._onKeyPress.bind(this), - false); - }, - - _onKeyPress: function DP__onKeyPress(aEvent) - { - // Handle unmodified keys only. - if (aEvent.altKey || aEvent.ctrlKey || aEvent.shiftKey || aEvent.metaKey) { - return; - } - - let richListBox = DownloadsView.richListBox; - - // If the user has pressed the tab, up, or down cursor key, start keyboard - // navigation, thus enabling focusrings in the panel. Keyboard navigation - // is automatically disabled if the user moves the mouse on the panel, or - // if the panel is closed. - if ((aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_TAB || - aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP || - aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) && - !this.keyFocusing) { - this.keyFocusing = true; - // Ensure there's a selection, we will show the focus ring around it and - // prevent the richlistbox from changing the selection. - if (DownloadsView.richListBox.selectedIndex == -1) - DownloadsView.richListBox.selectedIndex = 0; - aEvent.preventDefault(); - return; - } - - if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) { - // If the last element in the list is selected, or the footer is already - // focused, focus the footer. - if (richListBox.selectedItem === richListBox.lastChild || - document.activeElement.parentNode.id === "downloadsFooter") { - DownloadsFooter.focus(); - aEvent.preventDefault(); - return; - } - } - - // Pass keypress events to the richlistbox view when it's focused. - if (document.activeElement === richListBox) { - DownloadsView.onDownloadKeyPress(aEvent); - } - }, - - /** - * Keydown listener that listens for the keys to start key focusing, as well - * as the the accel-V "paste" event, which initiates a file download if the - * pasted item can be resolved to a URI. - */ - _onKeyDown: function DP__onKeyDown(aEvent) - { - // If the footer is focused and the downloads list has at least 1 element - // in it, focus the last element in the list when going up. - if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP && - document.activeElement.parentNode.id === "downloadsFooter" && - DownloadsView.richListBox.firstChild) { - DownloadsView.richListBox.focus(); - DownloadsView.richListBox.selectedItem = DownloadsView.richListBox.lastChild; - aEvent.preventDefault(); - return; - } - - let pasting = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_V && -#ifdef XP_MACOSX - aEvent.metaKey; -#else - aEvent.ctrlKey; -#endif - - if (!pasting) { - return; - } - - DownloadsCommon.log("Received a paste event."); - - let trans = Cc["@mozilla.org/widget/transferable;1"] - .createInstance(Ci.nsITransferable); - trans.init(null); - let flavors = ["text/x-moz-url", "text/unicode"]; - flavors.forEach(trans.addDataFlavor); - Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard); - // Getting the data or creating the nsIURI might fail - try { - let data = {}; - trans.getAnyTransferData({}, data, {}); - let [url, name] = data.value - .QueryInterface(Ci.nsISupportsString) - .data - .split("\n"); - if (!url) { - return; - } - - let uri = NetUtil.newURI(url); - DownloadsCommon.log("Pasted URL seems valid. Starting download."); - DownloadURL(uri.spec, name, document); - } catch (ex) {} - }, - - /** - * Move focus to the main element in the downloads panel, unless another - * element in the panel is already focused. - */ - _focusPanel: function DP_focusPanel() - { - // We may be invoked while the panel is still waiting to be shown. - if (this._state != this.kStateShown) { - return; - } - - let element = document.commandDispatcher.focusedElement; - while (element && element != this.panel) { - element = element.parentNode; - } - if (!element) { - if (DownloadsView.richListBox.itemCount > 0) { - DownloadsView.richListBox.focus(); - } else { - DownloadsFooter.focus(); - } - } - }, - - /** - * Opens the downloads panel when data is ready to be displayed. - */ - _openPopupIfDataReady: function DP_openPopupIfDataReady() - { - // We don't want to open the popup if we already displayed it, or if we are - // still loading data. - if (this._state != this.kStateWaitingData || DownloadsView.loading) { - return; - } - - this._state = this.kStateWaitingAnchor; - - // Ensure the anchor is visible. If that is not possible, show the panel - // anchored to the top area of the window, near the default anchor position. - DownloadsButton.getAnchor(function DP_OPIDR_callback(aAnchor) { - // If somehow we've switched states already (by getting a panel hiding - // event before an overlay is loaded, for example), bail out. - if (this._state != this.kStateWaitingAnchor) - return; - - // At this point, if the window is minimized, opening the panel could fail - // without any notification, and there would be no way to either open or - // close the panel any more. To prevent this, check if the window is - // minimized and in that case force the panel to the closed state. - if (window.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED) { - DownloadsButton.releaseAnchor(); - this._state = this.kStateHidden; - return; - } - - // When the panel is opened, we check if the target files of visible items - // still exist, and update the allowed items interactions accordingly. We - // do these checks on a background thread, and don't prevent the panel to - // be displayed while these checks are being performed. - for (let viewItem of DownloadsView._visibleViewItems.values()) { - viewItem.download.refresh().catch(Cu.reportError); - } - - if (aAnchor) { - DownloadsCommon.log("Opening downloads panel popup."); - this.panel.openPopup(aAnchor, "bottomcenter topright", 0, 0, false, - null); - } else { - DownloadsCommon.error("We can't find the anchor! Failure case - opening", - "downloads panel on TabsToolbar. We should never", - "get here!"); - Components.utils.reportError( - "Downloads button cannot be found"); - } - }.bind(this)); - } -}; - -XPCOMUtils.defineConstant(this, "DownloadsPanel", DownloadsPanel); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsOverlayLoader - -/** - * Allows loading the downloads panel and the status indicator interfaces on - * demand, to improve startup performance. - */ -const DownloadsOverlayLoader = { - /** - * We cannot load two overlays at the same time, thus we use a queue of - * pending load requests. - */ - _loadRequests: [], - - /** - * True while we are waiting for an overlay to be loaded. - */ - _overlayLoading: false, - - /** - * This object has a key for each overlay URI that is already loaded. - */ - _loadedOverlays: {}, - - /** - * Loads the specified overlay and invokes the given callback when finished. - * - * @param aOverlay - * String containing the URI of the overlay to load in the current - * window. If this overlay has already been loaded using this - * function, then the overlay is not loaded again. - * @param aCallback - * Invoked when loading is completed. If the overlay is already - * loaded, the function is called immediately. - */ - ensureOverlayLoaded: function DOL_ensureOverlayLoaded(aOverlay, aCallback) - { - // The overlay is already loaded, invoke the callback immediately. - if (aOverlay in this._loadedOverlays) { - aCallback(); - return; - } - - // The callback will be invoked when loading is finished. - this._loadRequests.push({ overlay: aOverlay, callback: aCallback }); - if (this._overlayLoading) { - return; - } - - function DOL_EOL_loadCallback() { - this._overlayLoading = false; - this._loadedOverlays[aOverlay] = true; - - // Loading the overlay causes all the persisted XUL attributes to be - // reapplied, including "iconsize" on the toolbars. Until bug 640158 is - // fixed, we must recalculate the correct "iconsize" attributes manually. - retrieveToolbarIconsizesFromTheme(); - - this.processPendingRequests(); - } - - this._overlayLoading = true; - DownloadsCommon.log("Loading overlay ", aOverlay); - document.loadOverlay(aOverlay, DOL_EOL_loadCallback.bind(this)); - }, - - /** - * Re-processes all the currently pending requests, invoking the callbacks - * and/or loading more overlays as needed. In most cases, there will be a - * single request for one overlay, that will be processed immediately. - */ - processPendingRequests: function DOL_processPendingRequests() - { - // Re-process all the currently pending requests, yet allow more requests - // to be appended at the end of the array if we're not ready for them. - let currentLength = this._loadRequests.length; - for (let i = 0; i < currentLength; i++) { - let request = this._loadRequests.shift(); - - // We must call ensureOverlayLoaded again for each request, to check if - // the associated callback can be invoked now, or if we must still wait - // for the associated overlay to load. - this.ensureOverlayLoaded(request.overlay, request.callback); - } - } -}; - -XPCOMUtils.defineConstant(this, "DownloadsOverlayLoader", DownloadsOverlayLoader); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsView - -/** - * Builds and updates the downloads list widget, responding to changes in the - * download state and real-time data. In addition, handles part of the user - * interaction events raised by the downloads list widget. - */ -const DownloadsView = { - ////////////////////////////////////////////////////////////////////////////// - //// Functions handling download items in the list - - /** - * Maximum number of items shown by the list at any given time. - */ - kItemCountLimit: 3, - - /** - * Indicates whether we are still loading downloads data asynchronously. - */ - loading: false, - - /** - * Ordered array of all Download objects. We need to keep this array because - * only a limited number of items are shown at once, and if an item that is - * currently visible is removed from the list, we might need to take another - * item from the array and make it appear at the bottom. - */ - _downloads: [], - - /** - * Associates the visible Download objects with their corresponding - * DownloadsViewItem object. There is a limited number of view items in the - * panel at any given time. - */ - _visibleViewItems: new Map(), - - /** - * Called when the number of items in the list changes. - */ - _itemCountChanged: function DV_itemCountChanged() - { - DownloadsCommon.log("The downloads item count has changed - we are tracking", - this._downloads.length, "downloads in total."); - let count = this._downloads.length; - let hiddenCount = count - this.kItemCountLimit; - - if (count > 0) { - DownloadsCommon.log("Setting the panel's hasdownloads attribute to true."); - DownloadsPanel.panel.setAttribute("hasdownloads", "true"); - } else { - DownloadsCommon.log("Removing the panel's hasdownloads attribute."); - DownloadsPanel.panel.removeAttribute("hasdownloads"); - } - - // If we've got some hidden downloads, we should activate the - // DownloadsSummary. The DownloadsSummary will determine whether or not - // it's appropriate to actually display the summary. - DownloadsSummary.active = hiddenCount > 0; - }, - - /** - * Element corresponding to the list of downloads. - */ - get richListBox() - { - delete this.richListBox; - return this.richListBox = document.getElementById("downloadsListBox"); - }, - - /** - * Element corresponding to the button for showing more downloads. - */ - get downloadsHistory() - { - delete this.downloadsHistory; - return this.downloadsHistory = document.getElementById("downloadsHistory"); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Callback functions from DownloadsData - - /** - * Called before multiple downloads are about to be loaded. - */ - onDataLoadStarting: function DV_onDataLoadStarting() - { - DownloadsCommon.log("onDataLoadStarting called for DownloadsView."); - this.loading = true; - }, - - /** - * Called after data loading finished. - */ - onDataLoadCompleted: function DV_onDataLoadCompleted() - { - DownloadsCommon.log("onDataLoadCompleted called for DownloadsView."); - - this.loading = false; - - // We suppressed item count change notifications during the batch load, at - // this point we should just call the function once. - this._itemCountChanged(); - - // Notify the panel that all the initially available downloads have been - // loaded. This ensures that the interface is visible, if still required. - DownloadsPanel.onViewLoadCompleted(); - }, - - /** - * Called when the downloads database becomes unavailable (for example, - * entering Private Browsing Mode). References to existing data should be - * discarded. - */ - onDataInvalidated: function DV_onDataInvalidated() - { - DownloadsCommon.log("Downloads data has been invalidated. Cleaning up", - "DownloadsView."); - - DownloadsPanel.terminate(); - - // Clear the list by replacing with a shallow copy. - let emptyView = this.richListBox.cloneNode(false); - this.richListBox.parentNode.replaceChild(emptyView, this.richListBox); - this.richListBox = emptyView; - this._viewItems = {}; - this._dataItems = []; - }, - - /** - * Called when a new download data item is available, either during the - * asynchronous data load or when a new download is started. - * - * @param aDownload - * Download object that was just added. - * @param aNewest - * When true, indicates that this item is the most recent and should be - * added in the topmost position. This happens when a new download is - * started. When false, indicates that the item is the least recent - * and should be appended. The latter generally happens during the - * asynchronous data load. - */ - onDownloadAdded(download, aNewest) { - DownloadsCommon.log("A new download data item was added - aNewest =", - aNewest); - - if (aNewest) { - this._downloads.unshift(download); - } else { - this._downloads.push(download); - } - - let itemsNowOverflow = this._downloads.length > this.kItemCountLimit; - if (aNewest || !itemsNowOverflow) { - // The newly added item is visible in the panel and we must add the - // corresponding element. This is either because it is the first item, or - // because it was added at the bottom but the list still doesn't overflow. - this._addViewItem(download, aNewest); - } - if (aNewest && itemsNowOverflow) { - // If the list overflows, remove the last item from the panel to make room - // for the new one that we just added at the top. - this._removeViewItem(this._downloads[this.kItemCountLimit]); - } - - // For better performance during batch loads, don't update the count for - // every item, because the interface won't be visible until load finishes. - if (!this.loading) { - this._itemCountChanged(); - } - }, - - onDownloadStateChanged(download) { - let viewItem = this._visibleViewItems.get(download); - if (viewItem) { - viewItem.onStateChanged(); - } - }, - - onDownloadChanged(download) { - let viewItem = this._visibleViewItems.get(download); - if (viewItem) { - viewItem.onChanged(); - } - }, - - /** - * Called when a data item is removed. Ensures that the widget associated - * with the view item is removed from the user interface. - * - * @param download - * Download object that is being removed. - */ - onDownloadRemoved(download) { - DownloadsCommon.log("A download data item was removed."); - - let itemIndex = this._downloads.indexOf(download); - this._downloads.splice(itemIndex, 1); - - if (itemIndex < this.kItemCountLimit) { - // The item to remove is visible in the panel. - this._removeViewItem(download); - if (this._downloads.length >= this.kItemCountLimit) { - // Reinsert the next item into the panel. - this._addViewItem(this._downloads[this.kItemCountLimit - 1], false); - } - } - - this._itemCountChanged(); - }, - - /** - * Associates each richlistitem for a download with its corresponding - * DownloadsViewItemController object. - */ - _controllersForElements: new Map(), - - controllerForElement(element) { - return this._controllersForElements.get(element); - }, - - /** - * Creates a new view item associated with the specified data item, and adds - * it to the top or the bottom of the list. - */ - _addViewItem(download, aNewest) - { - DownloadsCommon.log("Adding a new DownloadsViewItem to the downloads list.", - "aNewest =", aNewest); - - let element = document.createElement("richlistitem"); - let viewItem = new DownloadsViewItem(download, element); - this._visibleViewItems.set(download, viewItem); - let viewItemController = new DownloadsViewItemController(download); - this._controllersForElements.set(element, viewItemController); - if (aNewest) { - this.richListBox.insertBefore(element, this.richListBox.firstChild); - } else { - this.richListBox.appendChild(element); - } - }, - - /** - * Removes the view item associated with the specified data item. - */ - _removeViewItem(download) { - DownloadsCommon.log("Removing a DownloadsViewItem from the downloads list."); - let element = this._visibleViewItems.get(download).element; - let previousSelectedIndex = this.richListBox.selectedIndex; - this.richListBox.removeChild(element); - if (previousSelectedIndex != -1) { - this.richListBox.selectedIndex = Math.min(previousSelectedIndex, - this.richListBox.itemCount - 1); - } - this._visibleViewItems.delete(download); - this._controllersForElements.delete(element); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// User interface event functions - - /** - * Helper function to do commands on a specific download item. - * - * @param aEvent - * Event object for the event being handled. If the event target is - * not a richlistitem that represents a download, this function will - * walk up the parent nodes until it finds a DOM node that is. - * @param aCommand - * The command to be performed. - */ - onDownloadCommand: function DV_onDownloadCommand(aEvent, aCommand) - { - let target = aEvent.target; - while (target.nodeName != "richlistitem") { - target = target.parentNode; - } - DownloadsView.controllerForElement(target).doCommand(aCommand); - }, - - onDownloadClick: function DV_onDownloadClick(aEvent) - { - // Handle primary clicks only, and exclude the action button. - if (aEvent.button == 0 && - !aEvent.originalTarget.hasAttribute("oncommand")) { - goDoCommand("downloadsCmd_open"); - } - }, - - /** - * Handles keypress events on a download item. - */ - onDownloadKeyPress: function DV_onDownloadKeyPress(aEvent) - { - // Pressing the key on buttons should not invoke the action because the - // event has already been handled by the button itself. - if (aEvent.originalTarget.hasAttribute("command") || - aEvent.originalTarget.hasAttribute("oncommand")) { - return; - } - - if (aEvent.charCode == " ".charCodeAt(0)) { - goDoCommand("downloadsCmd_pauseResume"); - return; - } - - if (aEvent.keyCode == KeyEvent.DOM_VK_ENTER || - aEvent.keyCode == KeyEvent.DOM_VK_RETURN) { - goDoCommand("downloadsCmd_doDefault"); - } - }, - - - /** - * Mouse listeners to handle selection on hover. - */ - onDownloadMouseOver: function DV_onDownloadMouseOver(aEvent) - { - if (aEvent.originalTarget.parentNode == this.richListBox) - this.richListBox.selectedItem = aEvent.originalTarget; - }, - onDownloadMouseOut: function DV_onDownloadMouseOut(aEvent) - { - if (aEvent.originalTarget.parentNode == this.richListBox) { - // If the destination element is outside of the richlistitem, clear the - // selection. - let element = aEvent.relatedTarget; - while (element && element != aEvent.originalTarget) { - element = element.parentNode; - } - if (!element) - this.richListBox.selectedIndex = -1; - } - }, - - onDownloadContextMenu: function DV_onDownloadContextMenu(aEvent) - { - let element = this.richListBox.selectedItem; - if (!element) { - return; - } - - DownloadsViewController.updateCommands(); - - // Set the state attribute so that only the appropriate items are displayed. - let contextMenu = document.getElementById("downloadsContextMenu"); - contextMenu.setAttribute("state", element.getAttribute("state")); - }, - - onDownloadDragStart: function DV_onDownloadDragStart(aEvent) - { - let element = this.richListBox.selectedItem; - if (!element) { - return; - } - - // We must check for existence synchronously because this is a DOM event. - let localFile = new FileUtils.File(DownloadsView.controllerForElement(element) - .download.target.path); - if (!localFile.exists()) { - return; - } - - let dataTransfer = aEvent.dataTransfer; - dataTransfer.mozSetDataAt("application/x-moz-file", localFile, 0); - dataTransfer.effectAllowed = "copyMove"; - var url = Services.io.newFileURI(localFile).spec; - dataTransfer.setData("text/uri-list", url); - dataTransfer.setData("text/plain", url); - dataTransfer.addElement(element); - - aEvent.stopPropagation(); - } -} - -XPCOMUtils.defineConstant(this, "DownloadsView", DownloadsView); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsViewItem - -/** - * Builds and updates a single item in the downloads list widget, responding to - * changes in the download state and real-time data. - * - * @param download - * Download object to be associated with the view item. - * @param aElement - * XUL element corresponding to the single download item in the view. - */ -function DownloadsViewItem(download, aElement) { - this.download = download; - - this.element = aElement; - this.element._shell = this; - - this.element.setAttribute("type", "download"); - this.element.classList.add("download-state"); - - this._updateState(); -} - -DownloadsViewItem.prototype = { - __proto__: DownloadsViewUI.DownloadElementShell.prototype, - - /** - * The XUL element corresponding to the associated richlistbox item. - */ - _element: null, - - onStateChanged() { - this.element.setAttribute("image", this.image); - this.element.setAttribute("state", - DownloadsCommon.stateOfDownload(this.download)); - }, - - onChanged() { - this._updateProgress(); - }, -}; - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsViewController - -/** - * Handles part of the user interaction events raised by the downloads list - * widget, in particular the "commands" that apply to multiple items, and - * dispatches the commands that apply to individual items. - */ -const DownloadsViewController = { - ////////////////////////////////////////////////////////////////////////////// - //// Initialization and termination - - initialize: function DVC_initialize() - { - window.controllers.insertControllerAt(0, this); - }, - - terminate: function DVC_terminate() - { - window.controllers.removeController(this); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// nsIController - - supportsCommand: function DVC_supportsCommand(aCommand) - { - // Firstly, determine if this is a command that we can handle. - if (!(aCommand in this.commands) && - !(aCommand in DownloadsViewItemController.prototype.commands)) { - return false; - } - // Secondly, determine if focus is on a control in the downloads list. - let element = document.commandDispatcher.focusedElement; - while (element && element != DownloadsView.richListBox) { - element = element.parentNode; - } - // We should handle the command only if the downloads list is among the - // ancestors of the focused element. - return !!element; - }, - - isCommandEnabled: function DVC_isCommandEnabled(aCommand) - { - // Handle commands that are not selection-specific. - if (aCommand == "downloadsCmd_clearList") { - return DownloadsCommon.getData(window).canRemoveFinished; - } - - // Other commands are selection-specific. - let element = DownloadsView.richListBox.selectedItem; - return element && DownloadsView.controllerForElement(element) - .isCommandEnabled(aCommand); - }, - - doCommand: function DVC_doCommand(aCommand) - { - // If this command is not selection-specific, execute it. - if (aCommand in this.commands) { - this.commands[aCommand].apply(this); - return; - } - - // Other commands are selection-specific. - let element = DownloadsView.richListBox.selectedItem; - if (element) { - // The doCommand function also checks if the command is enabled. - DownloadsView.controllerForElement(element).doCommand(aCommand); - } - }, - - onEvent: function () { }, - - ////////////////////////////////////////////////////////////////////////////// - //// Other functions - - updateCommands: function DVC_updateCommands() - { - Object.keys(this.commands).forEach(goUpdateCommand); - Object.keys(DownloadsViewItemController.prototype.commands) - .forEach(goUpdateCommand); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Selection-independent commands - - /** - * This object contains one key for each command that operates regardless of - * the currently selected item in the list. - */ - commands: { - downloadsCmd_clearList: function DVC_downloadsCmd_clearList() - { - DownloadsCommon.getData(window).removeFinished(); - } - } -}; - -XPCOMUtils.defineConstant(this, "DownloadsViewController", DownloadsViewController); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsViewItemController - -/** - * Handles all the user interaction events, in particular the "commands", - * related to a single item in the downloads list widgets. - */ -function DownloadsViewItemController(download) { - this.download = download; -} - -DownloadsViewItemController.prototype = { - isCommandEnabled: function DVIC_isCommandEnabled(aCommand) - { - switch (aCommand) { - case "downloadsCmd_open": { - if (!this.download.succeeded) { - return false; - } - - let file = new FileUtils.File(this.download.target.path); - return file.exists(); - } - case "downloadsCmd_show": { - let file = new FileUtils.File(this.download.target.path); - if (file.exists()) { - return true; - } - - if (!this.download.target.partFilePath) { - return false; - } - - let partFile = new FileUtils.File(this.download.target.partFilePath); - return partFile.exists(); - } - case "downloadsCmd_pauseResume": - return this.download.hasPartialData && !this.download.error; - case "downloadsCmd_retry": - return this.download.canceled || this.download.error; - case "downloadsCmd_openReferrer": - return !!this.download.source.referrer; - case "cmd_delete": - case "downloadsCmd_cancel": - case "downloadsCmd_copyLocation": - case "downloadsCmd_doDefault": - return true; - } - return false; - }, - - doCommand: function DVIC_doCommand(aCommand) - { - if (this.isCommandEnabled(aCommand)) { - this.commands[aCommand].apply(this); - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Item commands - - /** - * This object contains one key for each command that operates on this item. - * - * In commands, the "this" identifier points to the controller item. - */ - commands: { - cmd_delete: function DVIC_cmd_delete() - { - DownloadsCommon.removeAndFinalizeDownload(this.download); - PlacesUtils.bhistory.removePage( - NetUtil.newURI(this.download.source.url)); - }, - - downloadsCmd_cancel: function DVIC_downloadsCmd_cancel() - { - this.download.cancel().catch(() => {}); - this.download.removePartialData().catch(Cu.reportError); - }, - - downloadsCmd_open: function DVIC_downloadsCmd_open() - { - this.download.launch().catch(Cu.reportError); - - // We explicitly close the panel here to give the user the feedback that - // their click has been received, and we're handling the action. - // Otherwise, we'd have to wait for the file-type handler to execute - // before the panel would close. This also helps to prevent the user from - // accidentally opening a file several times. - DownloadsPanel.hidePanel(); - }, - - downloadsCmd_show: function DVIC_downloadsCmd_show() - { - let file = new FileUtils.File(this.download.target.path); - DownloadsCommon.showDownloadedFile(file); - - // We explicitly close the panel here to give the user the feedback that - // their click has been received, and we're handling the action. - // Otherwise, we'd have to wait for the operating system file manager - // window to open before the panel closed. This also helps to prevent the - // user from opening the containing folder several times. - DownloadsPanel.hidePanel(); - }, - - downloadsCmd_pauseResume: function DVIC_downloadsCmd_pauseResume() - { - if (this.download.stopped) { - this.download.start(); - } else { - this.download.cancel(); - } - }, - - downloadsCmd_retry: function DVIC_downloadsCmd_retry() - { - this.download.start().catch(() => {}); - }, - - downloadsCmd_openReferrer: function DVIC_downloadsCmd_openReferrer() - { - openURL(this.download.source.referrer); - }, - - downloadsCmd_copyLocation: function DVIC_downloadsCmd_copyLocation() - { - let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] - .getService(Ci.nsIClipboardHelper); - clipboard.copyString(this.download.source.url, document); - }, - - downloadsCmd_doDefault: function DVIC_downloadsCmd_doDefault() - { - const nsIDM = Ci.nsIDownloadManager; - - // Determine the default command for the current item. - let defaultCommand = function () { - switch (DownloadsCommon.stateOfDownload(this.download)) { - case nsIDM.DOWNLOAD_NOTSTARTED: return "downloadsCmd_cancel"; - case nsIDM.DOWNLOAD_FINISHED: return "downloadsCmd_open"; - case nsIDM.DOWNLOAD_FAILED: return "downloadsCmd_retry"; - case nsIDM.DOWNLOAD_CANCELED: return "downloadsCmd_retry"; - case nsIDM.DOWNLOAD_PAUSED: return "downloadsCmd_pauseResume"; - case nsIDM.DOWNLOAD_QUEUED: return "downloadsCmd_cancel"; - case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: return "downloadsCmd_openReferrer"; - case nsIDM.DOWNLOAD_SCANNING: return "downloadsCmd_show"; - case nsIDM.DOWNLOAD_DIRTY: return "downloadsCmd_openReferrer"; - case nsIDM.DOWNLOAD_BLOCKED_POLICY: return "downloadsCmd_openReferrer"; - } - return ""; - }.apply(this); - if (defaultCommand && this.isCommandEnabled(defaultCommand)) - this.doCommand(defaultCommand); - } - } -}; - - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsSummary - -/** - * Manages the summary at the bottom of the downloads panel list if the number - * of items in the list exceeds the panels limit. - */ -const DownloadsSummary = { - - /** - * Sets the active state of the summary. When active, the summary subscribes - * to the DownloadsCommon DownloadsSummaryData singleton. - * - * @param aActive - * Set to true to activate the summary. - */ - set active(aActive) - { - if (aActive == this._active || !this._summaryNode) { - return this._active; - } - if (aActive) { - DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit) - .refreshView(this); - } else { - DownloadsFooter.showingSummary = false; - } - - return this._active = aActive; - }, - - /** - * Returns the active state of the downloads summary. - */ - get active() this._active, - - _active: false, - - /** - * Sets whether or not we show the progress bar. - * - * @param aShowingProgress - * True if we should show the progress bar. - */ - set showingProgress(aShowingProgress) - { - if (aShowingProgress) { - this._summaryNode.setAttribute("inprogress", "true"); - } else { - this._summaryNode.removeAttribute("inprogress"); - } - // If progress isn't being shown, then we simply do not show the summary. - return DownloadsFooter.showingSummary = aShowingProgress; - }, - - /** - * Sets the amount of progress that is visible in the progress bar. - * - * @param aValue - * A value between 0 and 100 to represent the progress of the - * summarized downloads. - */ - set percentComplete(aValue) - { - if (this._progressNode) { - this._progressNode.setAttribute("value", aValue); - } - return aValue; - }, - - /** - * Sets the description for the download summary. - * - * @param aValue - * A string representing the description of the summarized - * downloads. - */ - set description(aValue) - { - if (this._descriptionNode) { - this._descriptionNode.setAttribute("value", aValue); - this._descriptionNode.setAttribute("tooltiptext", aValue); - } - return aValue; - }, - - /** - * Sets the details for the download summary, such as the time remaining, - * the amount of bytes transferred, etc. - * - * @param aValue - * A string representing the details of the summarized - * downloads. - */ - set details(aValue) - { - if (this._detailsNode) { - this._detailsNode.setAttribute("value", aValue); - this._detailsNode.setAttribute("tooltiptext", aValue); - } - return aValue; - }, - - /** - * Focuses the root element of the summary. - */ - focus: function() - { - if (this._summaryNode) { - this._summaryNode.focus(); - } - }, - - /** - * Respond to keydown events on the Downloads Summary node. - * - * @param aEvent - * The keydown event being handled. - */ - onKeyDown: function DS_onKeyDown(aEvent) - { - if (aEvent.charCode == " ".charCodeAt(0) || - aEvent.keyCode == KeyEvent.DOM_VK_ENTER || - aEvent.keyCode == KeyEvent.DOM_VK_RETURN) { - DownloadsPanel.showDownloadsHistory(); - } - }, - - /** - * Respond to click events on the Downloads Summary node. - * - * @param aEvent - * The click event being handled. - */ - onClick: function DS_onClick(aEvent) - { - DownloadsPanel.showDownloadsHistory(); - }, - - /** - * Element corresponding to the root of the downloads summary. - */ - get _summaryNode() - { - let node = document.getElementById("downloadsSummary"); - if (!node) { - return null; - } - delete this._summaryNode; - return this._summaryNode = node; - }, - - /** - * Element corresponding to the progress bar in the downloads summary. - */ - get _progressNode() - { - let node = document.getElementById("downloadsSummaryProgress"); - if (!node) { - return null; - } - delete this._progressNode; - return this._progressNode = node; - }, - - /** - * Element corresponding to the main description of the downloads - * summary. - */ - get _descriptionNode() - { - let node = document.getElementById("downloadsSummaryDescription"); - if (!node) { - return null; - } - delete this._descriptionNode; - return this._descriptionNode = node; - }, - - /** - * Element corresponding to the secondary description of the downloads - * summary. - */ - get _detailsNode() - { - let node = document.getElementById("downloadsSummaryDetails"); - if (!node) { - return null; - } - delete this._detailsNode; - return this._detailsNode = node; - } -}; - -XPCOMUtils.defineConstant(this, "DownloadsSummary", DownloadsSummary); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsFooter - -/** - * Manages events sent to to the footer vbox, which contains both the - * DownloadsSummary as well as the "Show All Downloads" button. - */ -const DownloadsFooter = { - - /** - * Focuses the appropriate element within the footer. If the summary - * is visible, focus it. If not, focus the "Show All Downloads" - * button. - */ - focus: function DF_focus() - { - if (this._showingSummary) { - DownloadsSummary.focus(); - } else { - DownloadsView.downloadsHistory.focus(); - } - }, - - _showingSummary: false, - - /** - * Sets whether or not the Downloads Summary should be displayed in the - * footer. If not, the "Show All Downloads" button is shown instead. - */ - set showingSummary(aValue) - { - if (this._footerNode) { - if (aValue) { - this._footerNode.setAttribute("showingsummary", "true"); - } else { - this._footerNode.removeAttribute("showingsummary"); - } - this._showingSummary = aValue; - } - return aValue; - }, - - /** - * Element corresponding to the footer of the downloads panel. - */ - get _footerNode() - { - let node = document.getElementById("downloadsFooter"); - if (!node) { - return null; - } - delete this._footerNode; - return this._footerNode = node; - } -}; - -XPCOMUtils.defineConstant(this, "DownloadsFooter", DownloadsFooter); diff --git a/components/downloads/content/downloadsOverlay.xul b/components/downloads/content/downloadsOverlay.xul deleted file mode 100644 index ca35ee3..0000000 --- a/components/downloads/content/downloadsOverlay.xul +++ /dev/null @@ -1,142 +0,0 @@ -<?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_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" - consumeoutsideclicks="true" - 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" - 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="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.clearList.label;" - accesskey="&cmd.clearList.accesskey;"/> - </menupopup> - - <richlistbox id="downloadsListBox" - class="plain" - flex="1" - 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> - - <vbox id="downloadsFooter"> - <hbox id="downloadsSummary" - align="center" - orient="horizontal" - onkeydown="DownloadsSummary.onKeyDown(event);" - onclick="DownloadsSummary.onClick(event);"> - <image class="downloadTypeIcon" /> - <vbox> - <description id="downloadsSummaryDescription" - style="min-width: &downloadsSummary.minWidth2;"/> - <progressmeter id="downloadsSummaryProgress" - class="downloadProgress" - min="0" - max="100" - mode="normal" /> - <description id="downloadsSummaryDetails" - style="width: &downloadDetails.width;" - crop="end"/> - </vbox> - </hbox> - - <button id="downloadsHistory" - class="plain" - label="&downloadsHistory.label;" - accesskey="&downloadsHistory.accesskey;" - oncommand="DownloadsPanel.showDownloadsHistory();"/> - </vbox> - </panel> - </popupset> -</overlay> diff --git a/components/downloads/content/indicator.js b/components/downloads/content/indicator.js deleted file mode 100644 index 1a2175a..0000000 --- a/components/downloads/content/indicator.js +++ /dev/null @@ -1,609 +0,0 @@ -/* -*- Mode: C++; 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/. */ - -/** - * 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, and in particular should not cause the Download Manager - * service to start. - */ - initializeIndicator: function DB_initializeIndicator() - { - this._update(); - }, - - /** - * 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() - { - // Hide the indicator and prevent it to be displayed as a temporary anchor - // during customization, even if requested using the getAnchor method. - this._customizing = true; - this._anchorRequested = false; - - let indicator = DownloadsIndicatorView.indicator; - if (indicator) { - indicator.collapsed = true; - } - - let placeholder = this._placeholder; - if (placeholder) { - placeholder.collapsed = false; - } - }, - - /** - * This function is called when toolbar customization ends. - */ - customizeDone: function DB_customizeDone() - { - this._customizing = false; - this._update(); - }, - - /** - * This function is called during initialization or when toolbar customization - * ends. It determines if we should enable or disable the object that keeps - * the indicator updated, and ensures that the placeholder is hidden unless it - * has been moved to the customization palette. - * - * NOTE: This function is also called on startup, thus it should limit the - * input/output it performs, and in particular should not cause the - * Download Manager service to start. - */ - _update: function DB_update() { - this._updatePositionInternal(); - - if (!DownloadsCommon.useToolkitUI) { - DownloadsIndicatorView.ensureInitialized(); - } else { - DownloadsIndicatorView.ensureTerminated(); - } - }, - - /** - * Determines the position where the indicator should appear, and moves its - * associated element to the new position. This does not happen if the - * indicator is currently being used as the anchor for the panel, to ensure - * that the panel doesn't flicker because we move the DOM element to which - * it's anchored. - */ - updatePosition: function DB_updatePosition() - { - if (!this._anchorRequested) { - this._updatePositionInternal(); - } - }, - - /** - * 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. - */ - _updatePositionInternal: function DB_updatePositionInternal() - { - let indicator = DownloadsIndicatorView.indicator; - if (!indicator) { - // Exit now if the indicator overlay isn't loaded yet. - return null; - } - - let placeholder = this._placeholder; - if (!placeholder) { - // The placeholder has been removed from the browser window. - indicator.collapsed = true; - // Move the indicator to a safe position on the toolbar, since otherwise - // it may break the merge of adjacent items, like back/forward + urlbar. - indicator.parentNode.appendChild(indicator); - return null; - } - - // Position the indicator where the placeholder is located. We should - // update the position even if the placeholder is located on an invisible - // toolbar, because the toolbar may be displayed later. - placeholder.parentNode.insertBefore(indicator, placeholder); - placeholder.collapsed = true; - indicator.collapsed = false; - - indicator.open = this._anchorRequested; - - // Determine if the placeholder is located on an invisible toolbar. - if (!isElementVisible(placeholder.parentNode)) { - 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._updatePositionInternal()); - } - - DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay, - DB_GA_callback.bind(this)); - }, - - /** - * Allows the temporary anchor to be hidden. - */ - releaseAnchor: function DB_releaseAnchor() - { - this._anchorRequested = false; - this._updatePositionInternal(); - }, - - 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: 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) { - aCallback(); - 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); - } - - 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, - - /** - * 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; - } - - function DIV_SEN_callback() { - if (this._notificationTimeout) { - clearTimeout(this._notificationTimeout); - } - - // Now that the overlay is loaded, place the indicator in its final - // position. - DownloadsButton.updatePosition(); - - let indicator = this.indicator; - indicator.setAttribute("notification", aType); - this._notificationTimeout = setTimeout( - function () indicator.removeAttribute("notification"), 1000); - } - - this._ensureOperational(DIV_SEN_callback.bind(this)); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// 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._hasDownloads = aValue; - - // If there is at least one download, ensure that the view elements are - // loaded before determining the position of the downloads button. - if (aValue) { - this._ensureOperational(function() DownloadsButton.updatePosition()); - } else { - DownloadsButton.updatePosition(); - } - } - 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 (DownloadsCommon.useToolkitUI) { - // The panel won't suppress attention for us, we need to clear now. - DownloadsCommon.getIndicatorData(window).attention = false; - BrowserDownloadsUI(); - } 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 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(); - } - }, - - /** - * Returns a reference to the main indicator element, or null if the element - * is not present in the browser window yet. - */ - get indicator() - { - let indicator = document.getElementById("downloads-indicator"); - if (!indicator) { - return null; - } - - // Once the element is loaded, it will never be unloaded. - delete this.indicator; - return this.indicator = indicator; - }, - - get indicatorAnchor() - { - delete this.indicatorAnchor; - return this.indicatorAnchor = - document.getElementById("downloads-indicator-anchor"); - }, - - get _indicatorCounter() - { - delete this._indicatorCounter; - return this._indicatorCounter = - document.getElementById("downloads-indicator-counter"); - }, - - get _indicatorProgress() - { - delete this._indicatorProgress; - return this._indicatorProgress = - document.getElementById("downloads-indicator-progress"); - } -}; - -Object.defineProperty(this, "DownloadsIndicatorView", { - value: DownloadsIndicatorView, - enumerable: true, - writable: false -}); diff --git a/components/downloads/content/indicatorOverlay.xul b/components/downloads/content/indicatorOverlay.xul deleted file mode 100644 index efb6cab..0000000 --- a/components/downloads/content/indicatorOverlay.xul +++ /dev/null @@ -1,60 +0,0 @@ -<?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 [ - <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" > - %browserDTD; - <!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd" > - %downloadsDTD; -]> - -<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"> - - <popupset> - <!-- The downloads indicator is placed in its final toolbar location - programmatically, and can be shown temporarily even when its - placeholder is removed from the toolbars. Its initial location within - the document must not be a toolbar or the toolbar palette, otherwise the - toolbar handling code could remove it from the document. --> - <toolbarbutton id="downloads-indicator" - class="toolbarbutton-1 chromeclass-toolbar-additional" - tooltiptext="&downloads.tooltip;" - collapsed="true" - oncommand="DownloadsIndicatorView.onCommand(event);" - ondrop="DownloadsIndicatorView.onDrop(event);" - ondragover="DownloadsIndicatorView.onDragOver(event);" - ondragenter="DownloadsIndicatorView.onDragOver(event);" - ondragleave="DownloadsIndicatorView.onDragLeave(event);" - skipintoolbarset="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" - class="toolbarbutton-icon"> - <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"/> - <vbox id="downloads-indicator-notification"/> - </stack> - <label class="toolbarbutton-text" crop="right" flex="1" - value="&downloads.label;"/> - </toolbarbutton> - </popupset> -</overlay> |