diff --git a/mozilla/browser/app/profile/firefox.js b/mozilla/browser/app/profile/firefox.js index 7f91d648d09..e1e9407dc72 100644 --- a/mozilla/browser/app/profile/firefox.js +++ b/mozilla/browser/app/profile/firefox.js @@ -219,7 +219,6 @@ pref("browser.download.manager.closeWhenDone", false); pref("browser.download.manager.openDelay", 0); pref("browser.download.manager.focusWhenStarting", false); pref("browser.download.manager.flashCount", 2); -pref("browser.download.manager.displayedHistoryDays", 7); pref("browser.download.manager.addToRecentDocs", true); // search engines URL diff --git a/mozilla/toolkit/mozapps/downloads/content/DownloadProgressListener.js b/mozilla/toolkit/mozapps/downloads/content/DownloadProgressListener.js index 09e6164811f..b81a82e744a 100644 --- a/mozilla/toolkit/mozapps/downloads/content/DownloadProgressListener.js +++ b/mozilla/toolkit/mozapps/downloads/content/DownloadProgressListener.js @@ -65,7 +65,7 @@ DownloadProgressListener.prototype = { let state = aDownload.state; switch (state) { case nsIDM.DOWNLOAD_QUEUED: - buildActiveDownloadsList(); + prependList(aDownload); break; case nsIDM.DOWNLOAD_FAILED: case nsIDM.DOWNLOAD_CANCELED: diff --git a/mozilla/toolkit/mozapps/downloads/content/download.xml b/mozilla/toolkit/mozapps/downloads/content/download.xml index 8ae30b0cdfd..dad41bb904f 100644 --- a/mozilla/toolkit/mozapps/downloads/content/download.xml +++ b/mozilla/toolkit/mozapps/downloads/content/download.xml @@ -78,7 +78,8 @@ return state == dl.DOWNLOAD_NOTSTARTED || state == dl.DOWNLOAD_QUEUED || state == dl.DOWNLOAD_DOWNLOADING || - state == dl.DOWNLOAD_PAUSED; + state == dl.DOWNLOAD_PAUSED || + state == dl.DOWNLOAD_SCANNING; ]]> diff --git a/mozilla/toolkit/mozapps/downloads/content/downloads.js b/mozilla/toolkit/mozapps/downloads/content/downloads.js index d7c4d80d2e5..222694b1213 100644 --- a/mozilla/toolkit/mozapps/downloads/content/downloads.js +++ b/mozilla/toolkit/mozapps/downloads/content/downloads.js @@ -47,8 +47,6 @@ const PREF_BDM_CLOSEWHENDONE = "browser.download.manager.closeWhenDone"; const PREF_BDM_ALERTONEXEOPEN = "browser.download.manager.alertOnEXEOpen"; -const PREF_BDM_DISPLAYEDHISTORYDAYS = - "browser.download.manager.displayedHistoryDays"; const nsLocalFile = Components.Constructor("@mozilla.org/file/local;1", "nsILocalFile", "initWithPath"); @@ -59,13 +57,18 @@ var Ci = Components.interfaces; const nsIDM = Ci.nsIDownloadManager; let gDownloadManager = Cc["@mozilla.org/download-manager;1"].getService(nsIDM); -var gDownloadListener = null; -var gDownloadsView = null; -var gDownloadsActiveArea = null; -var gDownloadsDoneArea = null; -var gDownloadInfoPopup = null; -var gUserInterfered = false; -var gSearching = false; +let gDownloadListener = null; +let gDownloadsView = null; +let gSearchBox = null; +let gDownloadInfoPopup = null; +let gSearchTerms = ""; +let gBuilder = 0; + +// Control the performance of the incremental list building by setting how many +// milliseconds to wait before building more of the list and how many items to +// add between each delay. +const gListBuildDelay = 100; +const gListBuildChunk = 10; // If the user has interacted with the window in a significant way, we should // not auto-close the window. Tough UI decisions about what is "significant." @@ -101,12 +104,13 @@ let gStr = { fileExecutableSecurityWarningDontAsk: "fileExecutableSecurityWarningDontAsk" }; -// base query used to display download items, use replaceInsert to set WHERE -let gBaseQuery = "SELECT id, target, name, source, state, startTime, " + - "endTime, referrer, currBytes, maxBytes " + - "FROM moz_downloads " + - "WHERE #1 " + - "ORDER BY endTime ASC, startTime ASC"; +// The statement to query for downloads that are active or match the search +let gStmt = gDownloadManager.DBConnection.createStatement( + "SELECT id, target, name, source, state, startTime, endTime, referrer, " + + "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " + + "FROM moz_downloads " + + "WHERE isActive OR name LIKE ?6 ESCAPE '/' " + + "ORDER BY isActive DESC, endTime DESC, startTime DESC"); //////////////////////////////////////////////////////////////////////////////// //// Utility Functions @@ -119,43 +123,6 @@ function fireEventForElement(aElement, aEventType) aElement.dispatchEvent(e); } -function createDownloadItem(aID, aFile, aTarget, aURI, aState, aProgress, - aStartTime, aEndTime, aReferrer, aCurrBytes, - aMaxBytes) -{ - var dl = document.createElement("richlistitem"); - dl.setAttribute("type", "download"); - dl.setAttribute("id", "dl" + aID); - dl.setAttribute("dlid", aID); - dl.setAttribute("image", "moz-icon://" + aFile + "?size=32"); - dl.setAttribute("file", aFile); - dl.setAttribute("target", aTarget); - dl.setAttribute("uri", aURI); - dl.setAttribute("state", aState); - dl.setAttribute("progress", aProgress); - dl.setAttribute("startTime", aStartTime); - dl.setAttribute("endTime", aEndTime); - if (aReferrer) - dl.setAttribute("referrer", aReferrer); - dl.setAttribute("currBytes", aCurrBytes); - dl.setAttribute("maxBytes", aMaxBytes); - dl.setAttribute("lastSeconds", Infinity); - - updateTime(dl); - updateStatus(dl); - - try { - var file = getLocalFileFromNativePathOrUrl(aFile); - dl.setAttribute("path", file.nativePath || file.path); - return dl; - } - catch (ex) { - // aFile might not be a file: url or a valid native path - // see bug #392386 for details - } - return null; -} - function getDownload(aID) { return document.getElementById("dl" + aID); @@ -181,13 +148,21 @@ function downloadCompleted(aDownload) // Show the finish time because we're done updateTime(dl); - // If we are displaying search results, we do not want to add it to the list - // of completed downloads - if (!gSearching) { - gDownloadsView.insertBefore(dl, gDownloadsDoneArea.nextSibling); - evenOddCellAttribution(); - } else + // If we aren't displaying search results, move the download to after the + // active ones + if (gSearchTerms.length == 0) { + // Iterate down until we find a non-active download + let next = dl.nextSibling; + while (next && next.inProgress) + next = next.nextSibling; + + // Move the item and color everything after where it moved from + let fixup = dl.nextSibling; + gDownloadsView.insertBefore(dl, next); + stripeifyList(fixup); + } else { removeFromView(dl); + } // getTypeFromFile fails if it can't find a type for this file. try { @@ -459,10 +434,9 @@ function onUpdateProgress() function Startup() { - gDownloadsView = document.getElementById("downloadView"); - gDownloadsActiveArea = document.getElementById("active-downloads-area"); - gDownloadsDoneArea = document.getElementById("done-downloads-area"); - gDownloadInfoPopup = document.getElementById("information"); + gDownloadsView = document.getElementById("downloadView"); + gSearchBox = document.getElementById("searchbox"); + gDownloadInfoPopup = document.getElementById("information"); // convert strings to those in the string bundle let (sb = document.getElementById("downloadStrings")) { @@ -471,7 +445,7 @@ function Startup() gStr[name] = typeof value == "string" ? getStr(value) : value.map(getStr); } - buildDefaultView(); + buildDownloadList(); // View event listeners gDownloadsView.addEventListener("dblclick", onDownloadDblClick, false); @@ -498,6 +472,10 @@ function Shutdown() let obs = Cc["@mozilla.org/observer-service;1"]. getService(Ci.nsIObserverService); obs.removeObserver(gDownloadObserver, "download-manager-remove-download"); + + clearTimeout(gBuilder); + gStmt.reset(); + gStmt.finalize(); } let gDownloadObserver = { @@ -507,7 +485,7 @@ let gDownloadObserver = { // A null subject here indicates "remove all" if (!aSubject) { // Rebuild the default view - buildDefaultView(); + buildDownloadList(); break; } @@ -781,9 +759,8 @@ var gDownloadViewController = { function setSearchboxFocus() { - var searchbox = document.getElementById("searchbox"); - searchbox.focus(); - searchbox.select(); + gSearchBox.focus(); + gSearchBox.select(); } function onDownloadShowInfo() @@ -805,7 +782,46 @@ function openExternal(aFile) } //////////////////////////////////////////////////////////////////////////////// -//// Utility functions +//// Utility Functions + +/** + * Create a download richlistitem with the provided attributes. Some attributes + * are *required* while optional ones will only be set on the item if provided. + * + * @param aAttrs + * An object that must have the following properties: dlid, file, + * target, uri, state, progress, startTime, endTime, currBytes, + * maxBytes; optional properties: referrer + * @return An initialized download richlistitem + */ +function createDownloadItem(aAttrs) +{ + let dl = document.createElement("richlistitem"); + + // Copy the attributes from the argument into the item + for (let attr in aAttrs) + dl.setAttribute(attr, aAttrs[attr]); + + // Initialize other attributes + dl.setAttribute("type", "download"); + dl.setAttribute("id", "dl" + aAttrs.dlid); + dl.setAttribute("image", "moz-icon://" + aAttrs.file + "?size=32"); + dl.setAttribute("lastSeconds", Infinity); + + // Initialize more complex attributes + updateTime(dl); + updateStatus(dl); + + try { + let file = getLocalFileFromNativePathOrUrl(aAttrs.file); + dl.setAttribute("path", file.nativePath || file.path); + return dl; + } catch (e) { + // aFile might not be a file: url or a valid native path + // see bug #392386 for details + } + return null; +} /** * Updates the status for a download item depending on its state @@ -1051,7 +1067,9 @@ function removeFromView(aDownload) let index = gDownloadsView.selectedIndex; gDownloadsView.removeChild(aDownload); gDownloadsView.selectedIndex = Math.min(index, gDownloadsView.itemCount - 1); - evenOddCellAttribution(); + + // Color everything after from the newly selected item + stripeifyList(gDownloadsView.selectedItem); } function getReferrerOrSource(aDownload) @@ -1065,204 +1083,169 @@ function getReferrerOrSource(aDownload) } /** - * Builds the default view that the download manager starts out with. + * Initiate building the download list to have the active downloads followed by + * completed ones filtered by the search term if necessary. */ -function buildDefaultView() +function buildDownloadList() { - buildActiveDownloadsList(); + // Clear out values before using them + clearTimeout(gBuilder); + gStmt.reset(); + gSearchTerms = ""; - let pref = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefBranch); - let days = pref.getIntPref(PREF_BDM_DISPLAYEDHISTORYDAYS); - buildDownloadListWithTime(Date.now() - days * 24 * 60 * 60 * 1000); - - // select the first visible download item, if any - var children = gDownloadsView.children; - if (children.length > 0) - gDownloadsView.selectedItem = children[0]; -} - - /** - * Gives every second cell an odd attribute so they can receive alternating - * backgrounds from the stylesheets. - */ -function evenOddCellAttribution() -{ - let alternateCell = false; - let allDownloadsInView = gDownloadsView.getElementsByTagName("richlistitem"); - - for (let i = 0; i < allDownloadsInView.length; i++) { - if (alternateCell) - allDownloadsInView[i].setAttribute("alternate", "true"); - else - allDownloadsInView[i].removeAttribute("alternate"); - - alternateCell = !alternateCell; + // Clear the list before adding items by replacing with a shallow copy + let (empty = gDownloadsView.cloneNode(false)) { + gDownloadsView.parentNode.replaceChild(empty, gDownloadsView); + gDownloadsView = empty; } + + // If the search box isn't empty, trim the search terms + if (!gSearchBox.hasAttribute("empty")) + gSearchTerms = gSearchBox.value.replace(/^\s+|\s+$/, ""); + + let like = "%" + gStmt.escapeStringForLIKE(gSearchTerms, "/") + "%"; + + try { + gStmt.bindInt32Parameter(0, nsIDM.DOWNLOAD_NOTSTARTED); + gStmt.bindInt32Parameter(1, nsIDM.DOWNLOAD_DOWNLOADING); + gStmt.bindInt32Parameter(2, nsIDM.DOWNLOAD_PAUSED); + gStmt.bindInt32Parameter(3, nsIDM.DOWNLOAD_QUEUED); + gStmt.bindInt32Parameter(4, nsIDM.DOWNLOAD_SCANNING); + gStmt.bindStringParameter(5, like); + } catch (e) { + // Something must have gone wrong when binding, so clear and quit + gStmt.reset(); + return; + } + + // Start building the list and select the first item + stepListBuilder(1); + gDownloadsView.selectedIndex = 0; } /** - * Builds the downloads list with a given statement and reference node. + * Incrementally build the download list by adding at most the requested number + * of items if there are items to add. After doing that, it will schedule + * another chunk of items specified by gListBuildDelay and gListBuildChunk. * - * @param aStmt - * The compiled SQL statement to build with. This needs to have the - * following columns in this order to work properly: - * id, target, name, source, state, startTime, endTime, referrer, - * currBytes, and maxBytes - * This statement should be ordered on the endTime ASC so that the end - * result is a list of downloads with their end time's descending. - * @param aRef - * The node we use for placement of the download objects. We place each - * new node above the previously inserted one. + * @param aNumItems + * Number of items to add to the list before taking a break */ -function buildDownloadList(aStmt, aRef) -{ - while (aRef.nextSibling && aRef.nextSibling.tagName == "richlistitem") - gDownloadsView.removeChild(aRef.nextSibling); +function stepListBuilder(aNumItems) { + try { + // If we're done adding all items, we can quit + if (!gStmt.executeStep()) + return; - while (aStmt.executeStep()) { - let id = aStmt.getInt64(0); - let state = aStmt.getInt32(4); - let percentComplete = 100; - if (state == nsIDM.DOWNLOAD_NOTSTARTED || - state == nsIDM.DOWNLOAD_DOWNLOADING || - state == nsIDM.DOWNLOAD_PAUSED) { - // so we have an in-progress download that we need to determine the - // proper percentage complete for. This download will actually be in - // the active downloads array internally, so calling getDownload is cheap. - let dl = gDownloadManager.getDownload(id); - percentComplete = dl.percentComplete; + // Try to get the attribute values from the statement + let attrs = { + dlid: gStmt.getInt64(0), + file: gStmt.getString(1), + target: gStmt.getString(2), + uri: gStmt.getString(3), + state: gStmt.getInt32(4), + startTime: Math.round(gStmt.getInt64(5) / 1000), + endTime: Math.round(gStmt.getInt64(6) / 1000), + currBytes: gStmt.getInt64(8), + maxBytes: gStmt.getInt64(9) + }; + + // Only add the referrer if it's not null + let (referrer = gStmt.getString(7)) { + if (referrer) + attrs.referrer = referrer; } - let dl = createDownloadItem(id, - aStmt.getString(1), - aStmt.getString(2), - aStmt.getString(3), - state, - percentComplete, - Math.round(aStmt.getInt64(5) / 1000), - Math.round(aStmt.getInt64(6) / 1000), - aStmt.getString(7), - aStmt.getInt64(8), - aStmt.getInt64(9)); - if (dl) - gDownloadsView.insertBefore(dl, aRef.nextSibling); - } - evenOddCellAttribution(); -} -var gActiveDownloadsQuery = null; -function buildActiveDownloadsList() -{ - // Are there any active downloads? - if (gDownloadManager.activeDownloadCount == 0) + // If the download is active, grab the real progress, otherwise default 100 + attrs.progress = gStmt.getInt32(10) ? + gDownloadManager.getDownload(attrs.dlid).percentComplete : 100; + + // Make the item and add it to the end + let item = createDownloadItem(attrs); + if (item) { + // Add item to the end and color just that one item + gDownloadsView.appendChild(item); + stripeifyList(item); + } + } catch (e) { + // Something went wrong when stepping or getting values, so clear and quit + gStmt.reset(); return; - - // repopulate the list - var db = gDownloadManager.DBConnection; - var stmt = gActiveDownloadsQuery; - if (!stmt) { - stmt = db.createStatement(replaceInsert(gBaseQuery, 1, - "state = ?1 OR state = ?2 OR state = ?3 OR state = ?4 OR state = ?5")); - gActiveDownloadsQuery = stmt; } - try { - stmt.bindInt32Parameter(0, nsIDM.DOWNLOAD_NOTSTARTED); - stmt.bindInt32Parameter(1, nsIDM.DOWNLOAD_DOWNLOADING); - stmt.bindInt32Parameter(2, nsIDM.DOWNLOAD_PAUSED); - stmt.bindInt32Parameter(3, nsIDM.DOWNLOAD_QUEUED); - stmt.bindInt32Parameter(4, nsIDM.DOWNLOAD_SCANNING); - buildDownloadList(stmt, gDownloadsActiveArea); - } finally { - stmt.reset(); + // Add another item to the list if we should; otherwise, let the UI update + // and continue later + if (aNumItems > 1) { + stepListBuilder(aNumItems - 1); + } else { + // Use a shorter delay for earlier downloads to display them faster + let delay = Math.min(gDownloadsView.itemCount, gListBuildDelay); + gBuilder = setTimeout(stepListBuilder, delay, gListBuildChunk); } } /** - * Builds the download view with downloads from a given time until now. + * Add a download to the front of the download list * - * @param aTime - * The time that we want to start displaying downloads from. This time - * is in milliseconds (what is returned from Date.now()). + * @param aDownload + * The nsIDownload to make into a richlistitem */ -var gDownloadListWithTimeQuery = null; -function buildDownloadListWithTime(aTime) +function prependList(aDownload) { - var db = gDownloadManager.DBConnection; - var stmt = gDownloadListWithTimeQuery; - if (!stmt) { - stmt = db.createStatement(replaceInsert(gBaseQuery, 1, "startTime >= ?1 " + - "AND (state = ?2 OR state = ?3 OR state = ?4 OR state = ?5 OR state = ?6)")); - gDownloadListWithTimeQuery = stmt; - } + let attrs = { + dlid: aDownload.id, + file: aDownload.target.spec, + target: aDownload.displayName, + uri: aDownload.source.spec, + state: aDownload.state, + progress: aDownload.percentComplete, + startTime: Math.round(aDownload.startTime / 1000), + endTime: Date.now(), + currBytes: aDownload.amountTransferred, + maxBytes: aDownload.size + }; - try { - stmt.bindInt64Parameter(0, aTime * 1000); - stmt.bindInt32Parameter(1, nsIDM.DOWNLOAD_FINISHED); - stmt.bindInt32Parameter(2, nsIDM.DOWNLOAD_FAILED); - stmt.bindInt32Parameter(3, nsIDM.DOWNLOAD_CANCELED); - stmt.bindInt32Parameter(4, nsIDM.DOWNLOAD_BLOCKED); - stmt.bindInt32Parameter(5, nsIDM.DOWNLOAD_DIRTY); - buildDownloadList(stmt, gDownloadsDoneArea); - } finally { - stmt.reset(); + // Make the item and add it to the beginning + let item = createDownloadItem(attrs); + if (item) { + // Add item to the beginning and color the whole list + gDownloadsView.insertBefore(item, gDownloadsView.firstChild); + stripeifyList(item); } } /** - * Builds the download list with an array of search terms. This also changes - * the label of the second group of downloads to search results (or the locale - * equivalent). + * Stripeify the download list by setting or clearing the "alternate" attribute + * on items starting from a particular item and continuing to the end. * - * @param aTerms - * An array of search terms that will be checked for past downloads. If - * this array is empty, we clear the search results and build the default - * view. + * @param aItem + * Download rishlist item to start stripeifying */ -function buildDownloadListWithSearch(aTerms) +function stripeifyList(aItem) { - gSearching = true; + let alt = "alternate"; + // Set the item to be opposite of the other + let flipFrom = function(aOther) aOther && aOther.hasAttribute(alt) ? + aItem.removeAttribute(alt) : aItem.setAttribute(alt, "true"); - // remove and trailing or leading whitespace first - aTerms = aTerms.replace(/^\s+|\s+$/, ""); - if (aTerms.length == 0) { - gSearching = false; - buildDefaultView(); - return; + // Keep coloring items as the opposite of its previous until no more + while (aItem) { + flipFrom(aItem.previousSibling); + aItem = aItem.nextSibling; } - - var db = gDownloadManager.DBConnection; - let stmt = db.createStatement(replaceInsert(gBaseQuery, 1, - "name LIKE ?1 ESCAPE '/' AND state != ?2 AND state != ?3")); - - try { - var paramForLike = stmt.escapeStringForLIKE(aTerms, '/'); - stmt.bindStringParameter(0, "%" + paramForLike + "%"); - stmt.bindInt32Parameter(1, nsIDM.DOWNLOAD_DOWNLOADING); - stmt.bindInt32Parameter(2, nsIDM.DOWNLOAD_PAUSED); - buildDownloadList(stmt, gDownloadsDoneArea); - } finally { - stmt.reset(); - } -} - -function performSearch() { - buildDownloadListWithSearch(document.getElementById("searchbox").value); } function onSearchboxBlur() { - var searchbox = document.getElementById("searchbox"); - if (searchbox.value == "") { - searchbox.setAttribute("empty", "true"); - searchbox.value = searchbox.getAttribute("defaultValue"); + if (gSearchBox.value == "") { + gSearchBox.setAttribute("empty", "true"); + gSearchBox.value = gSearchBox.getAttribute("defaultValue"); } } function onSearchboxFocus() { - var searchbox = document.getElementById("searchbox"); - if (searchbox.hasAttribute("empty")) { - searchbox.value = ""; - searchbox.removeAttribute("empty"); + if (gSearchBox.hasAttribute("empty")) { + gSearchBox.value = ""; + gSearchBox.removeAttribute("empty"); } } diff --git a/mozilla/toolkit/mozapps/downloads/content/downloads.xul b/mozilla/toolkit/mozapps/downloads/content/downloads.xul index 606177eb7d8..6682e137672 100644 --- a/mozilla/toolkit/mozapps/downloads/content/downloads.xul +++ b/mozilla/toolkit/mozapps/downloads/content/downloads.xul @@ -25,6 +25,7 @@ # Ben Goodger (v2.0) # Blake Ross # Shawn Wilsher (v3.0) +# Edward Lee # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or @@ -193,13 +194,11 @@ -