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