1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/downloads/content/downloads.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1331 @@ 1.4 +# -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 +# This Source Code Form is subject to the terms of the Mozilla Public 1.6 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.8 + 1.9 +//////////////////////////////////////////////////////////////////////////////// 1.10 +//// Globals 1.11 + 1.12 +const PREF_BDM_CLOSEWHENDONE = "browser.download.manager.closeWhenDone"; 1.13 +const PREF_BDM_ALERTONEXEOPEN = "browser.download.manager.alertOnEXEOpen"; 1.14 +const PREF_BDM_SCANWHENDONE = "browser.download.manager.scanWhenDone"; 1.15 + 1.16 +const nsLocalFile = Components.Constructor("@mozilla.org/file/local;1", 1.17 + "nsILocalFile", "initWithPath"); 1.18 + 1.19 +var Cc = Components.classes; 1.20 +var Ci = Components.interfaces; 1.21 +let Cu = Components.utils; 1.22 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.23 +Cu.import("resource://gre/modules/DownloadUtils.jsm"); 1.24 +Cu.import("resource://gre/modules/Services.jsm"); 1.25 + 1.26 +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", 1.27 + "resource://gre/modules/PluralForm.jsm"); 1.28 + 1.29 +const nsIDM = Ci.nsIDownloadManager; 1.30 + 1.31 +let gDownloadManager = Cc["@mozilla.org/download-manager;1"].getService(nsIDM); 1.32 +let gDownloadManagerUI = Cc["@mozilla.org/download-manager-ui;1"]. 1.33 + getService(Ci.nsIDownloadManagerUI); 1.34 + 1.35 +let gDownloadListener = null; 1.36 +let gDownloadsView = null; 1.37 +let gSearchBox = null; 1.38 +let gSearchTerms = []; 1.39 +let gBuilder = 0; 1.40 + 1.41 +// This variable is used when performing commands on download items and gives 1.42 +// the command the ability to do something after all items have been operated 1.43 +// on. The following convention is used to handle the value of the variable: 1.44 +// whenever we aren't performing a command, the value is |undefined|; just 1.45 +// before executing commands, the value will be set to |null|; and when 1.46 +// commands want to create a callback, they set the value to be a callback 1.47 +// function to be executed after all download items have been visited. 1.48 +let gPerformAllCallback; 1.49 + 1.50 +// Control the performance of the incremental list building by setting how many 1.51 +// milliseconds to wait before building more of the list and how many items to 1.52 +// add between each delay. 1.53 +const gListBuildDelay = 300; 1.54 +const gListBuildChunk = 3; 1.55 + 1.56 +// Array of download richlistitem attributes to check when searching 1.57 +const gSearchAttributes = [ 1.58 + "target", 1.59 + "status", 1.60 + "dateTime", 1.61 +]; 1.62 + 1.63 +// If the user has interacted with the window in a significant way, we should 1.64 +// not auto-close the window. Tough UI decisions about what is "significant." 1.65 +var gUserInteracted = false; 1.66 + 1.67 +// These strings will be converted to the corresponding ones from the string 1.68 +// bundle on startup. 1.69 +let gStr = { 1.70 + paused: "paused", 1.71 + cannotPause: "cannotPause", 1.72 + doneStatus: "doneStatus", 1.73 + doneSize: "doneSize", 1.74 + doneSizeUnknown: "doneSizeUnknown", 1.75 + stateFailed: "stateFailed", 1.76 + stateCanceled: "stateCanceled", 1.77 + stateBlockedParentalControls: "stateBlocked", 1.78 + stateBlockedPolicy: "stateBlockedPolicy", 1.79 + stateDirty: "stateDirty", 1.80 + downloadsTitleFiles: "downloadsTitleFiles", 1.81 + downloadsTitlePercent: "downloadsTitlePercent", 1.82 + fileExecutableSecurityWarningTitle: "fileExecutableSecurityWarningTitle", 1.83 + fileExecutableSecurityWarningDontAsk: "fileExecutableSecurityWarningDontAsk" 1.84 +}; 1.85 + 1.86 +// The statement to query for downloads that are active or match the search 1.87 +let gStmt = null; 1.88 + 1.89 +//////////////////////////////////////////////////////////////////////////////// 1.90 +//// Start/Stop Observers 1.91 + 1.92 +function downloadCompleted(aDownload) 1.93 +{ 1.94 + // The download is changing state, so update the clear list button 1.95 + updateClearListButton(); 1.96 + 1.97 + // Wrap this in try...catch since this can be called while shutting down... 1.98 + // it doesn't really matter if it fails then since well.. we're shutting down 1.99 + // and there's no UI to update! 1.100 + try { 1.101 + let dl = getDownload(aDownload.id); 1.102 + 1.103 + // Update attributes now that we've finished 1.104 + dl.setAttribute("startTime", Math.round(aDownload.startTime / 1000)); 1.105 + dl.setAttribute("endTime", Date.now()); 1.106 + dl.setAttribute("currBytes", aDownload.amountTransferred); 1.107 + dl.setAttribute("maxBytes", aDownload.size); 1.108 + 1.109 + // Move the download below active if it should stay in the list 1.110 + if (downloadMatchesSearch(dl)) { 1.111 + // Iterate down until we find a non-active download 1.112 + let next = dl.nextSibling; 1.113 + while (next && next.inProgress) 1.114 + next = next.nextSibling; 1.115 + 1.116 + // Move the item 1.117 + gDownloadsView.insertBefore(dl, next); 1.118 + } else { 1.119 + removeFromView(dl); 1.120 + } 1.121 + 1.122 + // getTypeFromFile fails if it can't find a type for this file. 1.123 + try { 1.124 + // Refresh the icon, so that executable icons are shown. 1.125 + var mimeService = Cc["@mozilla.org/mime;1"]. 1.126 + getService(Ci.nsIMIMEService); 1.127 + var contentType = mimeService.getTypeFromFile(aDownload.targetFile); 1.128 + 1.129 + var listItem = getDownload(aDownload.id) 1.130 + var oldImage = listItem.getAttribute("image"); 1.131 + // Tacking on contentType bypasses cache 1.132 + listItem.setAttribute("image", oldImage + "&contentType=" + contentType); 1.133 + } catch (e) { } 1.134 + 1.135 + if (gDownloadManager.activeDownloadCount == 0) 1.136 + document.title = document.documentElement.getAttribute("statictitle"); 1.137 + 1.138 + gDownloadManagerUI.getAttention(); 1.139 + } 1.140 + catch (e) { } 1.141 +} 1.142 + 1.143 +function autoRemoveAndClose(aDownload) 1.144 +{ 1.145 + var pref = Cc["@mozilla.org/preferences-service;1"]. 1.146 + getService(Ci.nsIPrefBranch); 1.147 + 1.148 + if (gDownloadManager.activeDownloadCount == 0) { 1.149 + // For the moment, just use the simple heuristic that if this window was 1.150 + // opened by the download process, rather than by the user, it should 1.151 + // auto-close if the pref is set that way. If the user opened it themselves, 1.152 + // it should not close until they explicitly close it. Additionally, the 1.153 + // preference to control the feature may not be set, so defaulting to 1.154 + // keeping the window open. 1.155 + let autoClose = false; 1.156 + try { 1.157 + autoClose = pref.getBoolPref(PREF_BDM_CLOSEWHENDONE); 1.158 + } catch (e) { } 1.159 + var autoOpened = 1.160 + !window.opener || window.opener.location.href == window.location.href; 1.161 + if (autoClose && autoOpened && !gUserInteracted) { 1.162 + gCloseDownloadManager(); 1.163 + return true; 1.164 + } 1.165 + } 1.166 + 1.167 + return false; 1.168 +} 1.169 + 1.170 +// This function can be overwritten by extensions that wish to place the 1.171 +// Download Window in another part of the UI. 1.172 +function gCloseDownloadManager() 1.173 +{ 1.174 + window.close(); 1.175 +} 1.176 + 1.177 +//////////////////////////////////////////////////////////////////////////////// 1.178 +//// Download Event Handlers 1.179 + 1.180 +function cancelDownload(aDownload) 1.181 +{ 1.182 + gDownloadManager.cancelDownload(aDownload.getAttribute("dlid")); 1.183 + 1.184 + // XXXben - 1.185 + // If we got here because we resumed the download, we weren't using a temp file 1.186 + // because we used saveURL instead. (this is because the proper download mechanism 1.187 + // employed by the helper app service isn't fully accessible yet... should be fixed... 1.188 + // talk to bz...) 1.189 + // the upshot is we have to delete the file if it exists. 1.190 + var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file")); 1.191 + 1.192 + if (f.exists()) 1.193 + f.remove(false); 1.194 +} 1.195 + 1.196 +function pauseDownload(aDownload) 1.197 +{ 1.198 + var id = aDownload.getAttribute("dlid"); 1.199 + gDownloadManager.pauseDownload(id); 1.200 +} 1.201 + 1.202 +function resumeDownload(aDownload) 1.203 +{ 1.204 + gDownloadManager.resumeDownload(aDownload.getAttribute("dlid")); 1.205 +} 1.206 + 1.207 +function removeDownload(aDownload) 1.208 +{ 1.209 + gDownloadManager.removeDownload(aDownload.getAttribute("dlid")); 1.210 +} 1.211 + 1.212 +function retryDownload(aDownload) 1.213 +{ 1.214 + removeFromView(aDownload); 1.215 + gDownloadManager.retryDownload(aDownload.getAttribute("dlid")); 1.216 +} 1.217 + 1.218 +function showDownload(aDownload) 1.219 +{ 1.220 + var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file")); 1.221 + 1.222 + try { 1.223 + // Show the directory containing the file and select the file 1.224 + f.reveal(); 1.225 + } catch (e) { 1.226 + // If reveal fails for some reason (e.g., it's not implemented on unix or 1.227 + // the file doesn't exist), try using the parent if we have it. 1.228 + let parent = f.parent.QueryInterface(Ci.nsILocalFile); 1.229 + if (!parent) 1.230 + return; 1.231 + 1.232 + try { 1.233 + // "Double click" the parent directory to show where the file should be 1.234 + parent.launch(); 1.235 + } catch (e) { 1.236 + // If launch also fails (probably because it's not implemented), let the 1.237 + // OS handler try to open the parent 1.238 + openExternal(parent); 1.239 + } 1.240 + } 1.241 +} 1.242 + 1.243 +function onDownloadDblClick(aEvent) 1.244 +{ 1.245 + // Only do the default action for double primary clicks 1.246 + if (aEvent.button == 0 && aEvent.target.selected) 1.247 + doDefaultForSelected(); 1.248 +} 1.249 + 1.250 +function openDownload(aDownload) 1.251 +{ 1.252 + var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file")); 1.253 + if (f.isExecutable()) { 1.254 + var dontAsk = false; 1.255 + var pref = Cc["@mozilla.org/preferences-service;1"]. 1.256 + getService(Ci.nsIPrefBranch); 1.257 + try { 1.258 + dontAsk = !pref.getBoolPref(PREF_BDM_ALERTONEXEOPEN); 1.259 + } catch (e) { } 1.260 + 1.261 +#ifdef XP_WIN 1.262 + // On Vista and above, we rely on native security prompting for 1.263 + // downloaded content unless it's disabled. 1.264 + try { 1.265 + var sysInfo = Cc["@mozilla.org/system-info;1"]. 1.266 + getService(Ci.nsIPropertyBag2); 1.267 + if (parseFloat(sysInfo.getProperty("version")) >= 6 && 1.268 + pref.getBoolPref(PREF_BDM_SCANWHENDONE)) { 1.269 + dontAsk = true; 1.270 + } 1.271 + } catch (ex) { } 1.272 +#endif 1.273 + 1.274 + if (!dontAsk) { 1.275 + var strings = document.getElementById("downloadStrings"); 1.276 + var name = aDownload.getAttribute("target"); 1.277 + var message = strings.getFormattedString("fileExecutableSecurityWarning", [name, name]); 1.278 + 1.279 + let title = gStr.fileExecutableSecurityWarningTitle; 1.280 + let dontAsk = gStr.fileExecutableSecurityWarningDontAsk; 1.281 + 1.282 + var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"]. 1.283 + getService(Ci.nsIPromptService); 1.284 + var checkbox = { value: false }; 1.285 + var open = promptSvc.confirmCheck(window, title, message, dontAsk, checkbox); 1.286 + 1.287 + if (!open) 1.288 + return; 1.289 + pref.setBoolPref(PREF_BDM_ALERTONEXEOPEN, !checkbox.value); 1.290 + } 1.291 + } 1.292 + try { 1.293 + try { 1.294 + let download = gDownloadManager.getDownload(aDownload.getAttribute("dlid")); 1.295 + let mimeInfo = download.MIMEInfo; 1.296 + if (mimeInfo.preferredAction == mimeInfo.useHelperApp) { 1.297 + mimeInfo.launchWithFile(f); 1.298 + return; 1.299 + } 1.300 + } catch (ex) { 1.301 + } 1.302 + f.launch(); 1.303 + } catch (ex) { 1.304 + // if launch fails, try sending it through the system's external 1.305 + // file: URL handler 1.306 + openExternal(f); 1.307 + } 1.308 +} 1.309 + 1.310 +function openReferrer(aDownload) 1.311 +{ 1.312 + openURL(getReferrerOrSource(aDownload)); 1.313 +} 1.314 + 1.315 +function copySourceLocation(aDownload) 1.316 +{ 1.317 + var uri = aDownload.getAttribute("uri"); 1.318 + var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]. 1.319 + getService(Ci.nsIClipboardHelper); 1.320 + 1.321 + // Check if we should initialize a callback 1.322 + if (gPerformAllCallback === null) { 1.323 + let uris = []; 1.324 + gPerformAllCallback = function(aURI) aURI ? uris.push(aURI) : 1.325 + clipboard.copyString(uris.join("\n"), document); 1.326 + } 1.327 + 1.328 + // We have a callback to use, so use it to add a uri 1.329 + if (typeof gPerformAllCallback == "function") 1.330 + gPerformAllCallback(uri); 1.331 + else { 1.332 + // It's a plain copy source, so copy it 1.333 + clipboard.copyString(uri, document); 1.334 + } 1.335 +} 1.336 + 1.337 +/** 1.338 + * Remove the currently shown downloads from the download list. 1.339 + */ 1.340 +function clearDownloadList() { 1.341 + // Clear the whole list if there's no search 1.342 + if (gSearchTerms == "") { 1.343 + gDownloadManager.cleanUp(); 1.344 + return; 1.345 + } 1.346 + 1.347 + // Remove each download starting from the end until we hit a download 1.348 + // that is in progress 1.349 + let item; 1.350 + while ((item = gDownloadsView.lastChild) && !item.inProgress) 1.351 + removeDownload(item); 1.352 + 1.353 + // Clear the input as if the user did it and move focus to the list 1.354 + gSearchBox.value = ""; 1.355 + gSearchBox.doCommand(); 1.356 + gDownloadsView.focus(); 1.357 +} 1.358 + 1.359 +// This is called by the progress listener. 1.360 +var gLastComputedMean = -1; 1.361 +var gLastActiveDownloads = 0; 1.362 +function onUpdateProgress() 1.363 +{ 1.364 + let numActiveDownloads = gDownloadManager.activeDownloadCount; 1.365 + 1.366 + // Use the default title and reset "last" values if there's no downloads 1.367 + if (numActiveDownloads == 0) { 1.368 + document.title = document.documentElement.getAttribute("statictitle"); 1.369 + gLastComputedMean = -1; 1.370 + gLastActiveDownloads = 0; 1.371 + 1.372 + return; 1.373 + } 1.374 + 1.375 + // Establish the mean transfer speed and amount downloaded. 1.376 + var mean = 0; 1.377 + var base = 0; 1.378 + var dls = gDownloadManager.activeDownloads; 1.379 + while (dls.hasMoreElements()) { 1.380 + let dl = dls.getNext(); 1.381 + if (dl.percentComplete < 100 && dl.size > 0) { 1.382 + mean += dl.amountTransferred; 1.383 + base += dl.size; 1.384 + } 1.385 + } 1.386 + 1.387 + // Calculate the percent transferred, unless we don't have a total file size 1.388 + let title = gStr.downloadsTitlePercent; 1.389 + if (base == 0) 1.390 + title = gStr.downloadsTitleFiles; 1.391 + else 1.392 + mean = Math.floor((mean / base) * 100); 1.393 + 1.394 + // Update title of window 1.395 + if (mean != gLastComputedMean || gLastActiveDownloads != numActiveDownloads) { 1.396 + gLastComputedMean = mean; 1.397 + gLastActiveDownloads = numActiveDownloads; 1.398 + 1.399 + // Get the correct plural form and insert number of downloads and percent 1.400 + title = PluralForm.get(numActiveDownloads, title); 1.401 + title = replaceInsert(title, 1, numActiveDownloads); 1.402 + title = replaceInsert(title, 2, mean); 1.403 + 1.404 + document.title = title; 1.405 + } 1.406 +} 1.407 + 1.408 +//////////////////////////////////////////////////////////////////////////////// 1.409 +//// Startup, Shutdown 1.410 + 1.411 +function Startup() 1.412 +{ 1.413 + gDownloadsView = document.getElementById("downloadView"); 1.414 + gSearchBox = document.getElementById("searchbox"); 1.415 + 1.416 + // convert strings to those in the string bundle 1.417 + let (sb = document.getElementById("downloadStrings")) { 1.418 + let getStr = function(string) sb.getString(string); 1.419 + for (let [name, value] in Iterator(gStr)) 1.420 + gStr[name] = typeof value == "string" ? getStr(value) : value.map(getStr); 1.421 + } 1.422 + 1.423 + initStatement(); 1.424 + buildDownloadList(true); 1.425 + 1.426 + // The DownloadProgressListener (DownloadProgressListener.js) handles progress 1.427 + // notifications. 1.428 + gDownloadListener = new DownloadProgressListener(); 1.429 + gDownloadManager.addListener(gDownloadListener); 1.430 + 1.431 + // If the UI was displayed because the user interacted, we need to make sure 1.432 + // we update gUserInteracted accordingly. 1.433 + if (window.arguments[1] == Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED) 1.434 + gUserInteracted = true; 1.435 + 1.436 + // downloads can finish before Startup() does, so check if the window should 1.437 + // close and act accordingly 1.438 + if (!autoRemoveAndClose()) 1.439 + gDownloadsView.focus(); 1.440 + 1.441 + let obs = Cc["@mozilla.org/observer-service;1"]. 1.442 + getService(Ci.nsIObserverService); 1.443 + obs.addObserver(gDownloadObserver, "download-manager-remove-download", false); 1.444 + obs.addObserver(gDownloadObserver, "browser-lastwindow-close-granted", false); 1.445 + 1.446 + // Clear the search box and move focus to the list on escape from the box 1.447 + gSearchBox.addEventListener("keypress", function(e) { 1.448 + if (e.keyCode == e.DOM_VK_ESCAPE) { 1.449 + // Move focus to the list instead of closing the window 1.450 + gDownloadsView.focus(); 1.451 + e.preventDefault(); 1.452 + } 1.453 + }, false); 1.454 + 1.455 + let DownloadTaskbarProgress = 1.456 + Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress; 1.457 + DownloadTaskbarProgress.onDownloadWindowLoad(window); 1.458 +} 1.459 + 1.460 +function Shutdown() 1.461 +{ 1.462 + gDownloadManager.removeListener(gDownloadListener); 1.463 + 1.464 + let obs = Cc["@mozilla.org/observer-service;1"]. 1.465 + getService(Ci.nsIObserverService); 1.466 + obs.removeObserver(gDownloadObserver, "download-manager-remove-download"); 1.467 + obs.removeObserver(gDownloadObserver, "browser-lastwindow-close-granted"); 1.468 + 1.469 + clearTimeout(gBuilder); 1.470 + gStmt.reset(); 1.471 + gStmt.finalize(); 1.472 +} 1.473 + 1.474 +let gDownloadObserver = { 1.475 + observe: function gdo_observe(aSubject, aTopic, aData) { 1.476 + switch (aTopic) { 1.477 + case "download-manager-remove-download": 1.478 + // A null subject here indicates "remove multiple", so we just rebuild. 1.479 + if (!aSubject) { 1.480 + // Rebuild the default view 1.481 + buildDownloadList(true); 1.482 + break; 1.483 + } 1.484 + 1.485 + // Otherwise, remove a single download 1.486 + let id = aSubject.QueryInterface(Ci.nsISupportsPRUint32); 1.487 + let dl = getDownload(id.data); 1.488 + removeFromView(dl); 1.489 + break; 1.490 + case "browser-lastwindow-close-granted": 1.491 +#ifndef XP_MACOSX 1.492 + if (gDownloadManager.activeDownloadCount == 0) { 1.493 + setTimeout(gCloseDownloadManager, 0); 1.494 + } 1.495 +#endif 1.496 + break; 1.497 + } 1.498 + } 1.499 +}; 1.500 + 1.501 +//////////////////////////////////////////////////////////////////////////////// 1.502 +//// View Context Menus 1.503 + 1.504 +var gContextMenus = [ 1.505 + // DOWNLOAD_DOWNLOADING 1.506 + [ 1.507 + "menuitem_pause" 1.508 + , "menuitem_cancel" 1.509 + , "menuseparator" 1.510 + , "menuitem_show" 1.511 + , "menuseparator" 1.512 + , "menuitem_openReferrer" 1.513 + , "menuitem_copyLocation" 1.514 + , "menuseparator" 1.515 + , "menuitem_selectAll" 1.516 + ], 1.517 + // DOWNLOAD_FINISHED 1.518 + [ 1.519 + "menuitem_open" 1.520 + , "menuitem_show" 1.521 + , "menuseparator" 1.522 + , "menuitem_openReferrer" 1.523 + , "menuitem_copyLocation" 1.524 + , "menuseparator" 1.525 + , "menuitem_selectAll" 1.526 + , "menuseparator" 1.527 + , "menuitem_removeFromList" 1.528 + ], 1.529 + // DOWNLOAD_FAILED 1.530 + [ 1.531 + "menuitem_retry" 1.532 + , "menuseparator" 1.533 + , "menuitem_openReferrer" 1.534 + , "menuitem_copyLocation" 1.535 + , "menuseparator" 1.536 + , "menuitem_selectAll" 1.537 + , "menuseparator" 1.538 + , "menuitem_removeFromList" 1.539 + ], 1.540 + // DOWNLOAD_CANCELED 1.541 + [ 1.542 + "menuitem_retry" 1.543 + , "menuseparator" 1.544 + , "menuitem_openReferrer" 1.545 + , "menuitem_copyLocation" 1.546 + , "menuseparator" 1.547 + , "menuitem_selectAll" 1.548 + , "menuseparator" 1.549 + , "menuitem_removeFromList" 1.550 + ], 1.551 + // DOWNLOAD_PAUSED 1.552 + [ 1.553 + "menuitem_resume" 1.554 + , "menuitem_cancel" 1.555 + , "menuseparator" 1.556 + , "menuitem_show" 1.557 + , "menuseparator" 1.558 + , "menuitem_openReferrer" 1.559 + , "menuitem_copyLocation" 1.560 + , "menuseparator" 1.561 + , "menuitem_selectAll" 1.562 + ], 1.563 + // DOWNLOAD_QUEUED 1.564 + [ 1.565 + "menuitem_cancel" 1.566 + , "menuseparator" 1.567 + , "menuitem_show" 1.568 + , "menuseparator" 1.569 + , "menuitem_openReferrer" 1.570 + , "menuitem_copyLocation" 1.571 + , "menuseparator" 1.572 + , "menuitem_selectAll" 1.573 + ], 1.574 + // DOWNLOAD_BLOCKED_PARENTAL 1.575 + [ 1.576 + "menuitem_openReferrer" 1.577 + , "menuitem_copyLocation" 1.578 + , "menuseparator" 1.579 + , "menuitem_selectAll" 1.580 + , "menuseparator" 1.581 + , "menuitem_removeFromList" 1.582 + ], 1.583 + // DOWNLOAD_SCANNING 1.584 + [ 1.585 + "menuitem_show" 1.586 + , "menuseparator" 1.587 + , "menuitem_openReferrer" 1.588 + , "menuitem_copyLocation" 1.589 + , "menuseparator" 1.590 + , "menuitem_selectAll" 1.591 + ], 1.592 + // DOWNLOAD_DIRTY 1.593 + [ 1.594 + "menuitem_openReferrer" 1.595 + , "menuitem_copyLocation" 1.596 + , "menuseparator" 1.597 + , "menuitem_selectAll" 1.598 + , "menuseparator" 1.599 + , "menuitem_removeFromList" 1.600 + ], 1.601 + // DOWNLOAD_BLOCKED_POLICY 1.602 + [ 1.603 + "menuitem_openReferrer" 1.604 + , "menuitem_copyLocation" 1.605 + , "menuseparator" 1.606 + , "menuitem_selectAll" 1.607 + , "menuseparator" 1.608 + , "menuitem_removeFromList" 1.609 + ] 1.610 +]; 1.611 + 1.612 +function buildContextMenu(aEvent) 1.613 +{ 1.614 + if (aEvent.target.id != "downloadContextMenu") 1.615 + return false; 1.616 + 1.617 + var popup = document.getElementById("downloadContextMenu"); 1.618 + while (popup.hasChildNodes()) 1.619 + popup.removeChild(popup.firstChild); 1.620 + 1.621 + if (gDownloadsView.selectedItem) { 1.622 + let dl = gDownloadsView.selectedItem; 1.623 + let idx = parseInt(dl.getAttribute("state")); 1.624 + if (idx < 0) 1.625 + idx = 0; 1.626 + 1.627 + var menus = gContextMenus[idx]; 1.628 + for (let i = 0; i < menus.length; ++i) { 1.629 + let menuitem = document.getElementById(menus[i]).cloneNode(true); 1.630 + let cmd = menuitem.getAttribute("cmd"); 1.631 + if (cmd) 1.632 + menuitem.disabled = !gDownloadViewController.isCommandEnabled(cmd, dl); 1.633 + 1.634 + popup.appendChild(menuitem); 1.635 + } 1.636 + 1.637 + return true; 1.638 + } 1.639 + 1.640 + return false; 1.641 +} 1.642 +//////////////////////////////////////////////////////////////////////////////// 1.643 +//// Drag and Drop 1.644 +var gDownloadDNDObserver = 1.645 +{ 1.646 + onDragStart: function (aEvent) 1.647 + { 1.648 + if (!gDownloadsView.selectedItem) 1.649 + return; 1.650 + var dl = gDownloadsView.selectedItem; 1.651 + var f = getLocalFileFromNativePathOrUrl(dl.getAttribute("file")); 1.652 + if (!f.exists()) 1.653 + return; 1.654 + 1.655 + var dt = aEvent.dataTransfer; 1.656 + dt.mozSetDataAt("application/x-moz-file", f, 0); 1.657 + var url = Services.io.newFileURI(f).spec; 1.658 + dt.setData("text/uri-list", url); 1.659 + dt.setData("text/plain", url); 1.660 + dt.effectAllowed = "copyMove"; 1.661 + dt.addElement(dl); 1.662 + }, 1.663 + 1.664 + onDragOver: function (aEvent) 1.665 + { 1.666 + var types = aEvent.dataTransfer.types; 1.667 + if (types.contains("text/uri-list") || 1.668 + types.contains("text/x-moz-url") || 1.669 + types.contains("text/plain")) 1.670 + aEvent.preventDefault(); 1.671 + }, 1.672 + 1.673 + onDrop: function(aEvent) 1.674 + { 1.675 + var dt = aEvent.dataTransfer; 1.676 + // If dragged item is from our source, do not try to 1.677 + // redownload already downloaded file. 1.678 + if (dt.mozGetDataAt("application/x-moz-file", 0)) 1.679 + return; 1.680 + 1.681 + var url = dt.getData("URL"); 1.682 + var name; 1.683 + if (!url) { 1.684 + url = dt.getData("text/x-moz-url") || dt.getData("text/plain"); 1.685 + [url, name] = url.split("\n"); 1.686 + } 1.687 + if (url) { 1.688 + let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document; 1.689 + saveURL(url, name ? name : url, null, true, true, null, sourceDoc); 1.690 + } 1.691 + } 1.692 +} 1.693 + 1.694 +function pasteHandler() { 1.695 + let trans = Cc["@mozilla.org/widget/transferable;1"]. 1.696 + createInstance(Ci.nsITransferable); 1.697 + trans.init(null); 1.698 + let flavors = ["text/x-moz-url", "text/unicode"]; 1.699 + flavors.forEach(trans.addDataFlavor); 1.700 + 1.701 + Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard); 1.702 + 1.703 + // Getting the data or creating the nsIURI might fail 1.704 + try { 1.705 + let data = {}; 1.706 + trans.getAnyTransferData({}, data, {}); 1.707 + let [url, name] = data.value.QueryInterface(Ci.nsISupportsString).data.split("\n"); 1.708 + 1.709 + if (!url) 1.710 + return; 1.711 + 1.712 + let uri = Services.io.newURI(url, null, null); 1.713 + 1.714 + saveURL(uri.spec, name || uri.spec, null, true, true, null, document); 1.715 + } catch (ex) {} 1.716 +} 1.717 + 1.718 +//////////////////////////////////////////////////////////////////////////////// 1.719 +//// Command Updating and Command Handlers 1.720 + 1.721 +var gDownloadViewController = { 1.722 + isCommandEnabled: function(aCommand, aItem) 1.723 + { 1.724 + let dl = aItem; 1.725 + let download = null; // used for getting an nsIDownload object 1.726 + 1.727 + switch (aCommand) { 1.728 + case "cmd_cancel": 1.729 + return dl.inProgress; 1.730 + case "cmd_open": { 1.731 + let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file")); 1.732 + return dl.openable && file.exists(); 1.733 + } 1.734 + case "cmd_show": { 1.735 + let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file")); 1.736 + return file.exists(); 1.737 + } 1.738 + case "cmd_pause": 1.739 + download = gDownloadManager.getDownload(dl.getAttribute("dlid")); 1.740 + return dl.inProgress && !dl.paused && download.resumable; 1.741 + case "cmd_pauseResume": 1.742 + download = gDownloadManager.getDownload(dl.getAttribute("dlid")); 1.743 + return (dl.inProgress || dl.paused) && download.resumable; 1.744 + case "cmd_resume": 1.745 + download = gDownloadManager.getDownload(dl.getAttribute("dlid")); 1.746 + return dl.paused && download.resumable; 1.747 + case "cmd_openReferrer": 1.748 + return dl.hasAttribute("referrer"); 1.749 + case "cmd_removeFromList": 1.750 + case "cmd_retry": 1.751 + return dl.removable; 1.752 + case "cmd_copyLocation": 1.753 + return true; 1.754 + } 1.755 + return false; 1.756 + }, 1.757 + 1.758 + doCommand: function(aCommand, aItem) 1.759 + { 1.760 + if (this.isCommandEnabled(aCommand, aItem)) 1.761 + this.commands[aCommand](aItem); 1.762 + }, 1.763 + 1.764 + commands: { 1.765 + cmd_cancel: function(aSelectedItem) { 1.766 + cancelDownload(aSelectedItem); 1.767 + }, 1.768 + cmd_open: function(aSelectedItem) { 1.769 + openDownload(aSelectedItem); 1.770 + }, 1.771 + cmd_openReferrer: function(aSelectedItem) { 1.772 + openReferrer(aSelectedItem); 1.773 + }, 1.774 + cmd_pause: function(aSelectedItem) { 1.775 + pauseDownload(aSelectedItem); 1.776 + }, 1.777 + cmd_pauseResume: function(aSelectedItem) { 1.778 + if (aSelectedItem.paused) 1.779 + this.cmd_resume(aSelectedItem); 1.780 + else 1.781 + this.cmd_pause(aSelectedItem); 1.782 + }, 1.783 + cmd_removeFromList: function(aSelectedItem) { 1.784 + removeDownload(aSelectedItem); 1.785 + }, 1.786 + cmd_resume: function(aSelectedItem) { 1.787 + resumeDownload(aSelectedItem); 1.788 + }, 1.789 + cmd_retry: function(aSelectedItem) { 1.790 + retryDownload(aSelectedItem); 1.791 + }, 1.792 + cmd_show: function(aSelectedItem) { 1.793 + showDownload(aSelectedItem); 1.794 + }, 1.795 + cmd_copyLocation: function(aSelectedItem) { 1.796 + copySourceLocation(aSelectedItem); 1.797 + }, 1.798 + } 1.799 +}; 1.800 + 1.801 +/** 1.802 + * Helper function to do commands. 1.803 + * 1.804 + * @param aCmd 1.805 + * The command to be performed. 1.806 + * @param aItem 1.807 + * The richlistitem that represents the download that will have the 1.808 + * command performed on it. If this is null, the command is performed on 1.809 + * all downloads. If the item passed in is not a richlistitem that 1.810 + * represents a download, it will walk up the parent nodes until it finds 1.811 + * a DOM node that is. 1.812 + */ 1.813 +function performCommand(aCmd, aItem) 1.814 +{ 1.815 + let elm = aItem; 1.816 + if (!elm) { 1.817 + // If we don't have a desired download item, do the command for all 1.818 + // selected items. Initialize the callback to null so commands know to add 1.819 + // a callback if they want. We will call the callback with empty arguments 1.820 + // after performing the command on each selected download item. 1.821 + gPerformAllCallback = null; 1.822 + 1.823 + // Convert the nodelist into an array to keep a copy of the download items 1.824 + let items = []; 1.825 + for (let i = gDownloadsView.selectedItems.length; --i >= 0; ) 1.826 + items.unshift(gDownloadsView.selectedItems[i]); 1.827 + 1.828 + // Do the command for each download item 1.829 + for each (let item in items) 1.830 + performCommand(aCmd, item); 1.831 + 1.832 + // Call the callback with no arguments and reset because we're done 1.833 + if (typeof gPerformAllCallback == "function") 1.834 + gPerformAllCallback(); 1.835 + gPerformAllCallback = undefined; 1.836 + 1.837 + return; 1.838 + } else { 1.839 + while (elm.nodeName != "richlistitem" || 1.840 + elm.getAttribute("type") != "download") 1.841 + elm = elm.parentNode; 1.842 + } 1.843 + 1.844 + gDownloadViewController.doCommand(aCmd, elm); 1.845 +} 1.846 + 1.847 +function setSearchboxFocus() 1.848 +{ 1.849 + gSearchBox.focus(); 1.850 + gSearchBox.select(); 1.851 +} 1.852 + 1.853 +function openExternal(aFile) 1.854 +{ 1.855 + var uri = Cc["@mozilla.org/network/io-service;1"]. 1.856 + getService(Ci.nsIIOService).newFileURI(aFile); 1.857 + 1.858 + var protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. 1.859 + getService(Ci.nsIExternalProtocolService); 1.860 + protocolSvc.loadUrl(uri); 1.861 + 1.862 + return; 1.863 +} 1.864 + 1.865 +//////////////////////////////////////////////////////////////////////////////// 1.866 +//// Utility Functions 1.867 + 1.868 +/** 1.869 + * Create a download richlistitem with the provided attributes. Some attributes 1.870 + * are *required* while optional ones will only be set on the item if provided. 1.871 + * 1.872 + * @param aAttrs 1.873 + * An object that must have the following properties: dlid, file, 1.874 + * target, uri, state, progress, startTime, endTime, currBytes, 1.875 + * maxBytes; optional properties: referrer 1.876 + * @return An initialized download richlistitem 1.877 + */ 1.878 +function createDownloadItem(aAttrs) 1.879 +{ 1.880 + let dl = document.createElement("richlistitem"); 1.881 + 1.882 + // Copy the attributes from the argument into the item 1.883 + for (let attr in aAttrs) 1.884 + dl.setAttribute(attr, aAttrs[attr]); 1.885 + 1.886 + // Initialize other attributes 1.887 + dl.setAttribute("type", "download"); 1.888 + dl.setAttribute("id", "dl" + aAttrs.dlid); 1.889 + dl.setAttribute("image", "moz-icon://" + aAttrs.file + "?size=32"); 1.890 + dl.setAttribute("lastSeconds", Infinity); 1.891 + 1.892 + // Initialize more complex attributes 1.893 + updateTime(dl); 1.894 + updateStatus(dl); 1.895 + 1.896 + try { 1.897 + let file = getLocalFileFromNativePathOrUrl(aAttrs.file); 1.898 + dl.setAttribute("path", file.nativePath || file.path); 1.899 + return dl; 1.900 + } catch (e) { 1.901 + // aFile might not be a file: url or a valid native path 1.902 + // see bug #392386 for details 1.903 + } 1.904 + return null; 1.905 +} 1.906 + 1.907 +/** 1.908 + * Updates the disabled state of the buttons of a downlaod. 1.909 + * 1.910 + * @param aItem 1.911 + * The richlistitem representing the download. 1.912 + */ 1.913 +function updateButtons(aItem) 1.914 +{ 1.915 + let buttons = aItem.buttons; 1.916 + 1.917 + for (let i = 0; i < buttons.length; ++i) { 1.918 + let cmd = buttons[i].getAttribute("cmd"); 1.919 + let enabled = gDownloadViewController.isCommandEnabled(cmd, aItem); 1.920 + buttons[i].disabled = !enabled; 1.921 + 1.922 + if ("cmd_pause" == cmd && !enabled) { 1.923 + // We need to add the tooltip indicating that the download cannot be 1.924 + // paused now. 1.925 + buttons[i].setAttribute("tooltiptext", gStr.cannotPause); 1.926 + } 1.927 + } 1.928 +} 1.929 + 1.930 +/** 1.931 + * Updates the status for a download item depending on its state 1.932 + * 1.933 + * @param aItem 1.934 + * The richlistitem that has various download attributes. 1.935 + * @param aDownload 1.936 + * The nsDownload from the backend. This is an optional parameter, but 1.937 + * is useful for certain states such as DOWNLOADING. 1.938 + */ 1.939 +function updateStatus(aItem, aDownload) { 1.940 + let status = ""; 1.941 + let statusTip = ""; 1.942 + 1.943 + let state = Number(aItem.getAttribute("state")); 1.944 + switch (state) { 1.945 + case nsIDM.DOWNLOAD_PAUSED: 1.946 + { 1.947 + let currBytes = Number(aItem.getAttribute("currBytes")); 1.948 + let maxBytes = Number(aItem.getAttribute("maxBytes")); 1.949 + 1.950 + let transfer = DownloadUtils.getTransferTotal(currBytes, maxBytes); 1.951 + status = replaceInsert(gStr.paused, 1, transfer); 1.952 + 1.953 + break; 1.954 + } 1.955 + case nsIDM.DOWNLOAD_DOWNLOADING: 1.956 + { 1.957 + let currBytes = Number(aItem.getAttribute("currBytes")); 1.958 + let maxBytes = Number(aItem.getAttribute("maxBytes")); 1.959 + // If we don't have an active download, assume 0 bytes/sec 1.960 + let speed = aDownload ? aDownload.speed : 0; 1.961 + let lastSec = Number(aItem.getAttribute("lastSeconds")); 1.962 + 1.963 + let newLast; 1.964 + [status, newLast] = 1.965 + DownloadUtils.getDownloadStatus(currBytes, maxBytes, speed, lastSec); 1.966 + 1.967 + // Update lastSeconds to be the new value 1.968 + aItem.setAttribute("lastSeconds", newLast); 1.969 + 1.970 + break; 1.971 + } 1.972 + case nsIDM.DOWNLOAD_FINISHED: 1.973 + case nsIDM.DOWNLOAD_FAILED: 1.974 + case nsIDM.DOWNLOAD_CANCELED: 1.975 + case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: 1.976 + case nsIDM.DOWNLOAD_BLOCKED_POLICY: 1.977 + case nsIDM.DOWNLOAD_DIRTY: 1.978 + { 1.979 + let (stateSize = {}) { 1.980 + stateSize[nsIDM.DOWNLOAD_FINISHED] = function() { 1.981 + // Display the file size, but show "Unknown" for negative sizes 1.982 + let fileSize = Number(aItem.getAttribute("maxBytes")); 1.983 + let sizeText = gStr.doneSizeUnknown; 1.984 + if (fileSize >= 0) { 1.985 + let [size, unit] = DownloadUtils.convertByteUnits(fileSize); 1.986 + sizeText = replaceInsert(gStr.doneSize, 1, size); 1.987 + sizeText = replaceInsert(sizeText, 2, unit); 1.988 + } 1.989 + return sizeText; 1.990 + }; 1.991 + stateSize[nsIDM.DOWNLOAD_FAILED] = function() gStr.stateFailed; 1.992 + stateSize[nsIDM.DOWNLOAD_CANCELED] = function() gStr.stateCanceled; 1.993 + stateSize[nsIDM.DOWNLOAD_BLOCKED_PARENTAL] = function() gStr.stateBlockedParentalControls; 1.994 + stateSize[nsIDM.DOWNLOAD_BLOCKED_POLICY] = function() gStr.stateBlockedPolicy; 1.995 + stateSize[nsIDM.DOWNLOAD_DIRTY] = function() gStr.stateDirty; 1.996 + 1.997 + // Insert 1 is the download size or download state 1.998 + status = replaceInsert(gStr.doneStatus, 1, stateSize[state]()); 1.999 + } 1.1000 + 1.1001 + let [displayHost, fullHost] = 1.1002 + DownloadUtils.getURIHost(getReferrerOrSource(aItem)); 1.1003 + // Insert 2 is the eTLD + 1 or other variations of the host 1.1004 + status = replaceInsert(status, 2, displayHost); 1.1005 + // Set the tooltip to be the full host 1.1006 + statusTip = fullHost; 1.1007 + 1.1008 + break; 1.1009 + } 1.1010 + } 1.1011 + 1.1012 + aItem.setAttribute("status", status); 1.1013 + aItem.setAttribute("statusTip", statusTip != "" ? statusTip : status); 1.1014 +} 1.1015 + 1.1016 +/** 1.1017 + * Updates the time that gets shown for completed download items 1.1018 + * 1.1019 + * @param aItem 1.1020 + * The richlistitem representing a download in the UI 1.1021 + */ 1.1022 +function updateTime(aItem) 1.1023 +{ 1.1024 + // Don't bother updating for things that aren't finished 1.1025 + if (aItem.inProgress) 1.1026 + return; 1.1027 + 1.1028 + let end = new Date(parseInt(aItem.getAttribute("endTime"))); 1.1029 + let [dateCompact, dateComplete] = DownloadUtils.getReadableDates(end); 1.1030 + aItem.setAttribute("dateTime", dateCompact); 1.1031 + aItem.setAttribute("dateTimeTip", dateComplete); 1.1032 +} 1.1033 + 1.1034 +/** 1.1035 + * Helper function to replace a placeholder string with a real string 1.1036 + * 1.1037 + * @param aText 1.1038 + * Source text containing placeholder (e.g., #1) 1.1039 + * @param aIndex 1.1040 + * Index number of placeholder to replace 1.1041 + * @param aValue 1.1042 + * New string to put in place of placeholder 1.1043 + * @return The string with placeholder replaced with the new string 1.1044 + */ 1.1045 +function replaceInsert(aText, aIndex, aValue) 1.1046 +{ 1.1047 + return aText.replace("#" + aIndex, aValue); 1.1048 +} 1.1049 + 1.1050 +/** 1.1051 + * Perform the default action for the currently selected download item 1.1052 + */ 1.1053 +function doDefaultForSelected() 1.1054 +{ 1.1055 + // Make sure we have something selected 1.1056 + let item = gDownloadsView.selectedItem; 1.1057 + if (!item) 1.1058 + return; 1.1059 + 1.1060 + // Get the default action (first item in the menu) 1.1061 + let state = Number(item.getAttribute("state")); 1.1062 + let menuitem = document.getElementById(gContextMenus[state][0]); 1.1063 + 1.1064 + // Try to do the action if the command is enabled 1.1065 + gDownloadViewController.doCommand(menuitem.getAttribute("cmd"), item); 1.1066 +} 1.1067 + 1.1068 +function removeFromView(aDownload) 1.1069 +{ 1.1070 + // Make sure we have an item to remove 1.1071 + if (!aDownload) return; 1.1072 + 1.1073 + let index = gDownloadsView.selectedIndex; 1.1074 + gDownloadsView.removeChild(aDownload); 1.1075 + gDownloadsView.selectedIndex = Math.min(index, gDownloadsView.itemCount - 1); 1.1076 + 1.1077 + // We might have removed the last item, so update the clear list button 1.1078 + updateClearListButton(); 1.1079 +} 1.1080 + 1.1081 +function getReferrerOrSource(aDownload) 1.1082 +{ 1.1083 + // Give the referrer if we have it set 1.1084 + if (aDownload.hasAttribute("referrer")) 1.1085 + return aDownload.getAttribute("referrer"); 1.1086 + 1.1087 + // Otherwise, provide the source 1.1088 + return aDownload.getAttribute("uri"); 1.1089 +} 1.1090 + 1.1091 +/** 1.1092 + * Initiate building the download list to have the active downloads followed by 1.1093 + * completed ones filtered by the search term if necessary. 1.1094 + * 1.1095 + * @param aForceBuild 1.1096 + * Force the list to be built even if the search terms don't change 1.1097 + */ 1.1098 +function buildDownloadList(aForceBuild) 1.1099 +{ 1.1100 + // Stringify the previous search 1.1101 + let prevSearch = gSearchTerms.join(" "); 1.1102 + 1.1103 + // Array of space-separated lower-case search terms 1.1104 + gSearchTerms = gSearchBox.value.replace(/^\s+|\s+$/g, ""). 1.1105 + toLowerCase().split(/\s+/); 1.1106 + 1.1107 + // Unless forced, don't rebuild the download list if the search didn't change 1.1108 + if (!aForceBuild && gSearchTerms.join(" ") == prevSearch) 1.1109 + return; 1.1110 + 1.1111 + // Clear out values before using them 1.1112 + clearTimeout(gBuilder); 1.1113 + gStmt.reset(); 1.1114 + 1.1115 + // Clear the list before adding items by replacing with a shallow copy 1.1116 + let (empty = gDownloadsView.cloneNode(false)) { 1.1117 + gDownloadsView.parentNode.replaceChild(empty, gDownloadsView); 1.1118 + gDownloadsView = empty; 1.1119 + } 1.1120 + 1.1121 + try { 1.1122 + gStmt.bindByIndex(0, nsIDM.DOWNLOAD_NOTSTARTED); 1.1123 + gStmt.bindByIndex(1, nsIDM.DOWNLOAD_DOWNLOADING); 1.1124 + gStmt.bindByIndex(2, nsIDM.DOWNLOAD_PAUSED); 1.1125 + gStmt.bindByIndex(3, nsIDM.DOWNLOAD_QUEUED); 1.1126 + gStmt.bindByIndex(4, nsIDM.DOWNLOAD_SCANNING); 1.1127 + } catch (e) { 1.1128 + // Something must have gone wrong when binding, so clear and quit 1.1129 + gStmt.reset(); 1.1130 + return; 1.1131 + } 1.1132 + 1.1133 + // Take a quick break before we actually start building the list 1.1134 + gBuilder = setTimeout(function() { 1.1135 + // Start building the list 1.1136 + stepListBuilder(1); 1.1137 + 1.1138 + // We just tried to add a single item, so we probably need to enable 1.1139 + updateClearListButton(); 1.1140 + }, 0); 1.1141 +} 1.1142 + 1.1143 +/** 1.1144 + * Incrementally build the download list by adding at most the requested number 1.1145 + * of items if there are items to add. After doing that, it will schedule 1.1146 + * another chunk of items specified by gListBuildDelay and gListBuildChunk. 1.1147 + * 1.1148 + * @param aNumItems 1.1149 + * Number of items to add to the list before taking a break 1.1150 + */ 1.1151 +function stepListBuilder(aNumItems) { 1.1152 + try { 1.1153 + // If we're done adding all items, we can quit 1.1154 + if (!gStmt.executeStep()) { 1.1155 + // Send a notification that we finished, but wait for clear list to update 1.1156 + updateClearListButton(); 1.1157 + setTimeout(function() Cc["@mozilla.org/observer-service;1"]. 1.1158 + getService(Ci.nsIObserverService). 1.1159 + notifyObservers(window, "download-manager-ui-done", null), 0); 1.1160 + 1.1161 + return; 1.1162 + } 1.1163 + 1.1164 + // Try to get the attribute values from the statement 1.1165 + let attrs = { 1.1166 + dlid: gStmt.getInt64(0), 1.1167 + file: gStmt.getString(1), 1.1168 + target: gStmt.getString(2), 1.1169 + uri: gStmt.getString(3), 1.1170 + state: gStmt.getInt32(4), 1.1171 + startTime: Math.round(gStmt.getInt64(5) / 1000), 1.1172 + endTime: Math.round(gStmt.getInt64(6) / 1000), 1.1173 + currBytes: gStmt.getInt64(8), 1.1174 + maxBytes: gStmt.getInt64(9) 1.1175 + }; 1.1176 + 1.1177 + // Only add the referrer if it's not null 1.1178 + let (referrer = gStmt.getString(7)) { 1.1179 + if (referrer) 1.1180 + attrs.referrer = referrer; 1.1181 + } 1.1182 + 1.1183 + // If the download is active, grab the real progress, otherwise default 100 1.1184 + let isActive = gStmt.getInt32(10); 1.1185 + attrs.progress = isActive ? gDownloadManager.getDownload(attrs.dlid). 1.1186 + percentComplete : 100; 1.1187 + 1.1188 + // Make the item and add it to the end if it's active or matches the search 1.1189 + let item = createDownloadItem(attrs); 1.1190 + if (item && (isActive || downloadMatchesSearch(item))) { 1.1191 + // Add item to the end 1.1192 + gDownloadsView.appendChild(item); 1.1193 + 1.1194 + // Because of the joys of XBL, we can't update the buttons until the 1.1195 + // download object is in the document. 1.1196 + updateButtons(item); 1.1197 + } else { 1.1198 + // We didn't add an item, so bump up the number of items to process, but 1.1199 + // not a whole number so that we eventually do pause for a chunk break 1.1200 + aNumItems += .9; 1.1201 + } 1.1202 + } catch (e) { 1.1203 + // Something went wrong when stepping or getting values, so clear and quit 1.1204 + gStmt.reset(); 1.1205 + return; 1.1206 + } 1.1207 + 1.1208 + // Add another item to the list if we should; otherwise, let the UI update 1.1209 + // and continue later 1.1210 + if (aNumItems > 1) { 1.1211 + stepListBuilder(aNumItems - 1); 1.1212 + } else { 1.1213 + // Use a shorter delay for earlier downloads to display them faster 1.1214 + let delay = Math.min(gDownloadsView.itemCount * 10, gListBuildDelay); 1.1215 + gBuilder = setTimeout(stepListBuilder, delay, gListBuildChunk); 1.1216 + } 1.1217 +} 1.1218 + 1.1219 +/** 1.1220 + * Add a download to the front of the download list 1.1221 + * 1.1222 + * @param aDownload 1.1223 + * The nsIDownload to make into a richlistitem 1.1224 + */ 1.1225 +function prependList(aDownload) 1.1226 +{ 1.1227 + let attrs = { 1.1228 + dlid: aDownload.id, 1.1229 + file: aDownload.target.spec, 1.1230 + target: aDownload.displayName, 1.1231 + uri: aDownload.source.spec, 1.1232 + state: aDownload.state, 1.1233 + progress: aDownload.percentComplete, 1.1234 + startTime: Math.round(aDownload.startTime / 1000), 1.1235 + endTime: Date.now(), 1.1236 + currBytes: aDownload.amountTransferred, 1.1237 + maxBytes: aDownload.size 1.1238 + }; 1.1239 + 1.1240 + // Make the item and add it to the beginning 1.1241 + let item = createDownloadItem(attrs); 1.1242 + if (item) { 1.1243 + // Add item to the beginning 1.1244 + gDownloadsView.insertBefore(item, gDownloadsView.firstChild); 1.1245 + 1.1246 + // Because of the joys of XBL, we can't update the buttons until the 1.1247 + // download object is in the document. 1.1248 + updateButtons(item); 1.1249 + 1.1250 + // We might have added an item to an empty list, so update button 1.1251 + updateClearListButton(); 1.1252 + } 1.1253 +} 1.1254 + 1.1255 +/** 1.1256 + * Check if the download matches the current search term based on the texts 1.1257 + * shown to the user. All search terms are checked to see if each matches any 1.1258 + * of the displayed texts. 1.1259 + * 1.1260 + * @param aItem 1.1261 + * Download richlistitem to check if it matches the current search 1.1262 + * @return Boolean true if it matches the search; false otherwise 1.1263 + */ 1.1264 +function downloadMatchesSearch(aItem) 1.1265 +{ 1.1266 + // Search through the download attributes that are shown to the user and 1.1267 + // make it into one big string for easy combined searching 1.1268 + let combinedSearch = ""; 1.1269 + for each (let attr in gSearchAttributes) 1.1270 + combinedSearch += aItem.getAttribute(attr).toLowerCase() + " "; 1.1271 + 1.1272 + // Make sure each of the terms are found 1.1273 + for each (let term in gSearchTerms) 1.1274 + if (combinedSearch.indexOf(term) == -1) 1.1275 + return false; 1.1276 + 1.1277 + return true; 1.1278 +} 1.1279 + 1.1280 +// we should be using real URLs all the time, but until 1.1281 +// bug 239948 is fully fixed, this will do... 1.1282 +// 1.1283 +// note, this will thrown an exception if the native path 1.1284 +// is not valid (for example a native Windows path on a Mac) 1.1285 +// see bug #392386 for details 1.1286 +function getLocalFileFromNativePathOrUrl(aPathOrUrl) 1.1287 +{ 1.1288 + if (aPathOrUrl.substring(0,7) == "file://") { 1.1289 + // if this is a URL, get the file from that 1.1290 + let ioSvc = Cc["@mozilla.org/network/io-service;1"]. 1.1291 + getService(Ci.nsIIOService); 1.1292 + 1.1293 + // XXX it's possible that using a null char-set here is bad 1.1294 + const fileUrl = ioSvc.newURI(aPathOrUrl, null, null). 1.1295 + QueryInterface(Ci.nsIFileURL); 1.1296 + return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile); 1.1297 + } else { 1.1298 + // if it's a pathname, create the nsILocalFile directly 1.1299 + var f = new nsLocalFile(aPathOrUrl); 1.1300 + 1.1301 + return f; 1.1302 + } 1.1303 +} 1.1304 + 1.1305 +/** 1.1306 + * Update the disabled state of the clear list button based on whether or not 1.1307 + * there are items in the list that can potentially be removed. 1.1308 + */ 1.1309 +function updateClearListButton() 1.1310 +{ 1.1311 + let button = document.getElementById("clearListButton"); 1.1312 + // The button is enabled if we have items in the list and we can clean up 1.1313 + button.disabled = !(gDownloadsView.itemCount && gDownloadManager.canCleanUp); 1.1314 +} 1.1315 + 1.1316 +function getDownload(aID) 1.1317 +{ 1.1318 + return document.getElementById("dl" + aID); 1.1319 +} 1.1320 + 1.1321 +/** 1.1322 + * Initialize the statement which is used to retrieve the list of downloads. 1.1323 + */ 1.1324 +function initStatement() 1.1325 +{ 1.1326 + if (gStmt) 1.1327 + gStmt.finalize(); 1.1328 + 1.1329 + gStmt = gDownloadManager.DBConnection.createStatement( 1.1330 + "SELECT id, target, name, source, state, startTime, endTime, referrer, " + 1.1331 + "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " + 1.1332 + "FROM moz_downloads " + 1.1333 + "ORDER BY isActive DESC, endTime DESC, startTime DESC"); 1.1334 +}