michael@0: # -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Globals michael@0: michael@0: const PREF_BDM_CLOSEWHENDONE = "browser.download.manager.closeWhenDone"; michael@0: const PREF_BDM_ALERTONEXEOPEN = "browser.download.manager.alertOnEXEOpen"; michael@0: const PREF_BDM_SCANWHENDONE = "browser.download.manager.scanWhenDone"; michael@0: michael@0: const nsLocalFile = Components.Constructor("@mozilla.org/file/local;1", michael@0: "nsILocalFile", "initWithPath"); michael@0: michael@0: var Cc = Components.classes; michael@0: var Ci = Components.interfaces; michael@0: let Cu = Components.utils; michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/DownloadUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", michael@0: "resource://gre/modules/PluralForm.jsm"); michael@0: michael@0: const nsIDM = Ci.nsIDownloadManager; michael@0: michael@0: let gDownloadManager = Cc["@mozilla.org/download-manager;1"].getService(nsIDM); michael@0: let gDownloadManagerUI = Cc["@mozilla.org/download-manager-ui;1"]. michael@0: getService(Ci.nsIDownloadManagerUI); michael@0: michael@0: let gDownloadListener = null; michael@0: let gDownloadsView = null; michael@0: let gSearchBox = null; michael@0: let gSearchTerms = []; michael@0: let gBuilder = 0; michael@0: michael@0: // This variable is used when performing commands on download items and gives michael@0: // the command the ability to do something after all items have been operated michael@0: // on. The following convention is used to handle the value of the variable: michael@0: // whenever we aren't performing a command, the value is |undefined|; just michael@0: // before executing commands, the value will be set to |null|; and when michael@0: // commands want to create a callback, they set the value to be a callback michael@0: // function to be executed after all download items have been visited. michael@0: let gPerformAllCallback; michael@0: michael@0: // Control the performance of the incremental list building by setting how many michael@0: // milliseconds to wait before building more of the list and how many items to michael@0: // add between each delay. michael@0: const gListBuildDelay = 300; michael@0: const gListBuildChunk = 3; michael@0: michael@0: // Array of download richlistitem attributes to check when searching michael@0: const gSearchAttributes = [ michael@0: "target", michael@0: "status", michael@0: "dateTime", michael@0: ]; michael@0: michael@0: // If the user has interacted with the window in a significant way, we should michael@0: // not auto-close the window. Tough UI decisions about what is "significant." michael@0: var gUserInteracted = false; michael@0: michael@0: // These strings will be converted to the corresponding ones from the string michael@0: // bundle on startup. michael@0: let gStr = { michael@0: paused: "paused", michael@0: cannotPause: "cannotPause", michael@0: doneStatus: "doneStatus", michael@0: doneSize: "doneSize", michael@0: doneSizeUnknown: "doneSizeUnknown", michael@0: stateFailed: "stateFailed", michael@0: stateCanceled: "stateCanceled", michael@0: stateBlockedParentalControls: "stateBlocked", michael@0: stateBlockedPolicy: "stateBlockedPolicy", michael@0: stateDirty: "stateDirty", michael@0: downloadsTitleFiles: "downloadsTitleFiles", michael@0: downloadsTitlePercent: "downloadsTitlePercent", michael@0: fileExecutableSecurityWarningTitle: "fileExecutableSecurityWarningTitle", michael@0: fileExecutableSecurityWarningDontAsk: "fileExecutableSecurityWarningDontAsk" michael@0: }; michael@0: michael@0: // The statement to query for downloads that are active or match the search michael@0: let gStmt = null; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Start/Stop Observers michael@0: michael@0: function downloadCompleted(aDownload) michael@0: { michael@0: // The download is changing state, so update the clear list button michael@0: updateClearListButton(); michael@0: michael@0: // Wrap this in try...catch since this can be called while shutting down... michael@0: // it doesn't really matter if it fails then since well.. we're shutting down michael@0: // and there's no UI to update! michael@0: try { michael@0: let dl = getDownload(aDownload.id); michael@0: michael@0: // Update attributes now that we've finished michael@0: dl.setAttribute("startTime", Math.round(aDownload.startTime / 1000)); michael@0: dl.setAttribute("endTime", Date.now()); michael@0: dl.setAttribute("currBytes", aDownload.amountTransferred); michael@0: dl.setAttribute("maxBytes", aDownload.size); michael@0: michael@0: // Move the download below active if it should stay in the list michael@0: if (downloadMatchesSearch(dl)) { michael@0: // Iterate down until we find a non-active download michael@0: let next = dl.nextSibling; michael@0: while (next && next.inProgress) michael@0: next = next.nextSibling; michael@0: michael@0: // Move the item michael@0: gDownloadsView.insertBefore(dl, next); michael@0: } else { michael@0: removeFromView(dl); michael@0: } michael@0: michael@0: // getTypeFromFile fails if it can't find a type for this file. michael@0: try { michael@0: // Refresh the icon, so that executable icons are shown. michael@0: var mimeService = Cc["@mozilla.org/mime;1"]. michael@0: getService(Ci.nsIMIMEService); michael@0: var contentType = mimeService.getTypeFromFile(aDownload.targetFile); michael@0: michael@0: var listItem = getDownload(aDownload.id) michael@0: var oldImage = listItem.getAttribute("image"); michael@0: // Tacking on contentType bypasses cache michael@0: listItem.setAttribute("image", oldImage + "&contentType=" + contentType); michael@0: } catch (e) { } michael@0: michael@0: if (gDownloadManager.activeDownloadCount == 0) michael@0: document.title = document.documentElement.getAttribute("statictitle"); michael@0: michael@0: gDownloadManagerUI.getAttention(); michael@0: } michael@0: catch (e) { } michael@0: } michael@0: michael@0: function autoRemoveAndClose(aDownload) michael@0: { michael@0: var pref = Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefBranch); michael@0: michael@0: if (gDownloadManager.activeDownloadCount == 0) { michael@0: // For the moment, just use the simple heuristic that if this window was michael@0: // opened by the download process, rather than by the user, it should michael@0: // auto-close if the pref is set that way. If the user opened it themselves, michael@0: // it should not close until they explicitly close it. Additionally, the michael@0: // preference to control the feature may not be set, so defaulting to michael@0: // keeping the window open. michael@0: let autoClose = false; michael@0: try { michael@0: autoClose = pref.getBoolPref(PREF_BDM_CLOSEWHENDONE); michael@0: } catch (e) { } michael@0: var autoOpened = michael@0: !window.opener || window.opener.location.href == window.location.href; michael@0: if (autoClose && autoOpened && !gUserInteracted) { michael@0: gCloseDownloadManager(); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // This function can be overwritten by extensions that wish to place the michael@0: // Download Window in another part of the UI. michael@0: function gCloseDownloadManager() michael@0: { michael@0: window.close(); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Download Event Handlers michael@0: michael@0: function cancelDownload(aDownload) michael@0: { michael@0: gDownloadManager.cancelDownload(aDownload.getAttribute("dlid")); michael@0: michael@0: // XXXben - michael@0: // If we got here because we resumed the download, we weren't using a temp file michael@0: // because we used saveURL instead. (this is because the proper download mechanism michael@0: // employed by the helper app service isn't fully accessible yet... should be fixed... michael@0: // talk to bz...) michael@0: // the upshot is we have to delete the file if it exists. michael@0: var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file")); michael@0: michael@0: if (f.exists()) michael@0: f.remove(false); michael@0: } michael@0: michael@0: function pauseDownload(aDownload) michael@0: { michael@0: var id = aDownload.getAttribute("dlid"); michael@0: gDownloadManager.pauseDownload(id); michael@0: } michael@0: michael@0: function resumeDownload(aDownload) michael@0: { michael@0: gDownloadManager.resumeDownload(aDownload.getAttribute("dlid")); michael@0: } michael@0: michael@0: function removeDownload(aDownload) michael@0: { michael@0: gDownloadManager.removeDownload(aDownload.getAttribute("dlid")); michael@0: } michael@0: michael@0: function retryDownload(aDownload) michael@0: { michael@0: removeFromView(aDownload); michael@0: gDownloadManager.retryDownload(aDownload.getAttribute("dlid")); michael@0: } michael@0: michael@0: function showDownload(aDownload) michael@0: { michael@0: var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file")); michael@0: michael@0: try { michael@0: // Show the directory containing the file and select the file michael@0: f.reveal(); michael@0: } catch (e) { michael@0: // If reveal fails for some reason (e.g., it's not implemented on unix or michael@0: // the file doesn't exist), try using the parent if we have it. michael@0: let parent = f.parent.QueryInterface(Ci.nsILocalFile); michael@0: if (!parent) michael@0: return; michael@0: michael@0: try { michael@0: // "Double click" the parent directory to show where the file should be michael@0: parent.launch(); michael@0: } catch (e) { michael@0: // If launch also fails (probably because it's not implemented), let the michael@0: // OS handler try to open the parent michael@0: openExternal(parent); michael@0: } michael@0: } michael@0: } michael@0: michael@0: function onDownloadDblClick(aEvent) michael@0: { michael@0: // Only do the default action for double primary clicks michael@0: if (aEvent.button == 0 && aEvent.target.selected) michael@0: doDefaultForSelected(); michael@0: } michael@0: michael@0: function openDownload(aDownload) michael@0: { michael@0: var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file")); michael@0: if (f.isExecutable()) { michael@0: var dontAsk = false; michael@0: var pref = Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefBranch); michael@0: try { michael@0: dontAsk = !pref.getBoolPref(PREF_BDM_ALERTONEXEOPEN); michael@0: } catch (e) { } michael@0: michael@0: #ifdef XP_WIN michael@0: // On Vista and above, we rely on native security prompting for michael@0: // downloaded content unless it's disabled. michael@0: try { michael@0: var sysInfo = Cc["@mozilla.org/system-info;1"]. michael@0: getService(Ci.nsIPropertyBag2); michael@0: if (parseFloat(sysInfo.getProperty("version")) >= 6 && michael@0: pref.getBoolPref(PREF_BDM_SCANWHENDONE)) { michael@0: dontAsk = true; michael@0: } michael@0: } catch (ex) { } michael@0: #endif michael@0: michael@0: if (!dontAsk) { michael@0: var strings = document.getElementById("downloadStrings"); michael@0: var name = aDownload.getAttribute("target"); michael@0: var message = strings.getFormattedString("fileExecutableSecurityWarning", [name, name]); michael@0: michael@0: let title = gStr.fileExecutableSecurityWarningTitle; michael@0: let dontAsk = gStr.fileExecutableSecurityWarningDontAsk; michael@0: michael@0: var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"]. michael@0: getService(Ci.nsIPromptService); michael@0: var checkbox = { value: false }; michael@0: var open = promptSvc.confirmCheck(window, title, message, dontAsk, checkbox); michael@0: michael@0: if (!open) michael@0: return; michael@0: pref.setBoolPref(PREF_BDM_ALERTONEXEOPEN, !checkbox.value); michael@0: } michael@0: } michael@0: try { michael@0: try { michael@0: let download = gDownloadManager.getDownload(aDownload.getAttribute("dlid")); michael@0: let mimeInfo = download.MIMEInfo; michael@0: if (mimeInfo.preferredAction == mimeInfo.useHelperApp) { michael@0: mimeInfo.launchWithFile(f); michael@0: return; michael@0: } michael@0: } catch (ex) { michael@0: } michael@0: f.launch(); michael@0: } catch (ex) { michael@0: // if launch fails, try sending it through the system's external michael@0: // file: URL handler michael@0: openExternal(f); michael@0: } michael@0: } michael@0: michael@0: function openReferrer(aDownload) michael@0: { michael@0: openURL(getReferrerOrSource(aDownload)); michael@0: } michael@0: michael@0: function copySourceLocation(aDownload) michael@0: { michael@0: var uri = aDownload.getAttribute("uri"); michael@0: var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. michael@0: getService(Ci.nsIClipboardHelper); michael@0: michael@0: // Check if we should initialize a callback michael@0: if (gPerformAllCallback === null) { michael@0: let uris = []; michael@0: gPerformAllCallback = function(aURI) aURI ? uris.push(aURI) : michael@0: clipboard.copyString(uris.join("\n"), document); michael@0: } michael@0: michael@0: // We have a callback to use, so use it to add a uri michael@0: if (typeof gPerformAllCallback == "function") michael@0: gPerformAllCallback(uri); michael@0: else { michael@0: // It's a plain copy source, so copy it michael@0: clipboard.copyString(uri, document); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Remove the currently shown downloads from the download list. michael@0: */ michael@0: function clearDownloadList() { michael@0: // Clear the whole list if there's no search michael@0: if (gSearchTerms == "") { michael@0: gDownloadManager.cleanUp(); michael@0: return; michael@0: } michael@0: michael@0: // Remove each download starting from the end until we hit a download michael@0: // that is in progress michael@0: let item; michael@0: while ((item = gDownloadsView.lastChild) && !item.inProgress) michael@0: removeDownload(item); michael@0: michael@0: // Clear the input as if the user did it and move focus to the list michael@0: gSearchBox.value = ""; michael@0: gSearchBox.doCommand(); michael@0: gDownloadsView.focus(); michael@0: } michael@0: michael@0: // This is called by the progress listener. michael@0: var gLastComputedMean = -1; michael@0: var gLastActiveDownloads = 0; michael@0: function onUpdateProgress() michael@0: { michael@0: let numActiveDownloads = gDownloadManager.activeDownloadCount; michael@0: michael@0: // Use the default title and reset "last" values if there's no downloads michael@0: if (numActiveDownloads == 0) { michael@0: document.title = document.documentElement.getAttribute("statictitle"); michael@0: gLastComputedMean = -1; michael@0: gLastActiveDownloads = 0; michael@0: michael@0: return; michael@0: } michael@0: michael@0: // Establish the mean transfer speed and amount downloaded. michael@0: var mean = 0; michael@0: var base = 0; michael@0: var dls = gDownloadManager.activeDownloads; michael@0: while (dls.hasMoreElements()) { michael@0: let dl = dls.getNext(); michael@0: if (dl.percentComplete < 100 && dl.size > 0) { michael@0: mean += dl.amountTransferred; michael@0: base += dl.size; michael@0: } michael@0: } michael@0: michael@0: // Calculate the percent transferred, unless we don't have a total file size michael@0: let title = gStr.downloadsTitlePercent; michael@0: if (base == 0) michael@0: title = gStr.downloadsTitleFiles; michael@0: else michael@0: mean = Math.floor((mean / base) * 100); michael@0: michael@0: // Update title of window michael@0: if (mean != gLastComputedMean || gLastActiveDownloads != numActiveDownloads) { michael@0: gLastComputedMean = mean; michael@0: gLastActiveDownloads = numActiveDownloads; michael@0: michael@0: // Get the correct plural form and insert number of downloads and percent michael@0: title = PluralForm.get(numActiveDownloads, title); michael@0: title = replaceInsert(title, 1, numActiveDownloads); michael@0: title = replaceInsert(title, 2, mean); michael@0: michael@0: document.title = title; michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Startup, Shutdown michael@0: michael@0: function Startup() michael@0: { michael@0: gDownloadsView = document.getElementById("downloadView"); michael@0: gSearchBox = document.getElementById("searchbox"); michael@0: michael@0: // convert strings to those in the string bundle michael@0: let (sb = document.getElementById("downloadStrings")) { michael@0: let getStr = function(string) sb.getString(string); michael@0: for (let [name, value] in Iterator(gStr)) michael@0: gStr[name] = typeof value == "string" ? getStr(value) : value.map(getStr); michael@0: } michael@0: michael@0: initStatement(); michael@0: buildDownloadList(true); michael@0: michael@0: // The DownloadProgressListener (DownloadProgressListener.js) handles progress michael@0: // notifications. michael@0: gDownloadListener = new DownloadProgressListener(); michael@0: gDownloadManager.addListener(gDownloadListener); michael@0: michael@0: // If the UI was displayed because the user interacted, we need to make sure michael@0: // we update gUserInteracted accordingly. michael@0: if (window.arguments[1] == Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED) michael@0: gUserInteracted = true; michael@0: michael@0: // downloads can finish before Startup() does, so check if the window should michael@0: // close and act accordingly michael@0: if (!autoRemoveAndClose()) michael@0: gDownloadsView.focus(); michael@0: michael@0: let obs = Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService); michael@0: obs.addObserver(gDownloadObserver, "download-manager-remove-download", false); michael@0: obs.addObserver(gDownloadObserver, "browser-lastwindow-close-granted", false); michael@0: michael@0: // Clear the search box and move focus to the list on escape from the box michael@0: gSearchBox.addEventListener("keypress", function(e) { michael@0: if (e.keyCode == e.DOM_VK_ESCAPE) { michael@0: // Move focus to the list instead of closing the window michael@0: gDownloadsView.focus(); michael@0: e.preventDefault(); michael@0: } michael@0: }, false); michael@0: michael@0: let DownloadTaskbarProgress = michael@0: Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress; michael@0: DownloadTaskbarProgress.onDownloadWindowLoad(window); michael@0: } michael@0: michael@0: function Shutdown() michael@0: { michael@0: gDownloadManager.removeListener(gDownloadListener); michael@0: michael@0: let obs = Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService); michael@0: obs.removeObserver(gDownloadObserver, "download-manager-remove-download"); michael@0: obs.removeObserver(gDownloadObserver, "browser-lastwindow-close-granted"); michael@0: michael@0: clearTimeout(gBuilder); michael@0: gStmt.reset(); michael@0: gStmt.finalize(); michael@0: } michael@0: michael@0: let gDownloadObserver = { michael@0: observe: function gdo_observe(aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "download-manager-remove-download": michael@0: // A null subject here indicates "remove multiple", so we just rebuild. michael@0: if (!aSubject) { michael@0: // Rebuild the default view michael@0: buildDownloadList(true); michael@0: break; michael@0: } michael@0: michael@0: // Otherwise, remove a single download michael@0: let id = aSubject.QueryInterface(Ci.nsISupportsPRUint32); michael@0: let dl = getDownload(id.data); michael@0: removeFromView(dl); michael@0: break; michael@0: case "browser-lastwindow-close-granted": michael@0: #ifndef XP_MACOSX michael@0: if (gDownloadManager.activeDownloadCount == 0) { michael@0: setTimeout(gCloseDownloadManager, 0); michael@0: } michael@0: #endif michael@0: break; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// View Context Menus michael@0: michael@0: var gContextMenus = [ michael@0: // DOWNLOAD_DOWNLOADING michael@0: [ michael@0: "menuitem_pause" michael@0: , "menuitem_cancel" michael@0: , "menuseparator" michael@0: , "menuitem_show" michael@0: , "menuseparator" michael@0: , "menuitem_openReferrer" michael@0: , "menuitem_copyLocation" michael@0: , "menuseparator" michael@0: , "menuitem_selectAll" michael@0: ], michael@0: // DOWNLOAD_FINISHED michael@0: [ michael@0: "menuitem_open" michael@0: , "menuitem_show" michael@0: , "menuseparator" michael@0: , "menuitem_openReferrer" michael@0: , "menuitem_copyLocation" michael@0: , "menuseparator" michael@0: , "menuitem_selectAll" michael@0: , "menuseparator" michael@0: , "menuitem_removeFromList" michael@0: ], michael@0: // DOWNLOAD_FAILED michael@0: [ michael@0: "menuitem_retry" michael@0: , "menuseparator" michael@0: , "menuitem_openReferrer" michael@0: , "menuitem_copyLocation" michael@0: , "menuseparator" michael@0: , "menuitem_selectAll" michael@0: , "menuseparator" michael@0: , "menuitem_removeFromList" michael@0: ], michael@0: // DOWNLOAD_CANCELED michael@0: [ michael@0: "menuitem_retry" michael@0: , "menuseparator" michael@0: , "menuitem_openReferrer" michael@0: , "menuitem_copyLocation" michael@0: , "menuseparator" michael@0: , "menuitem_selectAll" michael@0: , "menuseparator" michael@0: , "menuitem_removeFromList" michael@0: ], michael@0: // DOWNLOAD_PAUSED michael@0: [ michael@0: "menuitem_resume" michael@0: , "menuitem_cancel" michael@0: , "menuseparator" michael@0: , "menuitem_show" michael@0: , "menuseparator" michael@0: , "menuitem_openReferrer" michael@0: , "menuitem_copyLocation" michael@0: , "menuseparator" michael@0: , "menuitem_selectAll" michael@0: ], michael@0: // DOWNLOAD_QUEUED michael@0: [ michael@0: "menuitem_cancel" michael@0: , "menuseparator" michael@0: , "menuitem_show" michael@0: , "menuseparator" michael@0: , "menuitem_openReferrer" michael@0: , "menuitem_copyLocation" michael@0: , "menuseparator" michael@0: , "menuitem_selectAll" michael@0: ], michael@0: // DOWNLOAD_BLOCKED_PARENTAL michael@0: [ michael@0: "menuitem_openReferrer" michael@0: , "menuitem_copyLocation" michael@0: , "menuseparator" michael@0: , "menuitem_selectAll" michael@0: , "menuseparator" michael@0: , "menuitem_removeFromList" michael@0: ], michael@0: // DOWNLOAD_SCANNING michael@0: [ michael@0: "menuitem_show" michael@0: , "menuseparator" michael@0: , "menuitem_openReferrer" michael@0: , "menuitem_copyLocation" michael@0: , "menuseparator" michael@0: , "menuitem_selectAll" michael@0: ], michael@0: // DOWNLOAD_DIRTY michael@0: [ michael@0: "menuitem_openReferrer" michael@0: , "menuitem_copyLocation" michael@0: , "menuseparator" michael@0: , "menuitem_selectAll" michael@0: , "menuseparator" michael@0: , "menuitem_removeFromList" michael@0: ], michael@0: // DOWNLOAD_BLOCKED_POLICY michael@0: [ michael@0: "menuitem_openReferrer" michael@0: , "menuitem_copyLocation" michael@0: , "menuseparator" michael@0: , "menuitem_selectAll" michael@0: , "menuseparator" michael@0: , "menuitem_removeFromList" michael@0: ] michael@0: ]; michael@0: michael@0: function buildContextMenu(aEvent) michael@0: { michael@0: if (aEvent.target.id != "downloadContextMenu") michael@0: return false; michael@0: michael@0: var popup = document.getElementById("downloadContextMenu"); michael@0: while (popup.hasChildNodes()) michael@0: popup.removeChild(popup.firstChild); michael@0: michael@0: if (gDownloadsView.selectedItem) { michael@0: let dl = gDownloadsView.selectedItem; michael@0: let idx = parseInt(dl.getAttribute("state")); michael@0: if (idx < 0) michael@0: idx = 0; michael@0: michael@0: var menus = gContextMenus[idx]; michael@0: for (let i = 0; i < menus.length; ++i) { michael@0: let menuitem = document.getElementById(menus[i]).cloneNode(true); michael@0: let cmd = menuitem.getAttribute("cmd"); michael@0: if (cmd) michael@0: menuitem.disabled = !gDownloadViewController.isCommandEnabled(cmd, dl); michael@0: michael@0: popup.appendChild(menuitem); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Drag and Drop michael@0: var gDownloadDNDObserver = michael@0: { michael@0: onDragStart: function (aEvent) michael@0: { michael@0: if (!gDownloadsView.selectedItem) michael@0: return; michael@0: var dl = gDownloadsView.selectedItem; michael@0: var f = getLocalFileFromNativePathOrUrl(dl.getAttribute("file")); michael@0: if (!f.exists()) michael@0: return; michael@0: michael@0: var dt = aEvent.dataTransfer; michael@0: dt.mozSetDataAt("application/x-moz-file", f, 0); michael@0: var url = Services.io.newFileURI(f).spec; michael@0: dt.setData("text/uri-list", url); michael@0: dt.setData("text/plain", url); michael@0: dt.effectAllowed = "copyMove"; michael@0: dt.addElement(dl); michael@0: }, michael@0: michael@0: onDragOver: function (aEvent) michael@0: { michael@0: var types = aEvent.dataTransfer.types; michael@0: if (types.contains("text/uri-list") || michael@0: types.contains("text/x-moz-url") || michael@0: types.contains("text/plain")) michael@0: aEvent.preventDefault(); michael@0: }, michael@0: michael@0: onDrop: function(aEvent) michael@0: { michael@0: var dt = aEvent.dataTransfer; michael@0: // If dragged item is from our source, do not try to michael@0: // redownload already downloaded file. michael@0: if (dt.mozGetDataAt("application/x-moz-file", 0)) michael@0: return; michael@0: michael@0: var url = dt.getData("URL"); michael@0: var name; michael@0: if (!url) { michael@0: url = dt.getData("text/x-moz-url") || dt.getData("text/plain"); michael@0: [url, name] = url.split("\n"); michael@0: } michael@0: if (url) { michael@0: let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document; michael@0: saveURL(url, name ? name : url, null, true, true, null, sourceDoc); michael@0: } michael@0: } michael@0: } michael@0: michael@0: function pasteHandler() { michael@0: let trans = Cc["@mozilla.org/widget/transferable;1"]. michael@0: createInstance(Ci.nsITransferable); michael@0: trans.init(null); michael@0: let flavors = ["text/x-moz-url", "text/unicode"]; michael@0: flavors.forEach(trans.addDataFlavor); michael@0: michael@0: Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard); michael@0: michael@0: // Getting the data or creating the nsIURI might fail michael@0: try { michael@0: let data = {}; michael@0: trans.getAnyTransferData({}, data, {}); michael@0: let [url, name] = data.value.QueryInterface(Ci.nsISupportsString).data.split("\n"); michael@0: michael@0: if (!url) michael@0: return; michael@0: michael@0: let uri = Services.io.newURI(url, null, null); michael@0: michael@0: saveURL(uri.spec, name || uri.spec, null, true, true, null, document); michael@0: } catch (ex) {} michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Command Updating and Command Handlers michael@0: michael@0: var gDownloadViewController = { michael@0: isCommandEnabled: function(aCommand, aItem) michael@0: { michael@0: let dl = aItem; michael@0: let download = null; // used for getting an nsIDownload object michael@0: michael@0: switch (aCommand) { michael@0: case "cmd_cancel": michael@0: return dl.inProgress; michael@0: case "cmd_open": { michael@0: let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file")); michael@0: return dl.openable && file.exists(); michael@0: } michael@0: case "cmd_show": { michael@0: let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file")); michael@0: return file.exists(); michael@0: } michael@0: case "cmd_pause": michael@0: download = gDownloadManager.getDownload(dl.getAttribute("dlid")); michael@0: return dl.inProgress && !dl.paused && download.resumable; michael@0: case "cmd_pauseResume": michael@0: download = gDownloadManager.getDownload(dl.getAttribute("dlid")); michael@0: return (dl.inProgress || dl.paused) && download.resumable; michael@0: case "cmd_resume": michael@0: download = gDownloadManager.getDownload(dl.getAttribute("dlid")); michael@0: return dl.paused && download.resumable; michael@0: case "cmd_openReferrer": michael@0: return dl.hasAttribute("referrer"); michael@0: case "cmd_removeFromList": michael@0: case "cmd_retry": michael@0: return dl.removable; michael@0: case "cmd_copyLocation": michael@0: return true; michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: doCommand: function(aCommand, aItem) michael@0: { michael@0: if (this.isCommandEnabled(aCommand, aItem)) michael@0: this.commands[aCommand](aItem); michael@0: }, michael@0: michael@0: commands: { michael@0: cmd_cancel: function(aSelectedItem) { michael@0: cancelDownload(aSelectedItem); michael@0: }, michael@0: cmd_open: function(aSelectedItem) { michael@0: openDownload(aSelectedItem); michael@0: }, michael@0: cmd_openReferrer: function(aSelectedItem) { michael@0: openReferrer(aSelectedItem); michael@0: }, michael@0: cmd_pause: function(aSelectedItem) { michael@0: pauseDownload(aSelectedItem); michael@0: }, michael@0: cmd_pauseResume: function(aSelectedItem) { michael@0: if (aSelectedItem.paused) michael@0: this.cmd_resume(aSelectedItem); michael@0: else michael@0: this.cmd_pause(aSelectedItem); michael@0: }, michael@0: cmd_removeFromList: function(aSelectedItem) { michael@0: removeDownload(aSelectedItem); michael@0: }, michael@0: cmd_resume: function(aSelectedItem) { michael@0: resumeDownload(aSelectedItem); michael@0: }, michael@0: cmd_retry: function(aSelectedItem) { michael@0: retryDownload(aSelectedItem); michael@0: }, michael@0: cmd_show: function(aSelectedItem) { michael@0: showDownload(aSelectedItem); michael@0: }, michael@0: cmd_copyLocation: function(aSelectedItem) { michael@0: copySourceLocation(aSelectedItem); michael@0: }, michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Helper function to do commands. michael@0: * michael@0: * @param aCmd michael@0: * The command to be performed. michael@0: * @param aItem michael@0: * The richlistitem that represents the download that will have the michael@0: * command performed on it. If this is null, the command is performed on michael@0: * all downloads. If the item passed in is not a richlistitem that michael@0: * represents a download, it will walk up the parent nodes until it finds michael@0: * a DOM node that is. michael@0: */ michael@0: function performCommand(aCmd, aItem) michael@0: { michael@0: let elm = aItem; michael@0: if (!elm) { michael@0: // If we don't have a desired download item, do the command for all michael@0: // selected items. Initialize the callback to null so commands know to add michael@0: // a callback if they want. We will call the callback with empty arguments michael@0: // after performing the command on each selected download item. michael@0: gPerformAllCallback = null; michael@0: michael@0: // Convert the nodelist into an array to keep a copy of the download items michael@0: let items = []; michael@0: for (let i = gDownloadsView.selectedItems.length; --i >= 0; ) michael@0: items.unshift(gDownloadsView.selectedItems[i]); michael@0: michael@0: // Do the command for each download item michael@0: for each (let item in items) michael@0: performCommand(aCmd, item); michael@0: michael@0: // Call the callback with no arguments and reset because we're done michael@0: if (typeof gPerformAllCallback == "function") michael@0: gPerformAllCallback(); michael@0: gPerformAllCallback = undefined; michael@0: michael@0: return; michael@0: } else { michael@0: while (elm.nodeName != "richlistitem" || michael@0: elm.getAttribute("type") != "download") michael@0: elm = elm.parentNode; michael@0: } michael@0: michael@0: gDownloadViewController.doCommand(aCmd, elm); michael@0: } michael@0: michael@0: function setSearchboxFocus() michael@0: { michael@0: gSearchBox.focus(); michael@0: gSearchBox.select(); michael@0: } michael@0: michael@0: function openExternal(aFile) michael@0: { michael@0: var uri = Cc["@mozilla.org/network/io-service;1"]. michael@0: getService(Ci.nsIIOService).newFileURI(aFile); michael@0: michael@0: var protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. michael@0: getService(Ci.nsIExternalProtocolService); michael@0: protocolSvc.loadUrl(uri); michael@0: michael@0: return; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Utility Functions michael@0: michael@0: /** michael@0: * Create a download richlistitem with the provided attributes. Some attributes michael@0: * are *required* while optional ones will only be set on the item if provided. michael@0: * michael@0: * @param aAttrs michael@0: * An object that must have the following properties: dlid, file, michael@0: * target, uri, state, progress, startTime, endTime, currBytes, michael@0: * maxBytes; optional properties: referrer michael@0: * @return An initialized download richlistitem michael@0: */ michael@0: function createDownloadItem(aAttrs) michael@0: { michael@0: let dl = document.createElement("richlistitem"); michael@0: michael@0: // Copy the attributes from the argument into the item michael@0: for (let attr in aAttrs) michael@0: dl.setAttribute(attr, aAttrs[attr]); michael@0: michael@0: // Initialize other attributes michael@0: dl.setAttribute("type", "download"); michael@0: dl.setAttribute("id", "dl" + aAttrs.dlid); michael@0: dl.setAttribute("image", "moz-icon://" + aAttrs.file + "?size=32"); michael@0: dl.setAttribute("lastSeconds", Infinity); michael@0: michael@0: // Initialize more complex attributes michael@0: updateTime(dl); michael@0: updateStatus(dl); michael@0: michael@0: try { michael@0: let file = getLocalFileFromNativePathOrUrl(aAttrs.file); michael@0: dl.setAttribute("path", file.nativePath || file.path); michael@0: return dl; michael@0: } catch (e) { michael@0: // aFile might not be a file: url or a valid native path michael@0: // see bug #392386 for details michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: /** michael@0: * Updates the disabled state of the buttons of a downlaod. michael@0: * michael@0: * @param aItem michael@0: * The richlistitem representing the download. michael@0: */ michael@0: function updateButtons(aItem) michael@0: { michael@0: let buttons = aItem.buttons; michael@0: michael@0: for (let i = 0; i < buttons.length; ++i) { michael@0: let cmd = buttons[i].getAttribute("cmd"); michael@0: let enabled = gDownloadViewController.isCommandEnabled(cmd, aItem); michael@0: buttons[i].disabled = !enabled; michael@0: michael@0: if ("cmd_pause" == cmd && !enabled) { michael@0: // We need to add the tooltip indicating that the download cannot be michael@0: // paused now. michael@0: buttons[i].setAttribute("tooltiptext", gStr.cannotPause); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Updates the status for a download item depending on its state michael@0: * michael@0: * @param aItem michael@0: * The richlistitem that has various download attributes. michael@0: * @param aDownload michael@0: * The nsDownload from the backend. This is an optional parameter, but michael@0: * is useful for certain states such as DOWNLOADING. michael@0: */ michael@0: function updateStatus(aItem, aDownload) { michael@0: let status = ""; michael@0: let statusTip = ""; michael@0: michael@0: let state = Number(aItem.getAttribute("state")); michael@0: switch (state) { michael@0: case nsIDM.DOWNLOAD_PAUSED: michael@0: { michael@0: let currBytes = Number(aItem.getAttribute("currBytes")); michael@0: let maxBytes = Number(aItem.getAttribute("maxBytes")); michael@0: michael@0: let transfer = DownloadUtils.getTransferTotal(currBytes, maxBytes); michael@0: status = replaceInsert(gStr.paused, 1, transfer); michael@0: michael@0: break; michael@0: } michael@0: case nsIDM.DOWNLOAD_DOWNLOADING: michael@0: { michael@0: let currBytes = Number(aItem.getAttribute("currBytes")); michael@0: let maxBytes = Number(aItem.getAttribute("maxBytes")); michael@0: // If we don't have an active download, assume 0 bytes/sec michael@0: let speed = aDownload ? aDownload.speed : 0; michael@0: let lastSec = Number(aItem.getAttribute("lastSeconds")); michael@0: michael@0: let newLast; michael@0: [status, newLast] = michael@0: DownloadUtils.getDownloadStatus(currBytes, maxBytes, speed, lastSec); michael@0: michael@0: // Update lastSeconds to be the new value michael@0: aItem.setAttribute("lastSeconds", newLast); michael@0: michael@0: break; michael@0: } michael@0: case nsIDM.DOWNLOAD_FINISHED: michael@0: case nsIDM.DOWNLOAD_FAILED: michael@0: case nsIDM.DOWNLOAD_CANCELED: michael@0: case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: michael@0: case nsIDM.DOWNLOAD_BLOCKED_POLICY: michael@0: case nsIDM.DOWNLOAD_DIRTY: michael@0: { michael@0: let (stateSize = {}) { michael@0: stateSize[nsIDM.DOWNLOAD_FINISHED] = function() { michael@0: // Display the file size, but show "Unknown" for negative sizes michael@0: let fileSize = Number(aItem.getAttribute("maxBytes")); michael@0: let sizeText = gStr.doneSizeUnknown; michael@0: if (fileSize >= 0) { michael@0: let [size, unit] = DownloadUtils.convertByteUnits(fileSize); michael@0: sizeText = replaceInsert(gStr.doneSize, 1, size); michael@0: sizeText = replaceInsert(sizeText, 2, unit); michael@0: } michael@0: return sizeText; michael@0: }; michael@0: stateSize[nsIDM.DOWNLOAD_FAILED] = function() gStr.stateFailed; michael@0: stateSize[nsIDM.DOWNLOAD_CANCELED] = function() gStr.stateCanceled; michael@0: stateSize[nsIDM.DOWNLOAD_BLOCKED_PARENTAL] = function() gStr.stateBlockedParentalControls; michael@0: stateSize[nsIDM.DOWNLOAD_BLOCKED_POLICY] = function() gStr.stateBlockedPolicy; michael@0: stateSize[nsIDM.DOWNLOAD_DIRTY] = function() gStr.stateDirty; michael@0: michael@0: // Insert 1 is the download size or download state michael@0: status = replaceInsert(gStr.doneStatus, 1, stateSize[state]()); michael@0: } michael@0: michael@0: let [displayHost, fullHost] = michael@0: DownloadUtils.getURIHost(getReferrerOrSource(aItem)); michael@0: // Insert 2 is the eTLD + 1 or other variations of the host michael@0: status = replaceInsert(status, 2, displayHost); michael@0: // Set the tooltip to be the full host michael@0: statusTip = fullHost; michael@0: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: aItem.setAttribute("status", status); michael@0: aItem.setAttribute("statusTip", statusTip != "" ? statusTip : status); michael@0: } michael@0: michael@0: /** michael@0: * Updates the time that gets shown for completed download items michael@0: * michael@0: * @param aItem michael@0: * The richlistitem representing a download in the UI michael@0: */ michael@0: function updateTime(aItem) michael@0: { michael@0: // Don't bother updating for things that aren't finished michael@0: if (aItem.inProgress) michael@0: return; michael@0: michael@0: let end = new Date(parseInt(aItem.getAttribute("endTime"))); michael@0: let [dateCompact, dateComplete] = DownloadUtils.getReadableDates(end); michael@0: aItem.setAttribute("dateTime", dateCompact); michael@0: aItem.setAttribute("dateTimeTip", dateComplete); michael@0: } michael@0: michael@0: /** michael@0: * Helper function to replace a placeholder string with a real string michael@0: * michael@0: * @param aText michael@0: * Source text containing placeholder (e.g., #1) michael@0: * @param aIndex michael@0: * Index number of placeholder to replace michael@0: * @param aValue michael@0: * New string to put in place of placeholder michael@0: * @return The string with placeholder replaced with the new string michael@0: */ michael@0: function replaceInsert(aText, aIndex, aValue) michael@0: { michael@0: return aText.replace("#" + aIndex, aValue); michael@0: } michael@0: michael@0: /** michael@0: * Perform the default action for the currently selected download item michael@0: */ michael@0: function doDefaultForSelected() michael@0: { michael@0: // Make sure we have something selected michael@0: let item = gDownloadsView.selectedItem; michael@0: if (!item) michael@0: return; michael@0: michael@0: // Get the default action (first item in the menu) michael@0: let state = Number(item.getAttribute("state")); michael@0: let menuitem = document.getElementById(gContextMenus[state][0]); michael@0: michael@0: // Try to do the action if the command is enabled michael@0: gDownloadViewController.doCommand(menuitem.getAttribute("cmd"), item); michael@0: } michael@0: michael@0: function removeFromView(aDownload) michael@0: { michael@0: // Make sure we have an item to remove michael@0: if (!aDownload) return; michael@0: michael@0: let index = gDownloadsView.selectedIndex; michael@0: gDownloadsView.removeChild(aDownload); michael@0: gDownloadsView.selectedIndex = Math.min(index, gDownloadsView.itemCount - 1); michael@0: michael@0: // We might have removed the last item, so update the clear list button michael@0: updateClearListButton(); michael@0: } michael@0: michael@0: function getReferrerOrSource(aDownload) michael@0: { michael@0: // Give the referrer if we have it set michael@0: if (aDownload.hasAttribute("referrer")) michael@0: return aDownload.getAttribute("referrer"); michael@0: michael@0: // Otherwise, provide the source michael@0: return aDownload.getAttribute("uri"); michael@0: } michael@0: michael@0: /** michael@0: * Initiate building the download list to have the active downloads followed by michael@0: * completed ones filtered by the search term if necessary. michael@0: * michael@0: * @param aForceBuild michael@0: * Force the list to be built even if the search terms don't change michael@0: */ michael@0: function buildDownloadList(aForceBuild) michael@0: { michael@0: // Stringify the previous search michael@0: let prevSearch = gSearchTerms.join(" "); michael@0: michael@0: // Array of space-separated lower-case search terms michael@0: gSearchTerms = gSearchBox.value.replace(/^\s+|\s+$/g, ""). michael@0: toLowerCase().split(/\s+/); michael@0: michael@0: // Unless forced, don't rebuild the download list if the search didn't change michael@0: if (!aForceBuild && gSearchTerms.join(" ") == prevSearch) michael@0: return; michael@0: michael@0: // Clear out values before using them michael@0: clearTimeout(gBuilder); michael@0: gStmt.reset(); michael@0: michael@0: // Clear the list before adding items by replacing with a shallow copy michael@0: let (empty = gDownloadsView.cloneNode(false)) { michael@0: gDownloadsView.parentNode.replaceChild(empty, gDownloadsView); michael@0: gDownloadsView = empty; michael@0: } michael@0: michael@0: try { michael@0: gStmt.bindByIndex(0, nsIDM.DOWNLOAD_NOTSTARTED); michael@0: gStmt.bindByIndex(1, nsIDM.DOWNLOAD_DOWNLOADING); michael@0: gStmt.bindByIndex(2, nsIDM.DOWNLOAD_PAUSED); michael@0: gStmt.bindByIndex(3, nsIDM.DOWNLOAD_QUEUED); michael@0: gStmt.bindByIndex(4, nsIDM.DOWNLOAD_SCANNING); michael@0: } catch (e) { michael@0: // Something must have gone wrong when binding, so clear and quit michael@0: gStmt.reset(); michael@0: return; michael@0: } michael@0: michael@0: // Take a quick break before we actually start building the list michael@0: gBuilder = setTimeout(function() { michael@0: // Start building the list michael@0: stepListBuilder(1); michael@0: michael@0: // We just tried to add a single item, so we probably need to enable michael@0: updateClearListButton(); michael@0: }, 0); michael@0: } michael@0: michael@0: /** michael@0: * Incrementally build the download list by adding at most the requested number michael@0: * of items if there are items to add. After doing that, it will schedule michael@0: * another chunk of items specified by gListBuildDelay and gListBuildChunk. michael@0: * michael@0: * @param aNumItems michael@0: * Number of items to add to the list before taking a break michael@0: */ michael@0: function stepListBuilder(aNumItems) { michael@0: try { michael@0: // If we're done adding all items, we can quit michael@0: if (!gStmt.executeStep()) { michael@0: // Send a notification that we finished, but wait for clear list to update michael@0: updateClearListButton(); michael@0: setTimeout(function() Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService). michael@0: notifyObservers(window, "download-manager-ui-done", null), 0); michael@0: michael@0: return; michael@0: } michael@0: michael@0: // Try to get the attribute values from the statement michael@0: let attrs = { michael@0: dlid: gStmt.getInt64(0), michael@0: file: gStmt.getString(1), michael@0: target: gStmt.getString(2), michael@0: uri: gStmt.getString(3), michael@0: state: gStmt.getInt32(4), michael@0: startTime: Math.round(gStmt.getInt64(5) / 1000), michael@0: endTime: Math.round(gStmt.getInt64(6) / 1000), michael@0: currBytes: gStmt.getInt64(8), michael@0: maxBytes: gStmt.getInt64(9) michael@0: }; michael@0: michael@0: // Only add the referrer if it's not null michael@0: let (referrer = gStmt.getString(7)) { michael@0: if (referrer) michael@0: attrs.referrer = referrer; michael@0: } michael@0: michael@0: // If the download is active, grab the real progress, otherwise default 100 michael@0: let isActive = gStmt.getInt32(10); michael@0: attrs.progress = isActive ? gDownloadManager.getDownload(attrs.dlid). michael@0: percentComplete : 100; michael@0: michael@0: // Make the item and add it to the end if it's active or matches the search michael@0: let item = createDownloadItem(attrs); michael@0: if (item && (isActive || downloadMatchesSearch(item))) { michael@0: // Add item to the end michael@0: gDownloadsView.appendChild(item); michael@0: michael@0: // Because of the joys of XBL, we can't update the buttons until the michael@0: // download object is in the document. michael@0: updateButtons(item); michael@0: } else { michael@0: // We didn't add an item, so bump up the number of items to process, but michael@0: // not a whole number so that we eventually do pause for a chunk break michael@0: aNumItems += .9; michael@0: } michael@0: } catch (e) { michael@0: // Something went wrong when stepping or getting values, so clear and quit michael@0: gStmt.reset(); michael@0: return; michael@0: } michael@0: michael@0: // Add another item to the list if we should; otherwise, let the UI update michael@0: // and continue later michael@0: if (aNumItems > 1) { michael@0: stepListBuilder(aNumItems - 1); michael@0: } else { michael@0: // Use a shorter delay for earlier downloads to display them faster michael@0: let delay = Math.min(gDownloadsView.itemCount * 10, gListBuildDelay); michael@0: gBuilder = setTimeout(stepListBuilder, delay, gListBuildChunk); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Add a download to the front of the download list michael@0: * michael@0: * @param aDownload michael@0: * The nsIDownload to make into a richlistitem michael@0: */ michael@0: function prependList(aDownload) michael@0: { michael@0: let attrs = { michael@0: dlid: aDownload.id, michael@0: file: aDownload.target.spec, michael@0: target: aDownload.displayName, michael@0: uri: aDownload.source.spec, michael@0: state: aDownload.state, michael@0: progress: aDownload.percentComplete, michael@0: startTime: Math.round(aDownload.startTime / 1000), michael@0: endTime: Date.now(), michael@0: currBytes: aDownload.amountTransferred, michael@0: maxBytes: aDownload.size michael@0: }; michael@0: michael@0: // Make the item and add it to the beginning michael@0: let item = createDownloadItem(attrs); michael@0: if (item) { michael@0: // Add item to the beginning michael@0: gDownloadsView.insertBefore(item, gDownloadsView.firstChild); michael@0: michael@0: // Because of the joys of XBL, we can't update the buttons until the michael@0: // download object is in the document. michael@0: updateButtons(item); michael@0: michael@0: // We might have added an item to an empty list, so update button michael@0: updateClearListButton(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Check if the download matches the current search term based on the texts michael@0: * shown to the user. All search terms are checked to see if each matches any michael@0: * of the displayed texts. michael@0: * michael@0: * @param aItem michael@0: * Download richlistitem to check if it matches the current search michael@0: * @return Boolean true if it matches the search; false otherwise michael@0: */ michael@0: function downloadMatchesSearch(aItem) michael@0: { michael@0: // Search through the download attributes that are shown to the user and michael@0: // make it into one big string for easy combined searching michael@0: let combinedSearch = ""; michael@0: for each (let attr in gSearchAttributes) michael@0: combinedSearch += aItem.getAttribute(attr).toLowerCase() + " "; michael@0: michael@0: // Make sure each of the terms are found michael@0: for each (let term in gSearchTerms) michael@0: if (combinedSearch.indexOf(term) == -1) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // we should be using real URLs all the time, but until michael@0: // bug 239948 is fully fixed, this will do... michael@0: // michael@0: // note, this will thrown an exception if the native path michael@0: // is not valid (for example a native Windows path on a Mac) michael@0: // see bug #392386 for details michael@0: function getLocalFileFromNativePathOrUrl(aPathOrUrl) michael@0: { michael@0: if (aPathOrUrl.substring(0,7) == "file://") { michael@0: // if this is a URL, get the file from that michael@0: let ioSvc = Cc["@mozilla.org/network/io-service;1"]. michael@0: getService(Ci.nsIIOService); michael@0: michael@0: // XXX it's possible that using a null char-set here is bad michael@0: const fileUrl = ioSvc.newURI(aPathOrUrl, null, null). michael@0: QueryInterface(Ci.nsIFileURL); michael@0: return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile); michael@0: } else { michael@0: // if it's a pathname, create the nsILocalFile directly michael@0: var f = new nsLocalFile(aPathOrUrl); michael@0: michael@0: return f; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Update the disabled state of the clear list button based on whether or not michael@0: * there are items in the list that can potentially be removed. michael@0: */ michael@0: function updateClearListButton() michael@0: { michael@0: let button = document.getElementById("clearListButton"); michael@0: // The button is enabled if we have items in the list and we can clean up michael@0: button.disabled = !(gDownloadsView.itemCount && gDownloadManager.canCleanUp); michael@0: } michael@0: michael@0: function getDownload(aID) michael@0: { michael@0: return document.getElementById("dl" + aID); michael@0: } michael@0: michael@0: /** michael@0: * Initialize the statement which is used to retrieve the list of downloads. michael@0: */ michael@0: function initStatement() michael@0: { michael@0: if (gStmt) michael@0: gStmt.finalize(); michael@0: michael@0: gStmt = gDownloadManager.DBConnection.createStatement( michael@0: "SELECT id, target, name, source, state, startTime, endTime, referrer, " + michael@0: "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " + michael@0: "FROM moz_downloads " + michael@0: "ORDER BY isActive DESC, endTime DESC, startTime DESC"); michael@0: }