toolkit/mozapps/downloads/content/downloads.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 # -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 5
michael@0 6 ////////////////////////////////////////////////////////////////////////////////
michael@0 7 //// Globals
michael@0 8
michael@0 9 const PREF_BDM_CLOSEWHENDONE = "browser.download.manager.closeWhenDone";
michael@0 10 const PREF_BDM_ALERTONEXEOPEN = "browser.download.manager.alertOnEXEOpen";
michael@0 11 const PREF_BDM_SCANWHENDONE = "browser.download.manager.scanWhenDone";
michael@0 12
michael@0 13 const nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
michael@0 14 "nsILocalFile", "initWithPath");
michael@0 15
michael@0 16 var Cc = Components.classes;
michael@0 17 var Ci = Components.interfaces;
michael@0 18 let Cu = Components.utils;
michael@0 19 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 20 Cu.import("resource://gre/modules/DownloadUtils.jsm");
michael@0 21 Cu.import("resource://gre/modules/Services.jsm");
michael@0 22
michael@0 23 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
michael@0 24 "resource://gre/modules/PluralForm.jsm");
michael@0 25
michael@0 26 const nsIDM = Ci.nsIDownloadManager;
michael@0 27
michael@0 28 let gDownloadManager = Cc["@mozilla.org/download-manager;1"].getService(nsIDM);
michael@0 29 let gDownloadManagerUI = Cc["@mozilla.org/download-manager-ui;1"].
michael@0 30 getService(Ci.nsIDownloadManagerUI);
michael@0 31
michael@0 32 let gDownloadListener = null;
michael@0 33 let gDownloadsView = null;
michael@0 34 let gSearchBox = null;
michael@0 35 let gSearchTerms = [];
michael@0 36 let gBuilder = 0;
michael@0 37
michael@0 38 // This variable is used when performing commands on download items and gives
michael@0 39 // the command the ability to do something after all items have been operated
michael@0 40 // on. The following convention is used to handle the value of the variable:
michael@0 41 // whenever we aren't performing a command, the value is |undefined|; just
michael@0 42 // before executing commands, the value will be set to |null|; and when
michael@0 43 // commands want to create a callback, they set the value to be a callback
michael@0 44 // function to be executed after all download items have been visited.
michael@0 45 let gPerformAllCallback;
michael@0 46
michael@0 47 // Control the performance of the incremental list building by setting how many
michael@0 48 // milliseconds to wait before building more of the list and how many items to
michael@0 49 // add between each delay.
michael@0 50 const gListBuildDelay = 300;
michael@0 51 const gListBuildChunk = 3;
michael@0 52
michael@0 53 // Array of download richlistitem attributes to check when searching
michael@0 54 const gSearchAttributes = [
michael@0 55 "target",
michael@0 56 "status",
michael@0 57 "dateTime",
michael@0 58 ];
michael@0 59
michael@0 60 // If the user has interacted with the window in a significant way, we should
michael@0 61 // not auto-close the window. Tough UI decisions about what is "significant."
michael@0 62 var gUserInteracted = false;
michael@0 63
michael@0 64 // These strings will be converted to the corresponding ones from the string
michael@0 65 // bundle on startup.
michael@0 66 let gStr = {
michael@0 67 paused: "paused",
michael@0 68 cannotPause: "cannotPause",
michael@0 69 doneStatus: "doneStatus",
michael@0 70 doneSize: "doneSize",
michael@0 71 doneSizeUnknown: "doneSizeUnknown",
michael@0 72 stateFailed: "stateFailed",
michael@0 73 stateCanceled: "stateCanceled",
michael@0 74 stateBlockedParentalControls: "stateBlocked",
michael@0 75 stateBlockedPolicy: "stateBlockedPolicy",
michael@0 76 stateDirty: "stateDirty",
michael@0 77 downloadsTitleFiles: "downloadsTitleFiles",
michael@0 78 downloadsTitlePercent: "downloadsTitlePercent",
michael@0 79 fileExecutableSecurityWarningTitle: "fileExecutableSecurityWarningTitle",
michael@0 80 fileExecutableSecurityWarningDontAsk: "fileExecutableSecurityWarningDontAsk"
michael@0 81 };
michael@0 82
michael@0 83 // The statement to query for downloads that are active or match the search
michael@0 84 let gStmt = null;
michael@0 85
michael@0 86 ////////////////////////////////////////////////////////////////////////////////
michael@0 87 //// Start/Stop Observers
michael@0 88
michael@0 89 function downloadCompleted(aDownload)
michael@0 90 {
michael@0 91 // The download is changing state, so update the clear list button
michael@0 92 updateClearListButton();
michael@0 93
michael@0 94 // Wrap this in try...catch since this can be called while shutting down...
michael@0 95 // it doesn't really matter if it fails then since well.. we're shutting down
michael@0 96 // and there's no UI to update!
michael@0 97 try {
michael@0 98 let dl = getDownload(aDownload.id);
michael@0 99
michael@0 100 // Update attributes now that we've finished
michael@0 101 dl.setAttribute("startTime", Math.round(aDownload.startTime / 1000));
michael@0 102 dl.setAttribute("endTime", Date.now());
michael@0 103 dl.setAttribute("currBytes", aDownload.amountTransferred);
michael@0 104 dl.setAttribute("maxBytes", aDownload.size);
michael@0 105
michael@0 106 // Move the download below active if it should stay in the list
michael@0 107 if (downloadMatchesSearch(dl)) {
michael@0 108 // Iterate down until we find a non-active download
michael@0 109 let next = dl.nextSibling;
michael@0 110 while (next && next.inProgress)
michael@0 111 next = next.nextSibling;
michael@0 112
michael@0 113 // Move the item
michael@0 114 gDownloadsView.insertBefore(dl, next);
michael@0 115 } else {
michael@0 116 removeFromView(dl);
michael@0 117 }
michael@0 118
michael@0 119 // getTypeFromFile fails if it can't find a type for this file.
michael@0 120 try {
michael@0 121 // Refresh the icon, so that executable icons are shown.
michael@0 122 var mimeService = Cc["@mozilla.org/mime;1"].
michael@0 123 getService(Ci.nsIMIMEService);
michael@0 124 var contentType = mimeService.getTypeFromFile(aDownload.targetFile);
michael@0 125
michael@0 126 var listItem = getDownload(aDownload.id)
michael@0 127 var oldImage = listItem.getAttribute("image");
michael@0 128 // Tacking on contentType bypasses cache
michael@0 129 listItem.setAttribute("image", oldImage + "&contentType=" + contentType);
michael@0 130 } catch (e) { }
michael@0 131
michael@0 132 if (gDownloadManager.activeDownloadCount == 0)
michael@0 133 document.title = document.documentElement.getAttribute("statictitle");
michael@0 134
michael@0 135 gDownloadManagerUI.getAttention();
michael@0 136 }
michael@0 137 catch (e) { }
michael@0 138 }
michael@0 139
michael@0 140 function autoRemoveAndClose(aDownload)
michael@0 141 {
michael@0 142 var pref = Cc["@mozilla.org/preferences-service;1"].
michael@0 143 getService(Ci.nsIPrefBranch);
michael@0 144
michael@0 145 if (gDownloadManager.activeDownloadCount == 0) {
michael@0 146 // For the moment, just use the simple heuristic that if this window was
michael@0 147 // opened by the download process, rather than by the user, it should
michael@0 148 // auto-close if the pref is set that way. If the user opened it themselves,
michael@0 149 // it should not close until they explicitly close it. Additionally, the
michael@0 150 // preference to control the feature may not be set, so defaulting to
michael@0 151 // keeping the window open.
michael@0 152 let autoClose = false;
michael@0 153 try {
michael@0 154 autoClose = pref.getBoolPref(PREF_BDM_CLOSEWHENDONE);
michael@0 155 } catch (e) { }
michael@0 156 var autoOpened =
michael@0 157 !window.opener || window.opener.location.href == window.location.href;
michael@0 158 if (autoClose && autoOpened && !gUserInteracted) {
michael@0 159 gCloseDownloadManager();
michael@0 160 return true;
michael@0 161 }
michael@0 162 }
michael@0 163
michael@0 164 return false;
michael@0 165 }
michael@0 166
michael@0 167 // This function can be overwritten by extensions that wish to place the
michael@0 168 // Download Window in another part of the UI.
michael@0 169 function gCloseDownloadManager()
michael@0 170 {
michael@0 171 window.close();
michael@0 172 }
michael@0 173
michael@0 174 ////////////////////////////////////////////////////////////////////////////////
michael@0 175 //// Download Event Handlers
michael@0 176
michael@0 177 function cancelDownload(aDownload)
michael@0 178 {
michael@0 179 gDownloadManager.cancelDownload(aDownload.getAttribute("dlid"));
michael@0 180
michael@0 181 // XXXben -
michael@0 182 // If we got here because we resumed the download, we weren't using a temp file
michael@0 183 // because we used saveURL instead. (this is because the proper download mechanism
michael@0 184 // employed by the helper app service isn't fully accessible yet... should be fixed...
michael@0 185 // talk to bz...)
michael@0 186 // the upshot is we have to delete the file if it exists.
michael@0 187 var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
michael@0 188
michael@0 189 if (f.exists())
michael@0 190 f.remove(false);
michael@0 191 }
michael@0 192
michael@0 193 function pauseDownload(aDownload)
michael@0 194 {
michael@0 195 var id = aDownload.getAttribute("dlid");
michael@0 196 gDownloadManager.pauseDownload(id);
michael@0 197 }
michael@0 198
michael@0 199 function resumeDownload(aDownload)
michael@0 200 {
michael@0 201 gDownloadManager.resumeDownload(aDownload.getAttribute("dlid"));
michael@0 202 }
michael@0 203
michael@0 204 function removeDownload(aDownload)
michael@0 205 {
michael@0 206 gDownloadManager.removeDownload(aDownload.getAttribute("dlid"));
michael@0 207 }
michael@0 208
michael@0 209 function retryDownload(aDownload)
michael@0 210 {
michael@0 211 removeFromView(aDownload);
michael@0 212 gDownloadManager.retryDownload(aDownload.getAttribute("dlid"));
michael@0 213 }
michael@0 214
michael@0 215 function showDownload(aDownload)
michael@0 216 {
michael@0 217 var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
michael@0 218
michael@0 219 try {
michael@0 220 // Show the directory containing the file and select the file
michael@0 221 f.reveal();
michael@0 222 } catch (e) {
michael@0 223 // If reveal fails for some reason (e.g., it's not implemented on unix or
michael@0 224 // the file doesn't exist), try using the parent if we have it.
michael@0 225 let parent = f.parent.QueryInterface(Ci.nsILocalFile);
michael@0 226 if (!parent)
michael@0 227 return;
michael@0 228
michael@0 229 try {
michael@0 230 // "Double click" the parent directory to show where the file should be
michael@0 231 parent.launch();
michael@0 232 } catch (e) {
michael@0 233 // If launch also fails (probably because it's not implemented), let the
michael@0 234 // OS handler try to open the parent
michael@0 235 openExternal(parent);
michael@0 236 }
michael@0 237 }
michael@0 238 }
michael@0 239
michael@0 240 function onDownloadDblClick(aEvent)
michael@0 241 {
michael@0 242 // Only do the default action for double primary clicks
michael@0 243 if (aEvent.button == 0 && aEvent.target.selected)
michael@0 244 doDefaultForSelected();
michael@0 245 }
michael@0 246
michael@0 247 function openDownload(aDownload)
michael@0 248 {
michael@0 249 var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
michael@0 250 if (f.isExecutable()) {
michael@0 251 var dontAsk = false;
michael@0 252 var pref = Cc["@mozilla.org/preferences-service;1"].
michael@0 253 getService(Ci.nsIPrefBranch);
michael@0 254 try {
michael@0 255 dontAsk = !pref.getBoolPref(PREF_BDM_ALERTONEXEOPEN);
michael@0 256 } catch (e) { }
michael@0 257
michael@0 258 #ifdef XP_WIN
michael@0 259 // On Vista and above, we rely on native security prompting for
michael@0 260 // downloaded content unless it's disabled.
michael@0 261 try {
michael@0 262 var sysInfo = Cc["@mozilla.org/system-info;1"].
michael@0 263 getService(Ci.nsIPropertyBag2);
michael@0 264 if (parseFloat(sysInfo.getProperty("version")) >= 6 &&
michael@0 265 pref.getBoolPref(PREF_BDM_SCANWHENDONE)) {
michael@0 266 dontAsk = true;
michael@0 267 }
michael@0 268 } catch (ex) { }
michael@0 269 #endif
michael@0 270
michael@0 271 if (!dontAsk) {
michael@0 272 var strings = document.getElementById("downloadStrings");
michael@0 273 var name = aDownload.getAttribute("target");
michael@0 274 var message = strings.getFormattedString("fileExecutableSecurityWarning", [name, name]);
michael@0 275
michael@0 276 let title = gStr.fileExecutableSecurityWarningTitle;
michael@0 277 let dontAsk = gStr.fileExecutableSecurityWarningDontAsk;
michael@0 278
michael@0 279 var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
michael@0 280 getService(Ci.nsIPromptService);
michael@0 281 var checkbox = { value: false };
michael@0 282 var open = promptSvc.confirmCheck(window, title, message, dontAsk, checkbox);
michael@0 283
michael@0 284 if (!open)
michael@0 285 return;
michael@0 286 pref.setBoolPref(PREF_BDM_ALERTONEXEOPEN, !checkbox.value);
michael@0 287 }
michael@0 288 }
michael@0 289 try {
michael@0 290 try {
michael@0 291 let download = gDownloadManager.getDownload(aDownload.getAttribute("dlid"));
michael@0 292 let mimeInfo = download.MIMEInfo;
michael@0 293 if (mimeInfo.preferredAction == mimeInfo.useHelperApp) {
michael@0 294 mimeInfo.launchWithFile(f);
michael@0 295 return;
michael@0 296 }
michael@0 297 } catch (ex) {
michael@0 298 }
michael@0 299 f.launch();
michael@0 300 } catch (ex) {
michael@0 301 // if launch fails, try sending it through the system's external
michael@0 302 // file: URL handler
michael@0 303 openExternal(f);
michael@0 304 }
michael@0 305 }
michael@0 306
michael@0 307 function openReferrer(aDownload)
michael@0 308 {
michael@0 309 openURL(getReferrerOrSource(aDownload));
michael@0 310 }
michael@0 311
michael@0 312 function copySourceLocation(aDownload)
michael@0 313 {
michael@0 314 var uri = aDownload.getAttribute("uri");
michael@0 315 var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
michael@0 316 getService(Ci.nsIClipboardHelper);
michael@0 317
michael@0 318 // Check if we should initialize a callback
michael@0 319 if (gPerformAllCallback === null) {
michael@0 320 let uris = [];
michael@0 321 gPerformAllCallback = function(aURI) aURI ? uris.push(aURI) :
michael@0 322 clipboard.copyString(uris.join("\n"), document);
michael@0 323 }
michael@0 324
michael@0 325 // We have a callback to use, so use it to add a uri
michael@0 326 if (typeof gPerformAllCallback == "function")
michael@0 327 gPerformAllCallback(uri);
michael@0 328 else {
michael@0 329 // It's a plain copy source, so copy it
michael@0 330 clipboard.copyString(uri, document);
michael@0 331 }
michael@0 332 }
michael@0 333
michael@0 334 /**
michael@0 335 * Remove the currently shown downloads from the download list.
michael@0 336 */
michael@0 337 function clearDownloadList() {
michael@0 338 // Clear the whole list if there's no search
michael@0 339 if (gSearchTerms == "") {
michael@0 340 gDownloadManager.cleanUp();
michael@0 341 return;
michael@0 342 }
michael@0 343
michael@0 344 // Remove each download starting from the end until we hit a download
michael@0 345 // that is in progress
michael@0 346 let item;
michael@0 347 while ((item = gDownloadsView.lastChild) && !item.inProgress)
michael@0 348 removeDownload(item);
michael@0 349
michael@0 350 // Clear the input as if the user did it and move focus to the list
michael@0 351 gSearchBox.value = "";
michael@0 352 gSearchBox.doCommand();
michael@0 353 gDownloadsView.focus();
michael@0 354 }
michael@0 355
michael@0 356 // This is called by the progress listener.
michael@0 357 var gLastComputedMean = -1;
michael@0 358 var gLastActiveDownloads = 0;
michael@0 359 function onUpdateProgress()
michael@0 360 {
michael@0 361 let numActiveDownloads = gDownloadManager.activeDownloadCount;
michael@0 362
michael@0 363 // Use the default title and reset "last" values if there's no downloads
michael@0 364 if (numActiveDownloads == 0) {
michael@0 365 document.title = document.documentElement.getAttribute("statictitle");
michael@0 366 gLastComputedMean = -1;
michael@0 367 gLastActiveDownloads = 0;
michael@0 368
michael@0 369 return;
michael@0 370 }
michael@0 371
michael@0 372 // Establish the mean transfer speed and amount downloaded.
michael@0 373 var mean = 0;
michael@0 374 var base = 0;
michael@0 375 var dls = gDownloadManager.activeDownloads;
michael@0 376 while (dls.hasMoreElements()) {
michael@0 377 let dl = dls.getNext();
michael@0 378 if (dl.percentComplete < 100 && dl.size > 0) {
michael@0 379 mean += dl.amountTransferred;
michael@0 380 base += dl.size;
michael@0 381 }
michael@0 382 }
michael@0 383
michael@0 384 // Calculate the percent transferred, unless we don't have a total file size
michael@0 385 let title = gStr.downloadsTitlePercent;
michael@0 386 if (base == 0)
michael@0 387 title = gStr.downloadsTitleFiles;
michael@0 388 else
michael@0 389 mean = Math.floor((mean / base) * 100);
michael@0 390
michael@0 391 // Update title of window
michael@0 392 if (mean != gLastComputedMean || gLastActiveDownloads != numActiveDownloads) {
michael@0 393 gLastComputedMean = mean;
michael@0 394 gLastActiveDownloads = numActiveDownloads;
michael@0 395
michael@0 396 // Get the correct plural form and insert number of downloads and percent
michael@0 397 title = PluralForm.get(numActiveDownloads, title);
michael@0 398 title = replaceInsert(title, 1, numActiveDownloads);
michael@0 399 title = replaceInsert(title, 2, mean);
michael@0 400
michael@0 401 document.title = title;
michael@0 402 }
michael@0 403 }
michael@0 404
michael@0 405 ////////////////////////////////////////////////////////////////////////////////
michael@0 406 //// Startup, Shutdown
michael@0 407
michael@0 408 function Startup()
michael@0 409 {
michael@0 410 gDownloadsView = document.getElementById("downloadView");
michael@0 411 gSearchBox = document.getElementById("searchbox");
michael@0 412
michael@0 413 // convert strings to those in the string bundle
michael@0 414 let (sb = document.getElementById("downloadStrings")) {
michael@0 415 let getStr = function(string) sb.getString(string);
michael@0 416 for (let [name, value] in Iterator(gStr))
michael@0 417 gStr[name] = typeof value == "string" ? getStr(value) : value.map(getStr);
michael@0 418 }
michael@0 419
michael@0 420 initStatement();
michael@0 421 buildDownloadList(true);
michael@0 422
michael@0 423 // The DownloadProgressListener (DownloadProgressListener.js) handles progress
michael@0 424 // notifications.
michael@0 425 gDownloadListener = new DownloadProgressListener();
michael@0 426 gDownloadManager.addListener(gDownloadListener);
michael@0 427
michael@0 428 // If the UI was displayed because the user interacted, we need to make sure
michael@0 429 // we update gUserInteracted accordingly.
michael@0 430 if (window.arguments[1] == Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED)
michael@0 431 gUserInteracted = true;
michael@0 432
michael@0 433 // downloads can finish before Startup() does, so check if the window should
michael@0 434 // close and act accordingly
michael@0 435 if (!autoRemoveAndClose())
michael@0 436 gDownloadsView.focus();
michael@0 437
michael@0 438 let obs = Cc["@mozilla.org/observer-service;1"].
michael@0 439 getService(Ci.nsIObserverService);
michael@0 440 obs.addObserver(gDownloadObserver, "download-manager-remove-download", false);
michael@0 441 obs.addObserver(gDownloadObserver, "browser-lastwindow-close-granted", false);
michael@0 442
michael@0 443 // Clear the search box and move focus to the list on escape from the box
michael@0 444 gSearchBox.addEventListener("keypress", function(e) {
michael@0 445 if (e.keyCode == e.DOM_VK_ESCAPE) {
michael@0 446 // Move focus to the list instead of closing the window
michael@0 447 gDownloadsView.focus();
michael@0 448 e.preventDefault();
michael@0 449 }
michael@0 450 }, false);
michael@0 451
michael@0 452 let DownloadTaskbarProgress =
michael@0 453 Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
michael@0 454 DownloadTaskbarProgress.onDownloadWindowLoad(window);
michael@0 455 }
michael@0 456
michael@0 457 function Shutdown()
michael@0 458 {
michael@0 459 gDownloadManager.removeListener(gDownloadListener);
michael@0 460
michael@0 461 let obs = Cc["@mozilla.org/observer-service;1"].
michael@0 462 getService(Ci.nsIObserverService);
michael@0 463 obs.removeObserver(gDownloadObserver, "download-manager-remove-download");
michael@0 464 obs.removeObserver(gDownloadObserver, "browser-lastwindow-close-granted");
michael@0 465
michael@0 466 clearTimeout(gBuilder);
michael@0 467 gStmt.reset();
michael@0 468 gStmt.finalize();
michael@0 469 }
michael@0 470
michael@0 471 let gDownloadObserver = {
michael@0 472 observe: function gdo_observe(aSubject, aTopic, aData) {
michael@0 473 switch (aTopic) {
michael@0 474 case "download-manager-remove-download":
michael@0 475 // A null subject here indicates "remove multiple", so we just rebuild.
michael@0 476 if (!aSubject) {
michael@0 477 // Rebuild the default view
michael@0 478 buildDownloadList(true);
michael@0 479 break;
michael@0 480 }
michael@0 481
michael@0 482 // Otherwise, remove a single download
michael@0 483 let id = aSubject.QueryInterface(Ci.nsISupportsPRUint32);
michael@0 484 let dl = getDownload(id.data);
michael@0 485 removeFromView(dl);
michael@0 486 break;
michael@0 487 case "browser-lastwindow-close-granted":
michael@0 488 #ifndef XP_MACOSX
michael@0 489 if (gDownloadManager.activeDownloadCount == 0) {
michael@0 490 setTimeout(gCloseDownloadManager, 0);
michael@0 491 }
michael@0 492 #endif
michael@0 493 break;
michael@0 494 }
michael@0 495 }
michael@0 496 };
michael@0 497
michael@0 498 ////////////////////////////////////////////////////////////////////////////////
michael@0 499 //// View Context Menus
michael@0 500
michael@0 501 var gContextMenus = [
michael@0 502 // DOWNLOAD_DOWNLOADING
michael@0 503 [
michael@0 504 "menuitem_pause"
michael@0 505 , "menuitem_cancel"
michael@0 506 , "menuseparator"
michael@0 507 , "menuitem_show"
michael@0 508 , "menuseparator"
michael@0 509 , "menuitem_openReferrer"
michael@0 510 , "menuitem_copyLocation"
michael@0 511 , "menuseparator"
michael@0 512 , "menuitem_selectAll"
michael@0 513 ],
michael@0 514 // DOWNLOAD_FINISHED
michael@0 515 [
michael@0 516 "menuitem_open"
michael@0 517 , "menuitem_show"
michael@0 518 , "menuseparator"
michael@0 519 , "menuitem_openReferrer"
michael@0 520 , "menuitem_copyLocation"
michael@0 521 , "menuseparator"
michael@0 522 , "menuitem_selectAll"
michael@0 523 , "menuseparator"
michael@0 524 , "menuitem_removeFromList"
michael@0 525 ],
michael@0 526 // DOWNLOAD_FAILED
michael@0 527 [
michael@0 528 "menuitem_retry"
michael@0 529 , "menuseparator"
michael@0 530 , "menuitem_openReferrer"
michael@0 531 , "menuitem_copyLocation"
michael@0 532 , "menuseparator"
michael@0 533 , "menuitem_selectAll"
michael@0 534 , "menuseparator"
michael@0 535 , "menuitem_removeFromList"
michael@0 536 ],
michael@0 537 // DOWNLOAD_CANCELED
michael@0 538 [
michael@0 539 "menuitem_retry"
michael@0 540 , "menuseparator"
michael@0 541 , "menuitem_openReferrer"
michael@0 542 , "menuitem_copyLocation"
michael@0 543 , "menuseparator"
michael@0 544 , "menuitem_selectAll"
michael@0 545 , "menuseparator"
michael@0 546 , "menuitem_removeFromList"
michael@0 547 ],
michael@0 548 // DOWNLOAD_PAUSED
michael@0 549 [
michael@0 550 "menuitem_resume"
michael@0 551 , "menuitem_cancel"
michael@0 552 , "menuseparator"
michael@0 553 , "menuitem_show"
michael@0 554 , "menuseparator"
michael@0 555 , "menuitem_openReferrer"
michael@0 556 , "menuitem_copyLocation"
michael@0 557 , "menuseparator"
michael@0 558 , "menuitem_selectAll"
michael@0 559 ],
michael@0 560 // DOWNLOAD_QUEUED
michael@0 561 [
michael@0 562 "menuitem_cancel"
michael@0 563 , "menuseparator"
michael@0 564 , "menuitem_show"
michael@0 565 , "menuseparator"
michael@0 566 , "menuitem_openReferrer"
michael@0 567 , "menuitem_copyLocation"
michael@0 568 , "menuseparator"
michael@0 569 , "menuitem_selectAll"
michael@0 570 ],
michael@0 571 // DOWNLOAD_BLOCKED_PARENTAL
michael@0 572 [
michael@0 573 "menuitem_openReferrer"
michael@0 574 , "menuitem_copyLocation"
michael@0 575 , "menuseparator"
michael@0 576 , "menuitem_selectAll"
michael@0 577 , "menuseparator"
michael@0 578 , "menuitem_removeFromList"
michael@0 579 ],
michael@0 580 // DOWNLOAD_SCANNING
michael@0 581 [
michael@0 582 "menuitem_show"
michael@0 583 , "menuseparator"
michael@0 584 , "menuitem_openReferrer"
michael@0 585 , "menuitem_copyLocation"
michael@0 586 , "menuseparator"
michael@0 587 , "menuitem_selectAll"
michael@0 588 ],
michael@0 589 // DOWNLOAD_DIRTY
michael@0 590 [
michael@0 591 "menuitem_openReferrer"
michael@0 592 , "menuitem_copyLocation"
michael@0 593 , "menuseparator"
michael@0 594 , "menuitem_selectAll"
michael@0 595 , "menuseparator"
michael@0 596 , "menuitem_removeFromList"
michael@0 597 ],
michael@0 598 // DOWNLOAD_BLOCKED_POLICY
michael@0 599 [
michael@0 600 "menuitem_openReferrer"
michael@0 601 , "menuitem_copyLocation"
michael@0 602 , "menuseparator"
michael@0 603 , "menuitem_selectAll"
michael@0 604 , "menuseparator"
michael@0 605 , "menuitem_removeFromList"
michael@0 606 ]
michael@0 607 ];
michael@0 608
michael@0 609 function buildContextMenu(aEvent)
michael@0 610 {
michael@0 611 if (aEvent.target.id != "downloadContextMenu")
michael@0 612 return false;
michael@0 613
michael@0 614 var popup = document.getElementById("downloadContextMenu");
michael@0 615 while (popup.hasChildNodes())
michael@0 616 popup.removeChild(popup.firstChild);
michael@0 617
michael@0 618 if (gDownloadsView.selectedItem) {
michael@0 619 let dl = gDownloadsView.selectedItem;
michael@0 620 let idx = parseInt(dl.getAttribute("state"));
michael@0 621 if (idx < 0)
michael@0 622 idx = 0;
michael@0 623
michael@0 624 var menus = gContextMenus[idx];
michael@0 625 for (let i = 0; i < menus.length; ++i) {
michael@0 626 let menuitem = document.getElementById(menus[i]).cloneNode(true);
michael@0 627 let cmd = menuitem.getAttribute("cmd");
michael@0 628 if (cmd)
michael@0 629 menuitem.disabled = !gDownloadViewController.isCommandEnabled(cmd, dl);
michael@0 630
michael@0 631 popup.appendChild(menuitem);
michael@0 632 }
michael@0 633
michael@0 634 return true;
michael@0 635 }
michael@0 636
michael@0 637 return false;
michael@0 638 }
michael@0 639 ////////////////////////////////////////////////////////////////////////////////
michael@0 640 //// Drag and Drop
michael@0 641 var gDownloadDNDObserver =
michael@0 642 {
michael@0 643 onDragStart: function (aEvent)
michael@0 644 {
michael@0 645 if (!gDownloadsView.selectedItem)
michael@0 646 return;
michael@0 647 var dl = gDownloadsView.selectedItem;
michael@0 648 var f = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
michael@0 649 if (!f.exists())
michael@0 650 return;
michael@0 651
michael@0 652 var dt = aEvent.dataTransfer;
michael@0 653 dt.mozSetDataAt("application/x-moz-file", f, 0);
michael@0 654 var url = Services.io.newFileURI(f).spec;
michael@0 655 dt.setData("text/uri-list", url);
michael@0 656 dt.setData("text/plain", url);
michael@0 657 dt.effectAllowed = "copyMove";
michael@0 658 dt.addElement(dl);
michael@0 659 },
michael@0 660
michael@0 661 onDragOver: function (aEvent)
michael@0 662 {
michael@0 663 var types = aEvent.dataTransfer.types;
michael@0 664 if (types.contains("text/uri-list") ||
michael@0 665 types.contains("text/x-moz-url") ||
michael@0 666 types.contains("text/plain"))
michael@0 667 aEvent.preventDefault();
michael@0 668 },
michael@0 669
michael@0 670 onDrop: function(aEvent)
michael@0 671 {
michael@0 672 var dt = aEvent.dataTransfer;
michael@0 673 // If dragged item is from our source, do not try to
michael@0 674 // redownload already downloaded file.
michael@0 675 if (dt.mozGetDataAt("application/x-moz-file", 0))
michael@0 676 return;
michael@0 677
michael@0 678 var url = dt.getData("URL");
michael@0 679 var name;
michael@0 680 if (!url) {
michael@0 681 url = dt.getData("text/x-moz-url") || dt.getData("text/plain");
michael@0 682 [url, name] = url.split("\n");
michael@0 683 }
michael@0 684 if (url) {
michael@0 685 let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
michael@0 686 saveURL(url, name ? name : url, null, true, true, null, sourceDoc);
michael@0 687 }
michael@0 688 }
michael@0 689 }
michael@0 690
michael@0 691 function pasteHandler() {
michael@0 692 let trans = Cc["@mozilla.org/widget/transferable;1"].
michael@0 693 createInstance(Ci.nsITransferable);
michael@0 694 trans.init(null);
michael@0 695 let flavors = ["text/x-moz-url", "text/unicode"];
michael@0 696 flavors.forEach(trans.addDataFlavor);
michael@0 697
michael@0 698 Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
michael@0 699
michael@0 700 // Getting the data or creating the nsIURI might fail
michael@0 701 try {
michael@0 702 let data = {};
michael@0 703 trans.getAnyTransferData({}, data, {});
michael@0 704 let [url, name] = data.value.QueryInterface(Ci.nsISupportsString).data.split("\n");
michael@0 705
michael@0 706 if (!url)
michael@0 707 return;
michael@0 708
michael@0 709 let uri = Services.io.newURI(url, null, null);
michael@0 710
michael@0 711 saveURL(uri.spec, name || uri.spec, null, true, true, null, document);
michael@0 712 } catch (ex) {}
michael@0 713 }
michael@0 714
michael@0 715 ////////////////////////////////////////////////////////////////////////////////
michael@0 716 //// Command Updating and Command Handlers
michael@0 717
michael@0 718 var gDownloadViewController = {
michael@0 719 isCommandEnabled: function(aCommand, aItem)
michael@0 720 {
michael@0 721 let dl = aItem;
michael@0 722 let download = null; // used for getting an nsIDownload object
michael@0 723
michael@0 724 switch (aCommand) {
michael@0 725 case "cmd_cancel":
michael@0 726 return dl.inProgress;
michael@0 727 case "cmd_open": {
michael@0 728 let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
michael@0 729 return dl.openable && file.exists();
michael@0 730 }
michael@0 731 case "cmd_show": {
michael@0 732 let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
michael@0 733 return file.exists();
michael@0 734 }
michael@0 735 case "cmd_pause":
michael@0 736 download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
michael@0 737 return dl.inProgress && !dl.paused && download.resumable;
michael@0 738 case "cmd_pauseResume":
michael@0 739 download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
michael@0 740 return (dl.inProgress || dl.paused) && download.resumable;
michael@0 741 case "cmd_resume":
michael@0 742 download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
michael@0 743 return dl.paused && download.resumable;
michael@0 744 case "cmd_openReferrer":
michael@0 745 return dl.hasAttribute("referrer");
michael@0 746 case "cmd_removeFromList":
michael@0 747 case "cmd_retry":
michael@0 748 return dl.removable;
michael@0 749 case "cmd_copyLocation":
michael@0 750 return true;
michael@0 751 }
michael@0 752 return false;
michael@0 753 },
michael@0 754
michael@0 755 doCommand: function(aCommand, aItem)
michael@0 756 {
michael@0 757 if (this.isCommandEnabled(aCommand, aItem))
michael@0 758 this.commands[aCommand](aItem);
michael@0 759 },
michael@0 760
michael@0 761 commands: {
michael@0 762 cmd_cancel: function(aSelectedItem) {
michael@0 763 cancelDownload(aSelectedItem);
michael@0 764 },
michael@0 765 cmd_open: function(aSelectedItem) {
michael@0 766 openDownload(aSelectedItem);
michael@0 767 },
michael@0 768 cmd_openReferrer: function(aSelectedItem) {
michael@0 769 openReferrer(aSelectedItem);
michael@0 770 },
michael@0 771 cmd_pause: function(aSelectedItem) {
michael@0 772 pauseDownload(aSelectedItem);
michael@0 773 },
michael@0 774 cmd_pauseResume: function(aSelectedItem) {
michael@0 775 if (aSelectedItem.paused)
michael@0 776 this.cmd_resume(aSelectedItem);
michael@0 777 else
michael@0 778 this.cmd_pause(aSelectedItem);
michael@0 779 },
michael@0 780 cmd_removeFromList: function(aSelectedItem) {
michael@0 781 removeDownload(aSelectedItem);
michael@0 782 },
michael@0 783 cmd_resume: function(aSelectedItem) {
michael@0 784 resumeDownload(aSelectedItem);
michael@0 785 },
michael@0 786 cmd_retry: function(aSelectedItem) {
michael@0 787 retryDownload(aSelectedItem);
michael@0 788 },
michael@0 789 cmd_show: function(aSelectedItem) {
michael@0 790 showDownload(aSelectedItem);
michael@0 791 },
michael@0 792 cmd_copyLocation: function(aSelectedItem) {
michael@0 793 copySourceLocation(aSelectedItem);
michael@0 794 },
michael@0 795 }
michael@0 796 };
michael@0 797
michael@0 798 /**
michael@0 799 * Helper function to do commands.
michael@0 800 *
michael@0 801 * @param aCmd
michael@0 802 * The command to be performed.
michael@0 803 * @param aItem
michael@0 804 * The richlistitem that represents the download that will have the
michael@0 805 * command performed on it. If this is null, the command is performed on
michael@0 806 * all downloads. If the item passed in is not a richlistitem that
michael@0 807 * represents a download, it will walk up the parent nodes until it finds
michael@0 808 * a DOM node that is.
michael@0 809 */
michael@0 810 function performCommand(aCmd, aItem)
michael@0 811 {
michael@0 812 let elm = aItem;
michael@0 813 if (!elm) {
michael@0 814 // If we don't have a desired download item, do the command for all
michael@0 815 // selected items. Initialize the callback to null so commands know to add
michael@0 816 // a callback if they want. We will call the callback with empty arguments
michael@0 817 // after performing the command on each selected download item.
michael@0 818 gPerformAllCallback = null;
michael@0 819
michael@0 820 // Convert the nodelist into an array to keep a copy of the download items
michael@0 821 let items = [];
michael@0 822 for (let i = gDownloadsView.selectedItems.length; --i >= 0; )
michael@0 823 items.unshift(gDownloadsView.selectedItems[i]);
michael@0 824
michael@0 825 // Do the command for each download item
michael@0 826 for each (let item in items)
michael@0 827 performCommand(aCmd, item);
michael@0 828
michael@0 829 // Call the callback with no arguments and reset because we're done
michael@0 830 if (typeof gPerformAllCallback == "function")
michael@0 831 gPerformAllCallback();
michael@0 832 gPerformAllCallback = undefined;
michael@0 833
michael@0 834 return;
michael@0 835 } else {
michael@0 836 while (elm.nodeName != "richlistitem" ||
michael@0 837 elm.getAttribute("type") != "download")
michael@0 838 elm = elm.parentNode;
michael@0 839 }
michael@0 840
michael@0 841 gDownloadViewController.doCommand(aCmd, elm);
michael@0 842 }
michael@0 843
michael@0 844 function setSearchboxFocus()
michael@0 845 {
michael@0 846 gSearchBox.focus();
michael@0 847 gSearchBox.select();
michael@0 848 }
michael@0 849
michael@0 850 function openExternal(aFile)
michael@0 851 {
michael@0 852 var uri = Cc["@mozilla.org/network/io-service;1"].
michael@0 853 getService(Ci.nsIIOService).newFileURI(aFile);
michael@0 854
michael@0 855 var protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
michael@0 856 getService(Ci.nsIExternalProtocolService);
michael@0 857 protocolSvc.loadUrl(uri);
michael@0 858
michael@0 859 return;
michael@0 860 }
michael@0 861
michael@0 862 ////////////////////////////////////////////////////////////////////////////////
michael@0 863 //// Utility Functions
michael@0 864
michael@0 865 /**
michael@0 866 * Create a download richlistitem with the provided attributes. Some attributes
michael@0 867 * are *required* while optional ones will only be set on the item if provided.
michael@0 868 *
michael@0 869 * @param aAttrs
michael@0 870 * An object that must have the following properties: dlid, file,
michael@0 871 * target, uri, state, progress, startTime, endTime, currBytes,
michael@0 872 * maxBytes; optional properties: referrer
michael@0 873 * @return An initialized download richlistitem
michael@0 874 */
michael@0 875 function createDownloadItem(aAttrs)
michael@0 876 {
michael@0 877 let dl = document.createElement("richlistitem");
michael@0 878
michael@0 879 // Copy the attributes from the argument into the item
michael@0 880 for (let attr in aAttrs)
michael@0 881 dl.setAttribute(attr, aAttrs[attr]);
michael@0 882
michael@0 883 // Initialize other attributes
michael@0 884 dl.setAttribute("type", "download");
michael@0 885 dl.setAttribute("id", "dl" + aAttrs.dlid);
michael@0 886 dl.setAttribute("image", "moz-icon://" + aAttrs.file + "?size=32");
michael@0 887 dl.setAttribute("lastSeconds", Infinity);
michael@0 888
michael@0 889 // Initialize more complex attributes
michael@0 890 updateTime(dl);
michael@0 891 updateStatus(dl);
michael@0 892
michael@0 893 try {
michael@0 894 let file = getLocalFileFromNativePathOrUrl(aAttrs.file);
michael@0 895 dl.setAttribute("path", file.nativePath || file.path);
michael@0 896 return dl;
michael@0 897 } catch (e) {
michael@0 898 // aFile might not be a file: url or a valid native path
michael@0 899 // see bug #392386 for details
michael@0 900 }
michael@0 901 return null;
michael@0 902 }
michael@0 903
michael@0 904 /**
michael@0 905 * Updates the disabled state of the buttons of a downlaod.
michael@0 906 *
michael@0 907 * @param aItem
michael@0 908 * The richlistitem representing the download.
michael@0 909 */
michael@0 910 function updateButtons(aItem)
michael@0 911 {
michael@0 912 let buttons = aItem.buttons;
michael@0 913
michael@0 914 for (let i = 0; i < buttons.length; ++i) {
michael@0 915 let cmd = buttons[i].getAttribute("cmd");
michael@0 916 let enabled = gDownloadViewController.isCommandEnabled(cmd, aItem);
michael@0 917 buttons[i].disabled = !enabled;
michael@0 918
michael@0 919 if ("cmd_pause" == cmd && !enabled) {
michael@0 920 // We need to add the tooltip indicating that the download cannot be
michael@0 921 // paused now.
michael@0 922 buttons[i].setAttribute("tooltiptext", gStr.cannotPause);
michael@0 923 }
michael@0 924 }
michael@0 925 }
michael@0 926
michael@0 927 /**
michael@0 928 * Updates the status for a download item depending on its state
michael@0 929 *
michael@0 930 * @param aItem
michael@0 931 * The richlistitem that has various download attributes.
michael@0 932 * @param aDownload
michael@0 933 * The nsDownload from the backend. This is an optional parameter, but
michael@0 934 * is useful for certain states such as DOWNLOADING.
michael@0 935 */
michael@0 936 function updateStatus(aItem, aDownload) {
michael@0 937 let status = "";
michael@0 938 let statusTip = "";
michael@0 939
michael@0 940 let state = Number(aItem.getAttribute("state"));
michael@0 941 switch (state) {
michael@0 942 case nsIDM.DOWNLOAD_PAUSED:
michael@0 943 {
michael@0 944 let currBytes = Number(aItem.getAttribute("currBytes"));
michael@0 945 let maxBytes = Number(aItem.getAttribute("maxBytes"));
michael@0 946
michael@0 947 let transfer = DownloadUtils.getTransferTotal(currBytes, maxBytes);
michael@0 948 status = replaceInsert(gStr.paused, 1, transfer);
michael@0 949
michael@0 950 break;
michael@0 951 }
michael@0 952 case nsIDM.DOWNLOAD_DOWNLOADING:
michael@0 953 {
michael@0 954 let currBytes = Number(aItem.getAttribute("currBytes"));
michael@0 955 let maxBytes = Number(aItem.getAttribute("maxBytes"));
michael@0 956 // If we don't have an active download, assume 0 bytes/sec
michael@0 957 let speed = aDownload ? aDownload.speed : 0;
michael@0 958 let lastSec = Number(aItem.getAttribute("lastSeconds"));
michael@0 959
michael@0 960 let newLast;
michael@0 961 [status, newLast] =
michael@0 962 DownloadUtils.getDownloadStatus(currBytes, maxBytes, speed, lastSec);
michael@0 963
michael@0 964 // Update lastSeconds to be the new value
michael@0 965 aItem.setAttribute("lastSeconds", newLast);
michael@0 966
michael@0 967 break;
michael@0 968 }
michael@0 969 case nsIDM.DOWNLOAD_FINISHED:
michael@0 970 case nsIDM.DOWNLOAD_FAILED:
michael@0 971 case nsIDM.DOWNLOAD_CANCELED:
michael@0 972 case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
michael@0 973 case nsIDM.DOWNLOAD_BLOCKED_POLICY:
michael@0 974 case nsIDM.DOWNLOAD_DIRTY:
michael@0 975 {
michael@0 976 let (stateSize = {}) {
michael@0 977 stateSize[nsIDM.DOWNLOAD_FINISHED] = function() {
michael@0 978 // Display the file size, but show "Unknown" for negative sizes
michael@0 979 let fileSize = Number(aItem.getAttribute("maxBytes"));
michael@0 980 let sizeText = gStr.doneSizeUnknown;
michael@0 981 if (fileSize >= 0) {
michael@0 982 let [size, unit] = DownloadUtils.convertByteUnits(fileSize);
michael@0 983 sizeText = replaceInsert(gStr.doneSize, 1, size);
michael@0 984 sizeText = replaceInsert(sizeText, 2, unit);
michael@0 985 }
michael@0 986 return sizeText;
michael@0 987 };
michael@0 988 stateSize[nsIDM.DOWNLOAD_FAILED] = function() gStr.stateFailed;
michael@0 989 stateSize[nsIDM.DOWNLOAD_CANCELED] = function() gStr.stateCanceled;
michael@0 990 stateSize[nsIDM.DOWNLOAD_BLOCKED_PARENTAL] = function() gStr.stateBlockedParentalControls;
michael@0 991 stateSize[nsIDM.DOWNLOAD_BLOCKED_POLICY] = function() gStr.stateBlockedPolicy;
michael@0 992 stateSize[nsIDM.DOWNLOAD_DIRTY] = function() gStr.stateDirty;
michael@0 993
michael@0 994 // Insert 1 is the download size or download state
michael@0 995 status = replaceInsert(gStr.doneStatus, 1, stateSize[state]());
michael@0 996 }
michael@0 997
michael@0 998 let [displayHost, fullHost] =
michael@0 999 DownloadUtils.getURIHost(getReferrerOrSource(aItem));
michael@0 1000 // Insert 2 is the eTLD + 1 or other variations of the host
michael@0 1001 status = replaceInsert(status, 2, displayHost);
michael@0 1002 // Set the tooltip to be the full host
michael@0 1003 statusTip = fullHost;
michael@0 1004
michael@0 1005 break;
michael@0 1006 }
michael@0 1007 }
michael@0 1008
michael@0 1009 aItem.setAttribute("status", status);
michael@0 1010 aItem.setAttribute("statusTip", statusTip != "" ? statusTip : status);
michael@0 1011 }
michael@0 1012
michael@0 1013 /**
michael@0 1014 * Updates the time that gets shown for completed download items
michael@0 1015 *
michael@0 1016 * @param aItem
michael@0 1017 * The richlistitem representing a download in the UI
michael@0 1018 */
michael@0 1019 function updateTime(aItem)
michael@0 1020 {
michael@0 1021 // Don't bother updating for things that aren't finished
michael@0 1022 if (aItem.inProgress)
michael@0 1023 return;
michael@0 1024
michael@0 1025 let end = new Date(parseInt(aItem.getAttribute("endTime")));
michael@0 1026 let [dateCompact, dateComplete] = DownloadUtils.getReadableDates(end);
michael@0 1027 aItem.setAttribute("dateTime", dateCompact);
michael@0 1028 aItem.setAttribute("dateTimeTip", dateComplete);
michael@0 1029 }
michael@0 1030
michael@0 1031 /**
michael@0 1032 * Helper function to replace a placeholder string with a real string
michael@0 1033 *
michael@0 1034 * @param aText
michael@0 1035 * Source text containing placeholder (e.g., #1)
michael@0 1036 * @param aIndex
michael@0 1037 * Index number of placeholder to replace
michael@0 1038 * @param aValue
michael@0 1039 * New string to put in place of placeholder
michael@0 1040 * @return The string with placeholder replaced with the new string
michael@0 1041 */
michael@0 1042 function replaceInsert(aText, aIndex, aValue)
michael@0 1043 {
michael@0 1044 return aText.replace("#" + aIndex, aValue);
michael@0 1045 }
michael@0 1046
michael@0 1047 /**
michael@0 1048 * Perform the default action for the currently selected download item
michael@0 1049 */
michael@0 1050 function doDefaultForSelected()
michael@0 1051 {
michael@0 1052 // Make sure we have something selected
michael@0 1053 let item = gDownloadsView.selectedItem;
michael@0 1054 if (!item)
michael@0 1055 return;
michael@0 1056
michael@0 1057 // Get the default action (first item in the menu)
michael@0 1058 let state = Number(item.getAttribute("state"));
michael@0 1059 let menuitem = document.getElementById(gContextMenus[state][0]);
michael@0 1060
michael@0 1061 // Try to do the action if the command is enabled
michael@0 1062 gDownloadViewController.doCommand(menuitem.getAttribute("cmd"), item);
michael@0 1063 }
michael@0 1064
michael@0 1065 function removeFromView(aDownload)
michael@0 1066 {
michael@0 1067 // Make sure we have an item to remove
michael@0 1068 if (!aDownload) return;
michael@0 1069
michael@0 1070 let index = gDownloadsView.selectedIndex;
michael@0 1071 gDownloadsView.removeChild(aDownload);
michael@0 1072 gDownloadsView.selectedIndex = Math.min(index, gDownloadsView.itemCount - 1);
michael@0 1073
michael@0 1074 // We might have removed the last item, so update the clear list button
michael@0 1075 updateClearListButton();
michael@0 1076 }
michael@0 1077
michael@0 1078 function getReferrerOrSource(aDownload)
michael@0 1079 {
michael@0 1080 // Give the referrer if we have it set
michael@0 1081 if (aDownload.hasAttribute("referrer"))
michael@0 1082 return aDownload.getAttribute("referrer");
michael@0 1083
michael@0 1084 // Otherwise, provide the source
michael@0 1085 return aDownload.getAttribute("uri");
michael@0 1086 }
michael@0 1087
michael@0 1088 /**
michael@0 1089 * Initiate building the download list to have the active downloads followed by
michael@0 1090 * completed ones filtered by the search term if necessary.
michael@0 1091 *
michael@0 1092 * @param aForceBuild
michael@0 1093 * Force the list to be built even if the search terms don't change
michael@0 1094 */
michael@0 1095 function buildDownloadList(aForceBuild)
michael@0 1096 {
michael@0 1097 // Stringify the previous search
michael@0 1098 let prevSearch = gSearchTerms.join(" ");
michael@0 1099
michael@0 1100 // Array of space-separated lower-case search terms
michael@0 1101 gSearchTerms = gSearchBox.value.replace(/^\s+|\s+$/g, "").
michael@0 1102 toLowerCase().split(/\s+/);
michael@0 1103
michael@0 1104 // Unless forced, don't rebuild the download list if the search didn't change
michael@0 1105 if (!aForceBuild && gSearchTerms.join(" ") == prevSearch)
michael@0 1106 return;
michael@0 1107
michael@0 1108 // Clear out values before using them
michael@0 1109 clearTimeout(gBuilder);
michael@0 1110 gStmt.reset();
michael@0 1111
michael@0 1112 // Clear the list before adding items by replacing with a shallow copy
michael@0 1113 let (empty = gDownloadsView.cloneNode(false)) {
michael@0 1114 gDownloadsView.parentNode.replaceChild(empty, gDownloadsView);
michael@0 1115 gDownloadsView = empty;
michael@0 1116 }
michael@0 1117
michael@0 1118 try {
michael@0 1119 gStmt.bindByIndex(0, nsIDM.DOWNLOAD_NOTSTARTED);
michael@0 1120 gStmt.bindByIndex(1, nsIDM.DOWNLOAD_DOWNLOADING);
michael@0 1121 gStmt.bindByIndex(2, nsIDM.DOWNLOAD_PAUSED);
michael@0 1122 gStmt.bindByIndex(3, nsIDM.DOWNLOAD_QUEUED);
michael@0 1123 gStmt.bindByIndex(4, nsIDM.DOWNLOAD_SCANNING);
michael@0 1124 } catch (e) {
michael@0 1125 // Something must have gone wrong when binding, so clear and quit
michael@0 1126 gStmt.reset();
michael@0 1127 return;
michael@0 1128 }
michael@0 1129
michael@0 1130 // Take a quick break before we actually start building the list
michael@0 1131 gBuilder = setTimeout(function() {
michael@0 1132 // Start building the list
michael@0 1133 stepListBuilder(1);
michael@0 1134
michael@0 1135 // We just tried to add a single item, so we probably need to enable
michael@0 1136 updateClearListButton();
michael@0 1137 }, 0);
michael@0 1138 }
michael@0 1139
michael@0 1140 /**
michael@0 1141 * Incrementally build the download list by adding at most the requested number
michael@0 1142 * of items if there are items to add. After doing that, it will schedule
michael@0 1143 * another chunk of items specified by gListBuildDelay and gListBuildChunk.
michael@0 1144 *
michael@0 1145 * @param aNumItems
michael@0 1146 * Number of items to add to the list before taking a break
michael@0 1147 */
michael@0 1148 function stepListBuilder(aNumItems) {
michael@0 1149 try {
michael@0 1150 // If we're done adding all items, we can quit
michael@0 1151 if (!gStmt.executeStep()) {
michael@0 1152 // Send a notification that we finished, but wait for clear list to update
michael@0 1153 updateClearListButton();
michael@0 1154 setTimeout(function() Cc["@mozilla.org/observer-service;1"].
michael@0 1155 getService(Ci.nsIObserverService).
michael@0 1156 notifyObservers(window, "download-manager-ui-done", null), 0);
michael@0 1157
michael@0 1158 return;
michael@0 1159 }
michael@0 1160
michael@0 1161 // Try to get the attribute values from the statement
michael@0 1162 let attrs = {
michael@0 1163 dlid: gStmt.getInt64(0),
michael@0 1164 file: gStmt.getString(1),
michael@0 1165 target: gStmt.getString(2),
michael@0 1166 uri: gStmt.getString(3),
michael@0 1167 state: gStmt.getInt32(4),
michael@0 1168 startTime: Math.round(gStmt.getInt64(5) / 1000),
michael@0 1169 endTime: Math.round(gStmt.getInt64(6) / 1000),
michael@0 1170 currBytes: gStmt.getInt64(8),
michael@0 1171 maxBytes: gStmt.getInt64(9)
michael@0 1172 };
michael@0 1173
michael@0 1174 // Only add the referrer if it's not null
michael@0 1175 let (referrer = gStmt.getString(7)) {
michael@0 1176 if (referrer)
michael@0 1177 attrs.referrer = referrer;
michael@0 1178 }
michael@0 1179
michael@0 1180 // If the download is active, grab the real progress, otherwise default 100
michael@0 1181 let isActive = gStmt.getInt32(10);
michael@0 1182 attrs.progress = isActive ? gDownloadManager.getDownload(attrs.dlid).
michael@0 1183 percentComplete : 100;
michael@0 1184
michael@0 1185 // Make the item and add it to the end if it's active or matches the search
michael@0 1186 let item = createDownloadItem(attrs);
michael@0 1187 if (item && (isActive || downloadMatchesSearch(item))) {
michael@0 1188 // Add item to the end
michael@0 1189 gDownloadsView.appendChild(item);
michael@0 1190
michael@0 1191 // Because of the joys of XBL, we can't update the buttons until the
michael@0 1192 // download object is in the document.
michael@0 1193 updateButtons(item);
michael@0 1194 } else {
michael@0 1195 // We didn't add an item, so bump up the number of items to process, but
michael@0 1196 // not a whole number so that we eventually do pause for a chunk break
michael@0 1197 aNumItems += .9;
michael@0 1198 }
michael@0 1199 } catch (e) {
michael@0 1200 // Something went wrong when stepping or getting values, so clear and quit
michael@0 1201 gStmt.reset();
michael@0 1202 return;
michael@0 1203 }
michael@0 1204
michael@0 1205 // Add another item to the list if we should; otherwise, let the UI update
michael@0 1206 // and continue later
michael@0 1207 if (aNumItems > 1) {
michael@0 1208 stepListBuilder(aNumItems - 1);
michael@0 1209 } else {
michael@0 1210 // Use a shorter delay for earlier downloads to display them faster
michael@0 1211 let delay = Math.min(gDownloadsView.itemCount * 10, gListBuildDelay);
michael@0 1212 gBuilder = setTimeout(stepListBuilder, delay, gListBuildChunk);
michael@0 1213 }
michael@0 1214 }
michael@0 1215
michael@0 1216 /**
michael@0 1217 * Add a download to the front of the download list
michael@0 1218 *
michael@0 1219 * @param aDownload
michael@0 1220 * The nsIDownload to make into a richlistitem
michael@0 1221 */
michael@0 1222 function prependList(aDownload)
michael@0 1223 {
michael@0 1224 let attrs = {
michael@0 1225 dlid: aDownload.id,
michael@0 1226 file: aDownload.target.spec,
michael@0 1227 target: aDownload.displayName,
michael@0 1228 uri: aDownload.source.spec,
michael@0 1229 state: aDownload.state,
michael@0 1230 progress: aDownload.percentComplete,
michael@0 1231 startTime: Math.round(aDownload.startTime / 1000),
michael@0 1232 endTime: Date.now(),
michael@0 1233 currBytes: aDownload.amountTransferred,
michael@0 1234 maxBytes: aDownload.size
michael@0 1235 };
michael@0 1236
michael@0 1237 // Make the item and add it to the beginning
michael@0 1238 let item = createDownloadItem(attrs);
michael@0 1239 if (item) {
michael@0 1240 // Add item to the beginning
michael@0 1241 gDownloadsView.insertBefore(item, gDownloadsView.firstChild);
michael@0 1242
michael@0 1243 // Because of the joys of XBL, we can't update the buttons until the
michael@0 1244 // download object is in the document.
michael@0 1245 updateButtons(item);
michael@0 1246
michael@0 1247 // We might have added an item to an empty list, so update button
michael@0 1248 updateClearListButton();
michael@0 1249 }
michael@0 1250 }
michael@0 1251
michael@0 1252 /**
michael@0 1253 * Check if the download matches the current search term based on the texts
michael@0 1254 * shown to the user. All search terms are checked to see if each matches any
michael@0 1255 * of the displayed texts.
michael@0 1256 *
michael@0 1257 * @param aItem
michael@0 1258 * Download richlistitem to check if it matches the current search
michael@0 1259 * @return Boolean true if it matches the search; false otherwise
michael@0 1260 */
michael@0 1261 function downloadMatchesSearch(aItem)
michael@0 1262 {
michael@0 1263 // Search through the download attributes that are shown to the user and
michael@0 1264 // make it into one big string for easy combined searching
michael@0 1265 let combinedSearch = "";
michael@0 1266 for each (let attr in gSearchAttributes)
michael@0 1267 combinedSearch += aItem.getAttribute(attr).toLowerCase() + " ";
michael@0 1268
michael@0 1269 // Make sure each of the terms are found
michael@0 1270 for each (let term in gSearchTerms)
michael@0 1271 if (combinedSearch.indexOf(term) == -1)
michael@0 1272 return false;
michael@0 1273
michael@0 1274 return true;
michael@0 1275 }
michael@0 1276
michael@0 1277 // we should be using real URLs all the time, but until
michael@0 1278 // bug 239948 is fully fixed, this will do...
michael@0 1279 //
michael@0 1280 // note, this will thrown an exception if the native path
michael@0 1281 // is not valid (for example a native Windows path on a Mac)
michael@0 1282 // see bug #392386 for details
michael@0 1283 function getLocalFileFromNativePathOrUrl(aPathOrUrl)
michael@0 1284 {
michael@0 1285 if (aPathOrUrl.substring(0,7) == "file://") {
michael@0 1286 // if this is a URL, get the file from that
michael@0 1287 let ioSvc = Cc["@mozilla.org/network/io-service;1"].
michael@0 1288 getService(Ci.nsIIOService);
michael@0 1289
michael@0 1290 // XXX it's possible that using a null char-set here is bad
michael@0 1291 const fileUrl = ioSvc.newURI(aPathOrUrl, null, null).
michael@0 1292 QueryInterface(Ci.nsIFileURL);
michael@0 1293 return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
michael@0 1294 } else {
michael@0 1295 // if it's a pathname, create the nsILocalFile directly
michael@0 1296 var f = new nsLocalFile(aPathOrUrl);
michael@0 1297
michael@0 1298 return f;
michael@0 1299 }
michael@0 1300 }
michael@0 1301
michael@0 1302 /**
michael@0 1303 * Update the disabled state of the clear list button based on whether or not
michael@0 1304 * there are items in the list that can potentially be removed.
michael@0 1305 */
michael@0 1306 function updateClearListButton()
michael@0 1307 {
michael@0 1308 let button = document.getElementById("clearListButton");
michael@0 1309 // The button is enabled if we have items in the list and we can clean up
michael@0 1310 button.disabled = !(gDownloadsView.itemCount && gDownloadManager.canCleanUp);
michael@0 1311 }
michael@0 1312
michael@0 1313 function getDownload(aID)
michael@0 1314 {
michael@0 1315 return document.getElementById("dl" + aID);
michael@0 1316 }
michael@0 1317
michael@0 1318 /**
michael@0 1319 * Initialize the statement which is used to retrieve the list of downloads.
michael@0 1320 */
michael@0 1321 function initStatement()
michael@0 1322 {
michael@0 1323 if (gStmt)
michael@0 1324 gStmt.finalize();
michael@0 1325
michael@0 1326 gStmt = gDownloadManager.DBConnection.createStatement(
michael@0 1327 "SELECT id, target, name, source, state, startTime, endTime, referrer, " +
michael@0 1328 "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
michael@0 1329 "FROM moz_downloads " +
michael@0 1330 "ORDER BY isActive DESC, endTime DESC, startTime DESC");
michael@0 1331 }

mercurial