diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2018-06-04 13:17:38 +0200 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2018-06-04 13:17:38 +0200 |
commit | a1be17c1cea81ebb1e8b131a662c698d78f3f7f2 (patch) | |
tree | a92f7de513be600cc07bac458183e9af40e00c06 /browser/components/downloads/content | |
parent | bf11fdd304898ac675e39b01b280d39550e419d0 (diff) | |
download | UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.gz UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.lz UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.xz UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.zip |
Issue #303 Part 1: Move basilisk files from /browser to /application/basilisk
Diffstat (limited to 'browser/components/downloads/content')
11 files changed, 0 insertions, 4594 deletions
diff --git a/browser/components/downloads/content/allDownloadsViewOverlay.js b/browser/components/downloads/content/allDownloadsViewOverlay.js deleted file mode 100644 index 58078cd08..000000000 --- a/browser/components/downloads/content/allDownloadsViewOverlay.js +++ /dev/null @@ -1,1439 +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"; - -/** - * 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.canceled = metaData.state == nsIDM.DOWNLOAD_CANCELED || - metaData.state == nsIDM.DOWNLOAD_PAUSED; - this.endTime = metaData.endTime; - - // Recreate partial error information from the state saved in history. - if (metaData.state == nsIDM.DOWNLOAD_FAILED) { - this.error = { message: "History download failed." }; - } else if (metaData.state == nsIDM.DOWNLOAD_BLOCKED_PARENTAL) { - this.error = { becauseBlockedByParentalControls: true }; - } else if (metaData.state == nsIDM.DOWNLOAD_DIRTY) { - this.error = { - becauseBlockedByReputationCheck: true, - reputationCheckVerdict: metaData.reputationCheckVerdict || "", - }; - } else { - this.error = null; - } - - // 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() { - if (!this._active) { - this._active = true; - this.element.setAttribute("active", true); - this._updateUI(); - } - }, - get active() { - return !!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() { - return this._sessionDownload || this._historyDownload; - }, - - _sessionDownload: null, - get sessionDownload() { - return 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() { - return 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._updateState(); - - if (this.element.selected) { - goUpdateDownloadCommands(); - } else { - // If a state change occurs in an item that is not currently selected, - // this is the only command that may be affected. - goUpdateCommand("downloadsCmd_clearDownloads"); - } - }, - - onChanged() { - // This cannot be placed within onStateChanged because - // when a download goes from hasBlockedData to !hasBlockedData - // it will still remain in the same state. - this.element.classList.toggle("temporary-block", - !!this.download.hasBlockedData); - this._updateProgress(); - }, - - 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 "cmd_delete": - // We don't want in-progress downloads to be removed accidentally. - return this.download.stopped; - case "downloadsCmd_cancel": - return !!this._sessionDownload; - } - return DownloadsViewUI.DownloadElementShell.prototype - .isCommandEnabled.call(this, aCommand); - }, - - doCommand(aCommand) { - if (DownloadsViewUI.isCommandName(aCommand)) { - this[aCommand](); - } - }, - - downloadsCmd_open() { - let file = new FileUtils.File(this.download.target.path); - DownloadsCommon.openDownloadedFile(file, null, window); - }, - - downloadsCmd_show() { - let file = new FileUtils.File(this.download.target.path); - DownloadsCommon.showDownloadedFile(file); - }, - - downloadsCmd_openReferrer() { - openURL(this.download.source.referrer); - }, - - cmd_delete() { - if (this._sessionDownload) { - DownloadsCommon.removeAndFinalizeDownload(this.download); - } - if (this._historyDownload) { - let uri = NetUtil.newURI(this.download.source.url); - PlacesUtils.bhistory.removePage(uri); - } - }, - - downloadsCmd_unblock() { - this.confirmUnblock(window, "unblock"); - }, - - downloadsCmd_chooseUnblock() { - this.confirmUnblock(window, "chooseUnblock"); - }, - - downloadsCmd_chooseOpen() { - this.confirmUnblock(window, "chooseOpen"); - }, - - // 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(aTerm) { - if (!aTerm) { - return true; - } - aTerm = aTerm.toLowerCase(); - return this.displayName.toLowerCase().includes(aTerm) || - this.download.source.url.toLowerCase().includes(aTerm); - }, - - // Handles return keypress on the element (the keypress listener is - // set in the DownloadsPlacesView object). - doDefaultCommand() { - let command = this.currentDefaultCommandName; - 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() { - 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(); - }), -}; - -/** - * Relays commands from the download.xml binding to the selected items. - */ -const DownloadsView = { - onDownloadCommand(event, command) { - goDoCommand(command); - }, - - onDownloadClick() {}, -}; - -/** - * 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 = DownloadsCommon.ATTENTION_NONE; - - // Make sure to unregister the view if the window is closed. - window.addEventListener("unload", () => { - window.controllers.removeController(this); - this._downloadsData.removeView(this); - this.result = null; - }, true); - // Resizing the window may change items visibility. - window.addEventListener("resize", () => { - this._ensureVisibleElementsAreActive(); - }, true); -} - -DownloadsPlacesView.prototype = { - get associatedElement() { - return this._richlistbox; - }, - - get active() { - return 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(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(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() { - if (!this.active || this._ensureVisibleTimer || - !this._richlistbox.firstChild) { - return; - } - - this._ensureVisibleTimer = setTimeout(() => { - 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(); - } - }, 10); - }, - - _place: "", - get place() { - return 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() { - return 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() { - return this.selectedNodes.length > 0; - }, - - containerStateChanged(aNode, aOldState, aNewState) { - this.invalidateContainer(aNode) - }, - - 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(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 of Object.getOwnPropertyNames(this._richlistbox)) { - let value = this._richlistbox[key]; - 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(aParent, aPlacesNode) { - this._addDownloadData(null, aPlacesNode); - }, - - nodeRemoved(aParent, aPlacesNode, aOldIndex) { - this._removeHistoryDownloadFromView(aPlacesNode); - }, - - nodeAnnotationChanged() {}, - nodeIconChanged() {}, - nodeTitleChanged() {}, - nodeKeywordChanged() {}, - nodeDateAddedChanged() {}, - nodeLastModifiedChanged() {}, - nodeHistoryDetailsChanged() {}, - nodeTagsChanged() {}, - sortingChanged() {}, - nodeMoved() {}, - nodeURIChanged() {}, - batching() {}, - - get controller() { - return this._richlistbox.controller; - }, - - get searchTerm() { - return 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() { - // 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(() => { - this._richlistbox.selectedItem = firstDownloadElement; - this._richlistbox.currentItem = firstDownloadElement; - this._initiallySelectedElement = firstDownloadElement; - }, Ci.nsIThread.DISPATCH_NORMAL); - } - } - }, - - onDataLoadStarting() {}, - 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); - }, - - // nsIController - supportsCommand(aCommand) { - // Firstly, determine if this is a command that we can handle. - if (!DownloadsViewUI.isCommandName(aCommand)) { - return false; - } - if (!(aCommand in this) && - !(aCommand in HistoryDownloadElementShell.prototype)) { - return false; - } - // If this function returns true, other controllers won't get a chance to - // process the command even if isCommandEnabled returns false, so it's - // important to check if the list is focused here to handle common commands - // like copy and paste correctly. The clear downloads command, instead, is - // specific to the downloads list but can be invoked from the toolbar, so we - // can just return true unconditionally. - return aCommand == "downloadsCmd_clearDownloads" || - document.activeElement == this._richlistbox; - }, - - // nsIController - 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, - element => element._shell.isCommandEnabled(aCommand)); - } - }, - - _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() { - 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")); - }, - - _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() { - let [url, name] = this._getURLFromClipboardData(); - return url != ""; - }, - - _downloadURLFromClipboard() { - let [url, name] = this._getURLFromClipboardData(); - let browserWin = RecentWindow.getMostRecentBrowserWindow(); - let initiatingDoc = browserWin ? browserWin.document : document; - DownloadURL(url, name, initiatingDoc); - }, - - // nsIController - doCommand(aCommand) { - // Commands may be invoked with keyboard shortcuts even if disabled. - if (!this.isCommandEnabled(aCommand)) { - return; - } - - // If this command is not selection-specific, execute it. - if (aCommand in this) { - this[aCommand](); - return; - } - - // 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); - } - }, - - // nsIController - onEvent() {}, - - cmd_copy() { - this._copySelectedDownloadsToClipboard(); - }, - - cmd_selectAll() { - this._richlistbox.selectAll(); - }, - - cmd_paste() { - this._downloadURLFromClipboard(); - }, - - 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"); - }, - - 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)); - contextMenu.classList.toggle("temporary-block", - !!download.hasBlockedData); - - 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(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(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() { - this._ensureVisibleElementsAreActive(); - }, - - onSelect() { - goUpdateDownloadCommands(); - - let selectedElements = this._richlistbox.selectedItems; - for (let elt of selectedElements) { - if (elt._shell) { - elt._shell.onSelect(); - } - } - }, - - 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(aEvent) { - let types = aEvent.dataTransfer.types; - if (types.includes("text/uri-list") || - types.includes("text/x-moz-url") || - types.includes("text/plain")) { - aEvent.preventDefault(); - } - }, - - 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() { - function updateCommandsForObject(object) { - for (let name in object) { - if (DownloadsViewUI.isCommandName(name)) { - goUpdateCommand(name); - } - } - } - updateCommandsForObject(DownloadsPlacesView.prototype); - updateCommandsForObject(HistoryDownloadElementShell.prototype); -} diff --git a/browser/components/downloads/content/allDownloadsViewOverlay.xul b/browser/components/downloads/content/allDownloadsViewOverlay.xul deleted file mode 100644 index cb8c699bf..000000000 --- a/browser/components/downloads/content/allDownloadsViewOverlay.xul +++ /dev/null @@ -1,131 +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/downloads.css"?> -<?xml-stylesheet href="chrome://browser/skin/downloads/allDownloadsViewOverlay.css"?> - -<!DOCTYPE overlay [ -<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd"> -%downloadsDTD; -]> - -<!-- This overlay provides a downloads view that lists both session downloads, - using the DownloadsView API, and history downloads, using places queries. - The view also implements a command controller and a context menu for - managing the downloads list. In order to use this view: - 1. Apply this overlay to your window. - 2. Insert in all the overlay entry-points, namely: - <richlistbox id="downloadsRichListBox"/> - <commandset id="downloadCommands"/> - <menupopup id="downloadsContextMenu"/> - 3. Make sure your window has the editMenuOverlay overlay applied, - because the view implements cmd_copy and cmd_delete. - 4. Make sure your window has the globalOverlay.js script loaded. - 5. To initialize the view - let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox")); - // This is what the Places Library uses. It could be tweaked a bit as long as the - // transition-type is set correctly - view.place = "place:transition=7&sort=4"; ---> -<overlay id="downloadsViewOverlay" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <script type="application/javascript" - src="chrome://browser/content/downloads/allDownloadsViewOverlay.js"/> - <script type="application/javascript" - src="chrome://global/content/contentAreaUtils.js"/> - - <richlistbox flex="1" - seltype="multiple" - id="downloadsRichListBox" context="downloadsContextMenu" - onscroll="return this._placesView.onScroll();" - onkeypress="return this._placesView.onKeyPress(event);" - ondblclick="return this._placesView.onDoubleClick(event);" - oncontextmenu="return this._placesView.onContextMenu(event);" - ondragstart="this._placesView.onDragStart(event);" - ondragover="this._placesView.onDragOver(event);" - ondrop="this._placesView.onDrop(event);" - onfocus="goUpdateDownloadCommands();" - onselect="this._placesView.onSelect();" - onblur="goUpdateDownloadCommands();"/> - - <commandset id="downloadCommands" - commandupdater="true" - events="focus,select,contextmenu" - oncommandupdate="goUpdateDownloadCommands();"> - <command id="downloadsCmd_pauseResume" - oncommand="goDoCommand('downloadsCmd_pauseResume')"/> - <command id="downloadsCmd_cancel" - oncommand="goDoCommand('downloadsCmd_cancel')"/> - <command id="downloadsCmd_unblock" - oncommand="goDoCommand('downloadsCmd_unblock')"/> - <command id="downloadsCmd_chooseUnblock" - oncommand="goDoCommand('downloadsCmd_chooseUnblock')"/> - <command id="downloadsCmd_chooseOpen" - oncommand="goDoCommand('downloadsCmd_chooseOpen')"/> - <command id="downloadsCmd_confirmBlock" - oncommand="goDoCommand('downloadsCmd_confirmBlock')"/> - <command id="downloadsCmd_open" - oncommand="goDoCommand('downloadsCmd_open')"/> - <command id="downloadsCmd_show" - oncommand="goDoCommand('downloadsCmd_show')"/> - <command id="downloadsCmd_retry" - oncommand="goDoCommand('downloadsCmd_retry')"/> - <command id="downloadsCmd_openReferrer" - oncommand="goDoCommand('downloadsCmd_openReferrer')"/> - <command id="downloadsCmd_clearDownloads" - oncommand="goDoCommand('downloadsCmd_clearDownloads')"/> - </commandset> - - <menupopup id="downloadsContextMenu" class="download-state"> - <menuitem command="downloadsCmd_pauseResume" - class="downloadPauseMenuItem" - label="&cmd.pause.label;" - accesskey="&cmd.pause.accesskey;"/> - <menuitem command="downloadsCmd_pauseResume" - class="downloadResumeMenuItem" - label="&cmd.resume.label;" - accesskey="&cmd.resume.accesskey;"/> - <menuitem command="downloadsCmd_cancel" - class="downloadCancelMenuItem" - label="&cmd.cancel.label;" - accesskey="&cmd.cancel.accesskey;"/> - <menuitem command="downloadsCmd_unblock" - class="downloadUnblockMenuItem" - label="&cmd.unblock2.label;" - accesskey="&cmd.unblock2.accesskey;"/> - <menuitem command="cmd_delete" - class="downloadRemoveFromHistoryMenuItem" - label="&cmd.removeFromHistory.label;" - accesskey="&cmd.removeFromHistory.accesskey;"/> - <menuitem command="downloadsCmd_show" - class="downloadShowMenuItem" -#ifdef XP_MACOSX - label="&cmd.showMac.label;" - accesskey="&cmd.showMac.accesskey;" -#else - label="&cmd.show.label;" - accesskey="&cmd.show.accesskey;" -#endif - /> - - <menuseparator class="downloadCommandsSeparator"/> - - <menuitem command="downloadsCmd_openReferrer" - label="&cmd.goToDownloadPage.label;" - accesskey="&cmd.goToDownloadPage.accesskey;"/> - <menuitem command="cmd_copy" - label="&cmd.copyDownloadLink.label;" - accesskey="&cmd.copyDownloadLink.accesskey;"/> - - <menuseparator/> - - <menuitem command="downloadsCmd_clearDownloads" - label="&cmd.clearDownloads.label;" - accesskey="&cmd.clearDownloads.accesskey;"/> - </menupopup> -</overlay> diff --git a/browser/components/downloads/content/contentAreaDownloadsView.css b/browser/components/downloads/content/contentAreaDownloadsView.css deleted file mode 100644 index abaae1f7b..000000000 --- a/browser/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/browser/components/downloads/content/contentAreaDownloadsView.js b/browser/components/downloads/content/contentAreaDownloadsView.js deleted file mode 100644 index 6e4f18599..000000000 --- a/browser/components/downloads/content/contentAreaDownloadsView.js +++ /dev/null @@ -1,17 +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() { - let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox")); - // Do not display the Places downloads in private windows - if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) { - view.place = "place:transition=7&sort=4"; - } - // Set focus to Downloads list once it is created - document.getElementById("downloadsRichListBox").focus(); - }, -}; diff --git a/browser/components/downloads/content/contentAreaDownloadsView.xul b/browser/components/downloads/content/contentAreaDownloadsView.xul deleted file mode 100644 index 91c986656..000000000 --- a/browser/components/downloads/content/contentAreaDownloadsView.xul +++ /dev/null @@ -1,46 +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;" - mousethrough="always"/> - </stack> - <commandset id="downloadCommands"/> - <menupopup id="downloadsContextMenu"/> -</window> diff --git a/browser/components/downloads/content/download.xml b/browser/components/downloads/content/download.xml deleted file mode 100644 index 29a1530fd..000000000 --- a/browser/components/downloads/content/download.xml +++ /dev/null @@ -1,99 +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" - onclick="DownloadsView.onDownloadClick(event);"> - <xul:hbox class="downloadMainArea" - flex="1" - align="center"> - <xul:stack> - <xul:image class="downloadTypeIcon" - validate="always" - xbl:inherits="src=image"/> - <xul:image class="downloadBlockedBadge" /> - </xul:stack> - <xul:vbox pack="center" - flex="1" - class="downloadContainer" - style="width: &downloadDetails.width;"> - <!-- We're letting localizers put a min-width in here primarily - because of the downloads summary at the bottom of the list of - download items. An element in the summary has the same min-width - on a description, and we don't want the panel to change size if the - summary isn't being displayed, so we ensure that items share the - same minimum width. - --> - <xul:description class="downloadTarget" - crop="center" - style="min-width: &downloadsSummary.minWidth2;" - xbl:inherits="value=displayName,tooltiptext=displayName"/> - <xul:progressmeter anonid="progressmeter" - class="downloadProgress" - min="0" - max="100" - xbl:inherits="mode=progressmode,value=progress,paused=progresspaused"/> - <xul:description class="downloadDetails" - crop="end" - xbl:inherits="value=status,tooltiptext=statusTip"/> - </xul:vbox> - </xul:hbox> - <xul:toolbarseparator /> - <xul:stack class="downloadButtonArea"> - <xul:button class="downloadButton downloadCancel downloadIconCancel" - tooltiptext="&cmd.cancel.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/> - <xul:button class="downloadButton downloadRetry downloadIconRetry" - tooltiptext="&cmd.retry.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/> - <xul:button class="downloadButton downloadShow downloadIconShow" -#ifdef XP_MACOSX - tooltiptext="&cmd.showMac.label;" -#else - tooltiptext="&cmd.show.label;" -#endif - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/> - <xul:button class="downloadButton downloadConfirmBlock downloadIconCancel" - tooltiptext="&cmd.removeFile.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_confirmBlock');"/> - <xul:button class="downloadButton downloadChooseUnblock downloadIconShow" - tooltiptext="&cmd.chooseUnblock.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseUnblock');"/> - <xul:button class="downloadButton downloadChooseOpen downloadIconShow" - tooltiptext="&cmd.chooseOpen.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseOpen');"/> - <xul:button class="downloadButton downloadShowBlockedInfo" - tooltiptext="&cmd.chooseUnblock.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_showBlockedInfo');"/> - </xul:stack> - </content> - </binding> - - <binding id="download-toolbarbutton" - extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged"> - <content> - <xul:stack class="toolbarbutton-badge-stack"> - <children /> - <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/> - <xul:label class="toolbarbutton-badge" xbl:inherits="value=badge" top="0" end="0" crop="none"/> - </xul:stack> - <xul:label class="toolbarbutton-text" crop="right" flex="1" - xbl:inherits="value=label,accesskey,crop,wrap"/> - <xul:label class="toolbarbutton-multiline-text" flex="1" - xbl:inherits="xbl:text=label,accesskey,wrap"/> - </content> - </binding> -</bindings> diff --git a/browser/components/downloads/content/downloads.css b/browser/components/downloads/content/downloads.css deleted file mode 100644 index dd47c6f91..000000000 --- a/browser/components/downloads/content/downloads.css +++ /dev/null @@ -1,267 +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/. */ - -/*** Downloads Panel ***/ - -richlistitem[type="download"] { - -moz-binding: url('chrome://browser/content/downloads/download.xml#download'); -} - -richlistitem[type="download"]:not([selected]) button { - /* Only focus buttons in the selected item. */ - -moz-user-focus: none; -} - -.downloadsHideDropmarker > #downloadsFooterButtonsSplitter, -.downloadsHideDropmarker > #downloadsFooterDropmarker { - display: none; -} - -richlistitem[type="download"].download-state[state="1"]:not([exists]) > .downloadButtonArea, -richlistitem[type="download"].download-state[state="1"]:not([exists]) > toolbarseparator { - display: none; -} - -#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress, -#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails, -#downloadsFooter:not([showingsummary]) #downloadsSummary { - display: none; -} - -#downloadsFooter[showingdropdown] > stack > #downloadsSummary, -#downloadsFooter[showingsummary] > stack:hover > #downloadsSummary, -#downloadsFooter[showingsummary]:not([showingdropdown]) > stack:not(:hover) > #downloadsFooterButtons { - /* If we used "visibility: hidden;" then the mouseenter event of - #downloadsHistory wouldn't be triggered immediately, and the hover styling - of the button would not apply until the mouse is moved again. - - "-moz-user-focus: ignore;" prevents the elements with "opacity: 0;" from - being focused with the keyboard. */ - opacity: 0; - -moz-user-focus: ignore; -} - -/*** Downloads View ***/ - -/** - * The downloads richlistbox may list thousands of items, and it turns out - * XBL binding attachment, and even more so detachment, is a performance hog. - * This hack makes sure we don't apply any binding to inactive items (inactive - * items are history downloads that haven't been in the visible area). - * We can do this because the richlistbox implementation does not interact - * much with the richlistitem binding. However, this may turn out to have - * some side effects (see bug 828111 for the details). - * - * We might be able to do away with this workaround once bug 653881 is fixed. - */ -richlistitem.download { - -moz-binding: none; -} - -richlistitem.download[active] { - -moz-binding: url("chrome://browser/content/downloads/download.xml#download"); -} - -richlistitem.download button { - /* These buttons should never get focus, as that would "disable" - the downloads view controller (it's only used when the richlistbox - is focused). */ - -moz-user-focus: none; -} - -/*** Visibility of controls inside download items ***/ -.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */) - .downloadBlockedBadge, - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="5"], /* Starting (queued) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="7"]) /* Scanning */) - .downloadProgress, - -.download-state:not( [state="0"] /* Downloading */) - .downloadPauseMenuItem, - -.download-state:not( [state="4"] /* Paused */) - .downloadResumeMenuItem, - -/* Blocked (dirty) downloads that have not been confirmed and - have temporary data. */ -.download-state:not( [state="8"] /* Blocked (dirty) */) - .downloadUnblockMenuItem, -.download-state[state="8"]:not(.temporary-block) - .downloadUnblockMenuItem, - -.download-state:not(:-moz-any([state="2"], /* Failed */ - [state="4"]) /* Paused */) - .downloadCancelMenuItem, - -.download-state:not(:-moz-any([state="1"], /* Finished */ - [state="2"], /* Failed */ - [state="3"], /* Canceled */ - [state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */) - .downloadRemoveFromHistoryMenuItem, - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="1"], /* Finished */ - [state="4"], /* Paused */ - [state="5"]) /* Starting (queued) */) - .downloadShowMenuItem, - -.download-state[state="7"] .downloadCommandsSeparator - -{ - display: none; -} - -/*** Visibility of download buttons ***/ - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="5"], /* Starting (queued) */ - [state="0"], /* Downloading */ - [state="4"]) /* Paused */) - .downloadCancel, - -/* Blocked (dirty) downloads that have not been confirmed and - have temporary data, for the Malware case. */ -.download-state:not( [state="8"] /* Blocked (dirty) */) - .downloadConfirmBlock, -.download-state[state="8"]:not(.temporary-block) - .downloadConfirmBlock, -.download-state[state="8"].temporary-block:not([verdict="Malware"]) - .downloadConfirmBlock, - -/* Blocked (dirty) downloads that have not been confirmed and - have temporary data, for the Potentially Unwanted case. */ -.download-state:not( [state="8"] /* Blocked (dirty) */) - .downloadChooseUnblock, -.download-state[state="8"]:not(.temporary-block) - .downloadChooseUnblock, -.download-state[state="8"].temporary-block:not([verdict="PotentiallyUnwanted"]) - .downloadChooseUnblock, - -/* Blocked (dirty) downloads that have not been confirmed and - have temporary data, for the Uncommon case. */ -.download-state:not( [state="8"] /* Blocked (dirty) */) - .downloadChooseOpen, -.download-state[state="8"]:not(.temporary-block) - .downloadChooseOpen, -.download-state[state="8"].temporary-block:not([verdict="Uncommon"]) - .downloadChooseOpen, - -.download-state:not(:-moz-any([state="2"], /* Failed */ - [state="3"]) /* Canceled */) - .downloadRetry, - -.download-state:not( [state="1"] /* Finished */) - .downloadShow, - -.download-state:-moz-any( [state="6"], /* Blocked (parental) */ - [state="7"], /* Scanning */ - [state="9"]) /* Blocked (policy) */ - > toolbarseparator, - -/* The "show blocked info" button is shown only in the downloads panel. */ -.downloadShowBlockedInfo -{ - display: none; -} - -/*** Downloads panel ***/ - -#downloadsPanel[hasdownloads] #emptyDownloads, -#downloadsPanel:not([hasdownloads]) #downloadsListBox { - display: none; -} - -/*** Downloads panel multiview (main view and blocked-downloads subview) ***/ - -/* Hide all the usual buttons. */ -#downloadsPanel-mainView .download-state[state="8"] .downloadCancel, -#downloadsPanel-mainView .download-state[state="8"] .downloadConfirmBlock, -#downloadsPanel-mainView .download-state[state="8"] .downloadChooseUnblock, -#downloadsPanel-mainView .download-state[state="8"] .downloadChooseOpen, -#downloadsPanel-mainView .download-state[state="8"] .downloadRetry, -#downloadsPanel-mainView .download-state[state="8"] .downloadShow { - display: none; -} - -/* Make the panel wide enough to show the download list items without improperly - truncating them. */ -#downloadsPanel-multiView > .panel-viewcontainer, -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack, -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-mainview { - max-width: unset; -} - -/* Show the "show blocked info" button. */ -#downloadsPanel-mainView .download-state[state="8"] .downloadShowBlockedInfo { - display: inline; -} - -/** When the main view is showing... **/ - -/* The subview should be off to the right and not visible at all. */ -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews { - transform: translateX(101%); - transition: transform var(--panelui-subview-transition-duration); -} - -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews:-moz-locale-dir(rtl) { - transform: translateX(-101%); -} - -/** When the subview is showing... **/ - -/* Hide the buttons of all downloads except the one that triggered the - subview. */ -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state:not([showingsubview]) .downloadButton { - visibility: hidden; -} - -/* For the download that triggered the subview, move its button farther to the - right by removing padding so that a minimum amount of the main view's right - edge needs to be shown. */ -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] { - padding: 0; -} - -/* The main view should slide to the left and its right edge should remain - visible. */ -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=subview] > .panel-mainview { - transform: translateX(calc(-100% + 38px)); - transition: transform var(--panelui-subview-transition-duration); -} - -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=subview] > .panel-mainview:-moz-locale-dir(rtl) { - transform: translateX(calc(100% - 38px)); -} - -/* The subview should leave the right edge of the main view uncovered. */ -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews { - /* Use a margin instead of a transform like above so that the subview's width - isn't wider than the panel. */ - -moz-margin-start: 38px !important; -} - -/* Prevent keyboard interaction in the main view by preventing all elements in - the main view from being focused... */ -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview #downloadsListBox, -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview richlistitem, -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview .downloadButton, -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview .downloadsPanelFooterButton, -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview #downloadsSummary { - -moz-user-focus: ignore; -} -/* ... except for the downloadShowBlockedInfo button in the blocked download. - Selecting it with the keyboard should show the main view again. */ -#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadShowBlockedInfo { - -moz-user-focus: normal; -} diff --git a/browser/components/downloads/content/downloads.js b/browser/components/downloads/content/downloads.js deleted file mode 100644 index 57397c815..000000000 --- a/browser/components/downloads/content/downloads.js +++ /dev/null @@ -1,1732 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Handles the 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, and handles the user - * interaction events related to a single item in the downloads list widgets. - * - * 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. - */ - -/** - * 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"; - -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"); - -//////////////////////////////////////////////////////////////////////////////// -//// 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() { - return 0; - }, - /** This object is linked to data, but the panel is invisible. */ - get kStateHidden() { - return 1; - }, - /** The panel will be shown as soon as possible. */ - get kStateWaitingData() { - return 2; - }, - /** The panel is almost shown - we're just waiting to get a handle on the - anchor. */ - get kStateWaitingAnchor() { - return 3; - }, - /** The panel is open. */ - get kStateShown() { - return 4; - }, - - /** - * Location of the panel overlay. - */ - get kDownloadsOverlay() { - return "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(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); - - // Load and resume active downloads if required. If there are downloads to - // be shown in the panel, they will be loaded 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, () => { - 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() { - 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() { - DownloadsCommon.log("Opening the downloads panel."); - - if (this.isPanelShowing) { - DownloadsCommon.log("Panel is already showing - focusing instead."); - this._focusPanel(); - return; - } - - this.initialize(() => { - let downloadsFooterButtons = - document.getElementById("downloadsFooterButtons"); - if (DownloadsCommon.showPanelDropmarker) { - downloadsFooterButtons.classList.remove("downloadsHideDropmarker"); - } else { - downloadsFooterButtons.classList.add("downloadsHideDropmarker"); - } - - // 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(() => this._openPopupIfDataReady(), 0); - }); - - 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() { - 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(aEvent) { - switch (aEvent.type) { - case "mousemove": - this.keyFocusing = false; - break; - case "keydown": - return this._onKeyDown(aEvent); - case "keypress": - return this._onKeyPress(aEvent); - case "popupshown": - if (this.setHeightToFitOnShow) { - this.setHeightToFitOnShow = false; - this.setHeightToFit(); - } - break; - } - }, - - setHeightToFit() { - if (this._state == this.kStateShown) { - DownloadsBlockedSubview.view.setHeightToFit(); - } else { - this.setHeightToFitOnShow = true; - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Callback functions from DownloadsView - - /** - * Called after data loading finished. - */ - onViewLoadCompleted() { - this._openPopupIfDataReady(); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// User interface event functions - - onWindowUnload() { - // This function is registered as an event listener, we can't use "this". - DownloadsPanel.terminate(); - }, - - 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(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; - }, - - onFooterPopupShowing(aEvent) { - let itemClearList = document.getElementById("downloadsDropdownItemClearList"); - if (DownloadsCommon.getData(window).canRemoveFinished) { - itemClearList.removeAttribute("hidden"); - } else { - itemClearList.setAttribute("hidden", "true"); - } - DownloadsViewController.updateCommands(); - - document.getElementById("downloadsFooter") - .setAttribute("showingdropdown", true); - }, - - onFooterPopupHidden(aEvent) { - document.getElementById("downloadsFooter") - .removeAttribute("showingdropdown"); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Related operations - - /** - * Shows or focuses the user interface dedicated to downloads history. - */ - 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(); - }, - - openDownloadsFolder() { - Downloads.getPreferredDownloadsDirectory().then(downloadsPath => { - DownloadsCommon.showDirectory(new FileUtils.File(downloadsPath)); - }).catch(Cu.reportError); - this.hidePanel(); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// 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() { - // Handle keydown to support accel-V. - this.panel.addEventListener("keydown", this, false); - // Handle keypress to be able to preventDefault() events before they reach - // the richlistbox, for keyboard navigation. - this.panel.addEventListener("keypress", this, false); - // Handle height adjustment on show. - this.panel.addEventListener("popupshown", this, false); - }, - - /** - * Unattach event listeners that were added in _attachEventListeners. This - * is called automatically on panel termination. - */ - _unattachEventListeners() { - this.panel.removeEventListener("keydown", this, false); - this.panel.removeEventListener("keypress", this, false); - this.panel.removeEventListener("popupshown", this, false); - }, - - _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(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 && - aEvent.getModifierState("Accel"); - - 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() { - // 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() { - // 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(anchor => { - // 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; - } - - if (!anchor) { - DownloadsCommon.error("Downloads button cannot be found."); - 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); - } - - DownloadsCommon.log("Opening downloads panel popup."); - this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, null); - }); - }, -}; - -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(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; - } - - this._overlayLoading = true; - DownloadsCommon.log("Loading overlay ", aOverlay); - document.loadOverlay(aOverlay, () => { - this._overlayLoading = false; - this._loadedOverlays[aOverlay] = true; - - this.processPendingRequests(); - }); - }, - - /** - * 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() { - // 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: 5, - - /** - * Indicates whether there is an open contextMenu for a download item. - */ - contextMenuOpen: false, - - /** - * Indicates whether there is a DownloadsBlockedSubview open. - */ - subViewOpen: false, - - /** - * 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() { - 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() { - DownloadsCommon.log("onDataLoadStarting called for DownloadsView."); - this.loading = true; - }, - - /** - * Called after data loading finished. - */ - 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 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(); - - // Adjust the panel height if we removed items. - DownloadsPanel.setHeightToFit(); - }, - - /** - * Associates each richlistitem for a download with its corresponding - * DownloadsViewItem object. - */ - _itemsForElements: new Map(), - - itemForElement(element) { - return this._itemsForElements.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); - this._itemsForElements.set(element, viewItem); - 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._itemsForElements.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(aEvent, aCommand) { - let target = aEvent.target; - while (target.nodeName != "richlistitem") { - target = target.parentNode; - } - DownloadsView.itemForElement(target).doCommand(aCommand); - }, - - onDownloadClick(aEvent) { - // Handle primary clicks only, and exclude the action button. - if (aEvent.button == 0 && - !aEvent.originalTarget.hasAttribute("oncommand")) { - let target = aEvent.target; - while (target.nodeName != "richlistitem") { - target = target.parentNode; - } - let download = DownloadsView.itemForElement(target).download; - if (download.hasBlockedData) { - goDoCommand("downloadsCmd_showBlockedInfo"); - } else { - goDoCommand("downloadsCmd_open"); - } - } - }, - - /** - * Handles keypress events on a download item. - */ - 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_RETURN) { - goDoCommand("downloadsCmd_doDefault"); - } - }, - - /** - * Event handlers to keep track of context menu state (open/closed) for - * download items. - */ - onContextPopupShown(aEvent) { - // Ignore events raised by nested popups. - if (aEvent.target != aEvent.currentTarget) { - return; - } - - DownloadsCommon.log("Context menu has shown."); - this.contextMenuOpen = true; - }, - - onContextPopupHidden(aEvent) { - // Ignore events raised by nested popups. - if (aEvent.target != aEvent.currentTarget) { - return; - } - - DownloadsCommon.log("Context menu has hidden."); - this.contextMenuOpen = false; - }, - - /** - * Mouse listeners to handle selection on hover. - */ - onDownloadMouseOver(aEvent) { - if (!(this.contextMenuOpen || this.subViewOpen) && - aEvent.target.parentNode == this.richListBox) { - this.richListBox.selectedItem = aEvent.target; - } - }, - - onDownloadMouseOut(aEvent) { - if (!(this.contextMenuOpen || this.subViewOpen) && - aEvent.target.parentNode == this.richListBox) { - // If the destination element is outside of the richlistitem, clear the - // selection. - let element = aEvent.relatedTarget; - while (element && element != aEvent.target) { - element = element.parentNode; - } - if (!element) { - this.richListBox.selectedIndex = -1; - } - } - }, - - 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")); - contextMenu.classList.toggle("temporary-block", - element.classList.contains("temporary-block")); - }, - - onDownloadDragStart(aEvent) { - let element = this.richListBox.selectedItem; - if (!element) { - return; - } - - // We must check for existence synchronously because this is a DOM event. - let file = new FileUtils.File(DownloadsView.itemForElement(element) - .download.target.path); - if (!file.exists()) { - return; - } - - let dataTransfer = aEvent.dataTransfer; - dataTransfer.mozSetDataAt("application/x-moz-file", file, 0); - dataTransfer.effectAllowed = "copyMove"; - let spec = NetUtil.newURI(file).spec; - dataTransfer.setData("text/uri-list", spec); - dataTransfer.setData("text/plain", spec); - 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, and handles the user - * interaction events related to a single item in the downloads list widgets. - * - * @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._updateState(); - }, - - onChanged() { - this._updateProgress(); - }, - - 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 "cmd_delete": - case "downloadsCmd_cancel": - case "downloadsCmd_copyLocation": - case "downloadsCmd_doDefault": - return true; - case "downloadsCmd_showBlockedInfo": - return this.download.hasBlockedData; - } - return DownloadsViewUI.DownloadElementShell.prototype - .isCommandEnabled.call(this, aCommand); - }, - - doCommand(aCommand) { - if (this.isCommandEnabled(aCommand)) { - this[aCommand](); - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Item commands - - cmd_delete() { - DownloadsCommon.removeAndFinalizeDownload(this.download); - PlacesUtils.bhistory.removePage( - NetUtil.newURI(this.download.source.url)); - }, - - downloadsCmd_unblock() { - DownloadsPanel.hidePanel(); - this.confirmUnblock(window, "unblock"); - }, - - downloadsCmd_chooseUnblock() { - DownloadsPanel.hidePanel(); - this.confirmUnblock(window, "chooseUnblock"); - }, - - downloadsCmd_unblockAndOpen() { - DownloadsPanel.hidePanel(); - this.unblockAndOpenDownload().catch(Cu.reportError); - }, - - 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() { - 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_showBlockedInfo() { - DownloadsBlockedSubview.toggle(this.element, - ...this.rawBlockedTitleAndDetails); - }, - - downloadsCmd_openReferrer() { - openURL(this.download.source.referrer); - }, - - downloadsCmd_copyLocation() { - let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] - .getService(Ci.nsIClipboardHelper); - clipboard.copyString(this.download.source.url); - }, - - downloadsCmd_doDefault() { - let defaultCommand = this.currentDefaultCommandName; - if (defaultCommand && this.isCommandEnabled(defaultCommand)) { - this.doCommand(defaultCommand); - } - }, -}; - -//////////////////////////////////////////////////////////////////////////////// -//// 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() { - window.controllers.insertControllerAt(0, this); - }, - - terminate() { - window.controllers.removeController(this); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// nsIController - - supportsCommand(aCommand) { - if (aCommand === "downloadsCmd_clearList") { - return true; - } - // Firstly, determine if this is a command that we can handle. - if (!DownloadsViewUI.isCommandName(aCommand)) { - return false; - } - if (!(aCommand in this) && - !(aCommand in DownloadsViewItem.prototype)) { - return false; - } - // The currently supported commands depend on whether the blocked subview is - // showing. If it is, then take the following path. - if (DownloadsBlockedSubview.view.showingSubView) { - let blockedSubviewCmds = [ - "downloadsCmd_unblockAndOpen", - "cmd_delete", - ]; - return blockedSubviewCmds.indexOf(aCommand) >= 0; - } - // If the blocked subview is not showing, then 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(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.itemForElement(element) - .isCommandEnabled(aCommand); - }, - - doCommand(aCommand) { - // If this command is not selection-specific, execute it. - if (aCommand in this) { - this[aCommand](); - return; - } - - // Other commands are selection-specific. - let element = DownloadsView.richListBox.selectedItem; - if (element) { - // The doCommand function also checks if the command is enabled. - DownloadsView.itemForElement(element).doCommand(aCommand); - } - }, - - onEvent() {}, - - ////////////////////////////////////////////////////////////////////////////// - //// Other functions - - updateCommands() { - function updateCommandsForObject(object) { - for (let name in object) { - if (DownloadsViewUI.isCommandName(name)) { - goUpdateCommand(name); - } - } - } - updateCommandsForObject(this); - updateCommandsForObject(DownloadsViewItem.prototype); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Selection-independent commands - - downloadsCmd_clearList() { - DownloadsCommon.getData(window).removeFinished(); - }, -}; - -XPCOMUtils.defineConstant(this, "DownloadsViewController", DownloadsViewController); - -//////////////////////////////////////////////////////////////////////////////// -//// 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() { - return 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() { - if (this._summaryNode) { - this._summaryNode.focus(); - } - }, - - /** - * Respond to keydown events on the Downloads Summary node. - * - * @param aEvent - * The keydown event being handled. - */ - onKeyDown(aEvent) { - if (aEvent.charCode == " ".charCodeAt(0) || - 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(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() { - 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"); - } - if (!aValue && this._showingSummary) { - // Make sure the panel's height shrinks when the summary is hidden. - DownloadsPanel.setHeightToFit(); - } - 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); - - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsBlockedSubview - -/** - * Manages the blocked subview that slides in when you click a blocked download. - */ -const DownloadsBlockedSubview = { - - get subview() { - let subview = document.getElementById("downloadsPanel-blockedSubview"); - delete this.subview; - return this.subview = subview; - }, - - /** - * Elements in the subview. - */ - get elements() { - let idSuffixes = [ - "title", - "details1", - "details2", - "openButton", - "deleteButton", - ]; - let elements = idSuffixes.reduce((memo, s) => { - memo[s] = document.getElementById("downloadsPanel-blockedSubview-" + s); - return memo; - }, {}); - delete this.elements; - return this.elements = elements; - }, - - /** - * The multiview that contains both the main view and the subview. - */ - get view() { - let view = document.getElementById("downloadsPanel-multiView"); - delete this.view; - return this.view = view; - }, - - /** - * The blocked-download richlistitem element that was clicked to show the - * subview. If the subview is not showing, this is undefined. - */ - element: undefined, - - /** - * Slides in the blocked subview. - * - * @param element - * The blocked-download richlistitem element that was clicked. - * @param title - * The title to show in the subview. - * @param details - * An array of strings with information about the block. - */ - toggle(element, title, details) { - if (this.view.showingSubView) { - this.hide(); - return; - } - - this.element = element; - element.setAttribute("showingsubview", "true"); - DownloadsView.subViewOpen = true; - DownloadsViewController.updateCommands(); - - let e = this.elements; - let s = DownloadsCommon.strings; - e.title.textContent = title; - e.details1.textContent = details[0]; - e.details2.textContent = details[1]; - e.openButton.label = s.unblockButtonOpen; - e.deleteButton.label = s.unblockButtonConfirmBlock; - - let verdict = element.getAttribute("verdict"); - this.subview.setAttribute("verdict", verdict); - this.subview.addEventListener("ViewHiding", this); - - this.view.showSubView(this.subview.id); - - // Without this, the mainView is more narrow than the panel once all - // downloads are removed from the panel. - document.getElementById("downloadsPanel-mainView").style.minWidth = - window.getComputedStyle(this.view).width; - }, - - handleEvent(event) { - switch (event.type) { - case "ViewHiding": - this.subview.removeEventListener(event.type, this); - this.element.removeAttribute("showingsubview"); - DownloadsView.subViewOpen = false; - delete this.element; - break; - default: - DownloadsCommon.log("Unhandled DownloadsBlockedSubview event: " + - event.type); - break; - } - }, - - /** - * Slides out the blocked subview and shows the main view. - */ - hide() { - this.view.showMainView(); - // The point of this is to focus the proper element in the panel now that - // the main view is showing again. showPanel handles that. - DownloadsPanel.showPanel(); - }, - - /** - * Deletes the download and hides the entire panel. - */ - confirmBlock() { - goDoCommand("cmd_delete"); - DownloadsPanel.hidePanel(); - }, -}; - -XPCOMUtils.defineConstant(this, "DownloadsBlockedSubview", - DownloadsBlockedSubview); diff --git a/browser/components/downloads/content/downloadsOverlay.xul b/browser/components/downloads/content/downloadsOverlay.xul deleted file mode 100644 index 9fe2ee022..000000000 --- a/browser/components/downloads/content/downloadsOverlay.xul +++ /dev/null @@ -1,210 +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_unblock" - oncommand="goDoCommand('downloadsCmd_unblock')"/> - <command id="downloadsCmd_chooseUnblock" - oncommand="goDoCommand('downloadsCmd_chooseUnblock')"/> - <command id="downloadsCmd_unblockAndOpen" - oncommand="goDoCommand('downloadsCmd_unblockAndOpen')"/> - <command id="downloadsCmd_confirmBlock" - oncommand="goDoCommand('downloadsCmd_confirmBlock')"/> - <command id="downloadsCmd_open" - oncommand="goDoCommand('downloadsCmd_open')"/> - <command id="downloadsCmd_show" - oncommand="goDoCommand('downloadsCmd_show')"/> - <command id="downloadsCmd_retry" - oncommand="goDoCommand('downloadsCmd_retry')"/> - <command id="downloadsCmd_openReferrer" - oncommand="goDoCommand('downloadsCmd_openReferrer')"/> - <command id="downloadsCmd_copyLocation" - oncommand="goDoCommand('downloadsCmd_copyLocation')"/> - <command id="downloadsCmd_clearList" - oncommand="goDoCommand('downloadsCmd_clearList')"/> - </commandset> - - <popupset id="mainPopupSet"> - <!-- The panel has level="top" to ensure that it is never hidden by the - taskbar on Windows. See bug 672365. For accessibility to screen - readers, we use a label on the panel instead of the anchor because the - panel can also be displayed without an anchor. --> - <panel id="downloadsPanel" - aria-label="&downloads.title;" - role="group" - type="arrow" - orient="vertical" - level="top" - onpopupshown="DownloadsPanel.onPopupShown(event);" - onpopuphidden="DownloadsPanel.onPopupHidden(event);"> - <!-- The following popup menu should be a child of the panel element, - otherwise flickering may occur when the cursor is moved over the area - of a disabled menu item that overlaps the panel. See bug 492960. --> - <menupopup id="downloadsContextMenu" - onpopupshown="DownloadsView.onContextPopupShown(event);" - onpopuphidden="DownloadsView.onContextPopupHidden(event);" - class="download-state"> - <menuitem command="downloadsCmd_pauseResume" - class="downloadPauseMenuItem" - label="&cmd.pause.label;" - accesskey="&cmd.pause.accesskey;"/> - <menuitem command="downloadsCmd_pauseResume" - class="downloadResumeMenuItem" - label="&cmd.resume.label;" - accesskey="&cmd.resume.accesskey;"/> - <menuitem command="downloadsCmd_cancel" - class="downloadCancelMenuItem" - label="&cmd.cancel.label;" - accesskey="&cmd.cancel.accesskey;"/> - <menuitem command="downloadsCmd_unblock" - class="downloadUnblockMenuItem" - label="&cmd.unblock2.label;" - accesskey="&cmd.unblock2.accesskey;"/> - <menuitem command="cmd_delete" - class="downloadRemoveFromHistoryMenuItem" - label="&cmd.removeFromHistory.label;" - accesskey="&cmd.removeFromHistory.accesskey;"/> - <menuitem command="downloadsCmd_show" - class="downloadShowMenuItem" -#ifdef XP_MACOSX - label="&cmd.showMac.label;" - accesskey="&cmd.showMac.accesskey;" -#else - label="&cmd.show.label;" - accesskey="&cmd.show.accesskey;" -#endif - /> - - <menuseparator class="downloadCommandsSeparator"/> - - <menuitem command="downloadsCmd_openReferrer" - label="&cmd.goToDownloadPage.label;" - accesskey="&cmd.goToDownloadPage.accesskey;"/> - <menuitem command="downloadsCmd_copyLocation" - label="&cmd.copyDownloadLink.label;" - accesskey="&cmd.copyDownloadLink.accesskey;"/> - - <menuseparator/> - - <menuitem command="downloadsCmd_clearList" - label="&cmd.clearList2.label;" - accesskey="&cmd.clearList2.accesskey;"/> - </menupopup> - - <panelmultiview id="downloadsPanel-multiView" - mainViewId="downloadsPanel-mainView" - align="stretch"> - - <panelview id="downloadsPanel-mainView" - flex="1" - align="stretch"> - <richlistbox id="downloadsListBox" - context="downloadsContextMenu" - onmouseover="DownloadsView.onDownloadMouseOver(event);" - onmouseout="DownloadsView.onDownloadMouseOut(event);" - oncontextmenu="DownloadsView.onDownloadContextMenu(event);" - ondragstart="DownloadsView.onDownloadDragStart(event);"/> - <description id="emptyDownloads" - mousethrough="always"> - &downloadsPanelEmpty.label; - </description> - <spacer flex="1"/> - <vbox id="downloadsFooter" - class="downloadsPanelFooter"> - <stack> - <hbox id="downloadsSummary" - align="center" - orient="horizontal" - onkeydown="DownloadsSummary.onKeyDown(event);" - onclick="DownloadsSummary.onClick(event);"> - <image class="downloadTypeIcon" /> - <vbox pack="center" - class="downloadContainer" - style="width: &downloadDetails.width;"> - <description id="downloadsSummaryDescription" - style="min-width: &downloadsSummary.minWidth2;"/> - <progressmeter id="downloadsSummaryProgress" - class="downloadProgress" - min="0" - max="100" - mode="normal" /> - <description id="downloadsSummaryDetails" - crop="end"/> - </vbox> - </hbox> - <hbox id="downloadsFooterButtons"> - <button id="downloadsHistory" - class="downloadsPanelFooterButton" - label="&downloadsHistory.label;" - accesskey="&downloadsHistory.accesskey;" - flex="1" - oncommand="DownloadsPanel.showDownloadsHistory();"/> - <toolbarseparator id="downloadsFooterButtonsSplitter" - class="downloadsDropmarkerSplitter"/> - <button id="downloadsFooterDropmarker" - class="downloadsPanelFooterButton downloadsDropmarker" - type="menu"> - <menupopup id="downloadSubPanel" - onpopupshowing="DownloadsPanel.onFooterPopupShowing(event);" - onpopuphidden="DownloadsPanel.onFooterPopupHidden(event);" - position="after_end"> - <menuitem id="downloadsDropdownItemClearList" - command="downloadsCmd_clearList" - label="&cmd.clearList2.label;"/> - <menuitem id="downloadsDropdownItemOpenDownloadsFolder" - oncommand="DownloadsPanel.openDownloadsFolder();" - label="&openDownloadsFolder.label;"/> - </menupopup> - </button> - </hbox> - </stack> - </vbox> - </panelview> - - <panelview id="downloadsPanel-blockedSubview" - orient="vertical" - flex="1"> - <description id="downloadsPanel-blockedSubview-title"/> - <description id="downloadsPanel-blockedSubview-details1"/> - <description id="downloadsPanel-blockedSubview-details2"/> - <spacer flex="1"/> - <hbox id="downloadsPanel-blockedSubview-buttons" - class="downloadsPanelFooter" - align="stretch"> - <button id="downloadsPanel-blockedSubview-openButton" - class="downloadsPanelFooterButton" - command="downloadsCmd_unblockAndOpen" - flex="1"/> - <toolbarseparator/> - <button id="downloadsPanel-blockedSubview-deleteButton" - class="downloadsPanelFooterButton" - oncommand="DownloadsBlockedSubview.confirmBlock();" - default="true" - flex="1"/> - </hbox> - </panelview> - - </panelmultiview> - - </panel> - </popupset> -</overlay> diff --git a/browser/components/downloads/content/indicator.js b/browser/components/downloads/content/indicator.js deleted file mode 100644 index 4c22a6e5d..000000000 --- a/browser/components/downloads/content/indicator.js +++ /dev/null @@ -1,606 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Handles the indicator that displays the progress of ongoing downloads, which - * is also used as the anchor for the downloads panel. - * - * This module includes the following constructors and global objects: - * - * DownloadsButton - * Main entry point for the downloads indicator. Depending on how the toolbars - * have been customized, this object determines if we should show a fully - * functional indicator, a placeholder used during customization and in the - * customization palette, or a neutral view as a temporary anchor for the - * downloads panel. - * - * DownloadsIndicatorView - * Builds and updates the actual downloads status widget, responding to changes - * in the global status data, or provides a neutral view if the indicator is - * removed from the toolbars and only used as a temporary anchor. In addition, - * handles the user interaction events raised by the widget. - */ - -"use strict"; - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsButton - -/** - * Main entry point for the downloads indicator. Depending on how the toolbars - * have been customized, this object determines if we should show a fully - * functional indicator, a placeholder used during customization and in the - * customization palette, or a neutral view as a temporary anchor for the - * downloads panel. - */ -const DownloadsButton = { - /** - * Location of the indicator overlay. - */ - get kIndicatorOverlay() { - return "chrome://browser/content/downloads/indicatorOverlay.xul"; - }, - - /** - * Returns a reference to the downloads button position placeholder, or null - * if not available because it has been removed from the toolbars. - */ - get _placeholder() { - return document.getElementById("downloads-button"); - }, - - /** - * This function is called asynchronously just after window initialization. - * - * NOTE: This function should limit the input/output it performs to improve - * startup time. - */ - initializeIndicator() { - DownloadsIndicatorView.ensureInitialized(); - }, - - /** - * Indicates whether toolbar customization is in progress. - */ - _customizing: false, - - /** - * This function is called when toolbar customization starts. - * - * During customization, we never show the actual download progress indication - * or the event notifications, but we show a neutral placeholder. The neutral - * placeholder is an ordinary button defined in the browser window that can be - * moved freely between the toolbars and the customization palette. - */ - customizeStart() { - // Prevent the indicator from being displayed as a temporary anchor - // during customization, even if requested using the getAnchor method. - this._customizing = true; - this._anchorRequested = false; - }, - - /** - * This function is called when toolbar customization ends. - */ - customizeDone() { - this._customizing = false; - DownloadsIndicatorView.afterCustomize(); - }, - - /** - * Determines the position where the indicator should appear, and moves its - * associated element to the new position. - * - * @return Anchor element, or null if the indicator is not visible. - */ - _getAnchorInternal() { - let indicator = DownloadsIndicatorView.indicator; - if (!indicator) { - // Exit now if the indicator overlay isn't loaded yet, or if the button - // is not in the document. - return null; - } - - indicator.open = this._anchorRequested; - - let widget = CustomizableUI.getWidget("downloads-button") - .forWindow(window); - // Determine if the indicator is located on an invisible toolbar. - if (!isElementVisible(indicator.parentNode) && !widget.overflowed) { - return null; - } - - return DownloadsIndicatorView.indicatorAnchor; - }, - - /** - * Checks whether the indicator is, or will soon be visible in the browser - * window. - * - * @param aCallback - * Called once the indicator overlay has loaded. Gets a boolean - * argument representing the indicator visibility. - */ - checkIsVisible(aCallback) { - DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay, () => { - if (!this._placeholder) { - aCallback(false); - } else { - let element = DownloadsIndicatorView.indicator || this._placeholder; - aCallback(isElementVisible(element.parentNode)); - } - }); - }, - - /** - * Indicates whether we should try and show the indicator temporarily as an - * anchor for the panel, even if the indicator would be hidden by default. - */ - _anchorRequested: false, - - /** - * Ensures that there is an anchor available for the panel. - * - * @param aCallback - * Called when the anchor is available, passing the element where the - * panel should be anchored, or null if an anchor is not available (for - * example because both the tab bar and the navigation bar are hidden). - */ - getAnchor(aCallback) { - // Do not allow anchoring the panel to the element while customizing. - if (this._customizing) { - aCallback(null); - return; - } - - DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay, () => { - this._anchorRequested = true; - aCallback(this._getAnchorInternal()); - }); - }, - - /** - * Allows the temporary anchor to be hidden. - */ - releaseAnchor() { - this._anchorRequested = false; - this._getAnchorInternal(); - }, - - get _tabsToolbar() { - delete this._tabsToolbar; - return this._tabsToolbar = document.getElementById("TabsToolbar"); - }, - - get _navBar() { - delete this._navBar; - return this._navBar = document.getElementById("nav-bar"); - } -}; - -Object.defineProperty(this, "DownloadsButton", { - value: DownloadsButton, - enumerable: true, - writable: false -}); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsIndicatorView - -/** - * Builds and updates the actual downloads status widget, responding to changes - * in the global status data, or provides a neutral view if the indicator is - * removed from the toolbars and only used as a temporary anchor. In addition, - * handles the user interaction events raised by the widget. - */ -const DownloadsIndicatorView = { - /** - * True when the view is connected with the underlying downloads data. - */ - _initialized: false, - - /** - * True when the user interface elements required to display the indicator - * have finished loading in the browser window, and can be referenced. - */ - _operational: false, - - /** - * Prepares the downloads indicator to be displayed. - */ - ensureInitialized() { - if (this._initialized) { - return; - } - this._initialized = true; - - window.addEventListener("unload", this.onWindowUnload, false); - DownloadsCommon.getIndicatorData(window).addView(this); - }, - - /** - * Frees the internal resources related to the indicator. - */ - ensureTerminated() { - if (!this._initialized) { - return; - } - this._initialized = false; - - window.removeEventListener("unload", this.onWindowUnload, false); - DownloadsCommon.getIndicatorData(window).removeView(this); - - // Reset the view properties, so that a neutral indicator is displayed if we - // are visible only temporarily as an anchor. - this.counter = ""; - this.percentComplete = 0; - this.paused = false; - this.attention = DownloadsCommon.ATTENTION_NONE; - }, - - /** - * Ensures that the user interface elements required to display the indicator - * are loaded, then invokes the given callback. - */ - _ensureOperational(aCallback) { - if (this._operational) { - if (aCallback) { - aCallback(); - } - return; - } - - // If we don't have a _placeholder, there's no chance that the overlay - // will load correctly: bail (and don't set _operational to true!) - if (!DownloadsButton._placeholder) { - return; - } - - DownloadsOverlayLoader.ensureOverlayLoaded( - DownloadsButton.kIndicatorOverlay, - () => { - this._operational = true; - - // If the view is initialized, we need to update the elements now that - // they are finally available in the document. - if (this._initialized) { - DownloadsCommon.getIndicatorData(window).refreshView(this); - } - - if (aCallback) { - aCallback(); - } - }); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Direct control functions - - /** - * Set while we are waiting for a notification to fade out. - */ - _notificationTimeout: null, - - /** - * Check if the panel containing aNode is open. - * @param aNode - * the node whose panel we're interested in. - */ - _isAncestorPanelOpen(aNode) { - while (aNode && aNode.localName != "panel") { - aNode = aNode.parentNode; - } - return aNode && aNode.state == "open"; - }, - - /** - * If the status indicator is visible in its assigned position, shows for a - * brief time a visual notification of a relevant event, like a new download. - * - * @param aType - * Set to "start" for new downloads, "finish" for completed downloads. - */ - showEventNotification(aType) { - if (!this._initialized) { - return; - } - - if (!DownloadsCommon.animateNotifications) { - return; - } - - // No need to show visual notification if the panel is visible. - if (DownloadsPanel.isPanelShowing) { - return; - } - - let anchor = DownloadsButton._placeholder; - let widgetGroup = CustomizableUI.getWidget("downloads-button"); - let widget = widgetGroup.forWindow(window); - if (widget.overflowed || widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) { - if (anchor && this._isAncestorPanelOpen(anchor)) { - // If the containing panel is open, don't do anything, because the - // notification would appear under the open panel. See - // https://bugzilla.mozilla.org/show_bug.cgi?id=984023 - return; - } - - // Otherwise, try to use the anchor of the panel: - anchor = widget.anchor; - } - if (!anchor || !isElementVisible(anchor.parentNode)) { - // Our container isn't visible, so can't show the animation: - return; - } - - if (this._notificationTimeout) { - clearTimeout(this._notificationTimeout); - } - - // The notification element is positioned to show in the same location as - // the downloads button. It's not in the downloads button itself in order to - // be able to anchor the notification elsewhere if required, and to ensure - // the notification isn't clipped by overflow properties of the anchor's - // container. - let notifier = this.notifier; - if (notifier.style.transform == '') { - let anchorRect = anchor.getBoundingClientRect(); - let notifierRect = notifier.getBoundingClientRect(); - let topDiff = anchorRect.top - notifierRect.top; - let leftDiff = anchorRect.left - notifierRect.left; - let heightDiff = anchorRect.height - notifierRect.height; - let widthDiff = anchorRect.width - notifierRect.width; - let translateX = (leftDiff + .5 * widthDiff) + "px"; - let translateY = (topDiff + .5 * heightDiff) + "px"; - notifier.style.transform = "translate(" + translateX + ", " + translateY + ")"; - } - notifier.setAttribute("notification", aType); - this._notificationTimeout = setTimeout(() => { - notifier.removeAttribute("notification"); - notifier.style.transform = ''; - }, 1000); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Callback functions from DownloadsIndicatorData - - /** - * Indicates whether the indicator should be shown because there are some - * downloads to be displayed. - */ - set hasDownloads(aValue) { - if (this._hasDownloads != aValue || (!this._operational && aValue)) { - this._hasDownloads = aValue; - - // If there is at least one download, ensure that the view elements are - if (aValue) { - this._ensureOperational(); - } - } - return aValue; - }, - get hasDownloads() { - return this._hasDownloads; - }, - _hasDownloads: false, - - /** - * Status text displayed in the indicator. If this is set to an empty value, - * then the small downloads icon is displayed instead of the text. - */ - set counter(aValue) { - if (!this._operational) { - return this._counter; - } - - if (this._counter !== aValue) { - this._counter = aValue; - if (this._counter) - this.indicator.setAttribute("counter", "true"); - else - this.indicator.removeAttribute("counter"); - // We have to set the attribute instead of using the property because the - // XBL binding isn't applied if the element is invisible for any reason. - this._indicatorCounter.setAttribute("value", aValue); - } - return aValue; - }, - _counter: null, - - /** - * Progress indication to display, from 0 to 100, or -1 if unknown. The - * progress bar is hidden if the current progress is unknown and no status - * text is set in the "counter" property. - */ - set percentComplete(aValue) { - if (!this._operational) { - return this._percentComplete; - } - - if (this._percentComplete !== aValue) { - this._percentComplete = aValue; - if (this._percentComplete >= 0) - this.indicator.setAttribute("progress", "true"); - else - this.indicator.removeAttribute("progress"); - // We have to set the attribute instead of using the property because the - // XBL binding isn't applied if the element is invisible for any reason. - this._indicatorProgress.setAttribute("value", Math.max(aValue, 0)); - } - return aValue; - }, - _percentComplete: null, - - /** - * Indicates whether the progress won't advance because of a paused state. - * Setting this property forces a paused progress bar to be displayed, even if - * the current progress information is unavailable. - */ - set paused(aValue) { - if (!this._operational) { - return this._paused; - } - - if (this._paused != aValue) { - this._paused = aValue; - if (this._paused) { - this.indicator.setAttribute("paused", "true") - } else { - this.indicator.removeAttribute("paused"); - } - } - return aValue; - }, - _paused: false, - - /** - * Set when the indicator should draw user attention to itself. - */ - set attention(aValue) { - if (!this._operational) { - return this._attention; - } - - if (this._attention != aValue) { - this._attention = aValue; - - // Check if the downloads button is in the menu panel, to determine which - // button needs to get a badge. - let widgetGroup = CustomizableUI.getWidget("downloads-button"); - let inMenu = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL; - - if (aValue == DownloadsCommon.ATTENTION_NONE) { - this.indicator.removeAttribute("attention"); - if (inMenu) { - gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD); - } - } else { - this.indicator.setAttribute("attention", aValue); - if (inMenu) { - let badgeClass = "download-" + aValue; - gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, badgeClass); - } - } - } - return aValue; - }, - _attention: DownloadsCommon.ATTENTION_NONE, - - ////////////////////////////////////////////////////////////////////////////// - //// User interface event functions - - onWindowUnload() { - // This function is registered as an event listener, we can't use "this". - DownloadsIndicatorView.ensureTerminated(); - }, - - onCommand(aEvent) { - // If the downloads button is in the menu panel, open the Library - let widgetGroup = CustomizableUI.getWidget("downloads-button"); - if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) { - DownloadsPanel.showDownloadsHistory(); - } else { - DownloadsPanel.showPanel(); - } - - aEvent.stopPropagation(); - }, - - onDragOver(aEvent) { - browserDragAndDrop.dragOver(aEvent); - }, - - onDrop(aEvent) { - let dt = aEvent.dataTransfer; - // If dragged item is from our source, do not try to - // redownload already downloaded file. - if (dt.mozGetDataAt("application/x-moz-file", 0)) - return; - - let links = browserDragAndDrop.dropLinks(aEvent); - if (!links.length) - return; - let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document; - let handled = false; - for (let link of links) { - if (link.url.startsWith("about:")) - continue; - saveURL(link.url, link.name, null, true, true, null, sourceDoc); - handled = true; - } - if (handled) { - aEvent.preventDefault(); - } - }, - - _indicator: null, - __indicatorCounter: null, - __indicatorProgress: null, - - /** - * Returns a reference to the main indicator element, or null if the element - * is not present in the browser window yet. - */ - get indicator() { - if (this._indicator) { - return this._indicator; - } - - let indicator = document.getElementById("downloads-button"); - if (!indicator || indicator.getAttribute("indicator") != "true") { - return null; - } - - return this._indicator = indicator; - }, - - get indicatorAnchor() { - let widget = CustomizableUI.getWidget("downloads-button") - .forWindow(window); - if (widget.overflowed) { - return widget.anchor; - } - return document.getElementById("downloads-indicator-anchor"); - }, - - get _indicatorCounter() { - return this.__indicatorCounter || - (this.__indicatorCounter = document.getElementById("downloads-indicator-counter")); - }, - - get _indicatorProgress() { - return this.__indicatorProgress || - (this.__indicatorProgress = document.getElementById("downloads-indicator-progress")); - }, - - get notifier() { - return this._notifier || - (this._notifier = document.getElementById("downloads-notification-anchor")); - }, - - _onCustomizedAway() { - this._indicator = null; - this.__indicatorCounter = null; - this.__indicatorProgress = null; - }, - - afterCustomize() { - // If the cached indicator is not the one currently in the document, - // invalidate our references - if (this._indicator != document.getElementById("downloads-button")) { - this._onCustomizedAway(); - this._operational = false; - this.ensureTerminated(); - this.ensureInitialized(); - } - }, -}; - -Object.defineProperty(this, "DownloadsIndicatorView", { - value: DownloadsIndicatorView, - enumerable: true, - writable: false -}); diff --git a/browser/components/downloads/content/indicatorOverlay.xul b/browser/components/downloads/content/indicatorOverlay.xul deleted file mode 100644 index 07987c88c..000000000 --- a/browser/components/downloads/content/indicatorOverlay.xul +++ /dev/null @@ -1,36 +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 overlay [ - <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" > - %browserDTD; -]> - -<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - id="indicatorOverlay"> - - <!-- We dynamically add the stack with the progress meter and notification icon, - originally loaded lazily because of performance reasons, to the existing - downloads-button. --> - <toolbarbutton id="downloads-button" indicator="true"> - <!-- The panel's anchor area is smaller than the outer button, but must - always be visible and must not move or resize when the indicator - state changes, otherwise the panel could change its position or lose - its arrow unexpectedly. --> - <stack id="downloads-indicator-anchor" - consumeanchor="downloads-button"> - <vbox id="downloads-indicator-progress-area" pack="center"> - <description id="downloads-indicator-counter"/> - <progressmeter id="downloads-indicator-progress" class="plain" - min="0" max="100"/> - </vbox> - <vbox id="downloads-indicator-icon"/> - </stack> - </toolbarbutton> -</overlay> |