|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils; |
|
6 |
|
7 Cu.import("resource://gre/modules/Services.jsm"); |
|
8 Cu.import("resource://gre/modules/DownloadUtils.jsm"); |
|
9 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
10 Cu.import("resource://gre/modules/PluralForm.jsm"); |
|
11 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); |
|
12 |
|
13 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); |
|
14 |
|
15 let gStrings = Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties"); |
|
16 |
|
17 let downloadTemplate = |
|
18 "<li downloadGUID='{guid}' class='list-item' role='button' state='{state}' contextmenu='downloadmenu'>" + |
|
19 "<img class='icon' src='{icon}'/>" + |
|
20 "<div class='details'>" + |
|
21 "<div class='row'>" + |
|
22 // This is a hack so that we can crop this label in its center |
|
23 "<xul:label class='title' crop='center' value='{target}'/>" + |
|
24 "<div class='date'>{date}</div>" + |
|
25 "</div>" + |
|
26 "<div class='size'>{size}</div>" + |
|
27 "<div class='domain'>{domain}</div>" + |
|
28 "<div class='displayState'>{displayState}</div>" + |
|
29 "</div>" + |
|
30 "</li>"; |
|
31 |
|
32 XPCOMUtils.defineLazyGetter(window, "gChromeWin", function () |
|
33 window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
34 .getInterface(Ci.nsIWebNavigation) |
|
35 .QueryInterface(Ci.nsIDocShellTreeItem) |
|
36 .rootTreeItem |
|
37 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
38 .getInterface(Ci.nsIDOMWindow) |
|
39 .QueryInterface(Ci.nsIDOMChromeWindow)); |
|
40 |
|
41 |
|
42 var ContextMenus = { |
|
43 target: null, |
|
44 |
|
45 init: function() { |
|
46 document.addEventListener("contextmenu", this, false); |
|
47 document.getElementById("contextmenu-open").addEventListener("click", this.open.bind(this), false); |
|
48 document.getElementById("contextmenu-retry").addEventListener("click", this.retry.bind(this), false); |
|
49 document.getElementById("contextmenu-remove").addEventListener("click", this.remove.bind(this), false); |
|
50 document.getElementById("contextmenu-pause").addEventListener("click", this.pause.bind(this), false); |
|
51 document.getElementById("contextmenu-resume").addEventListener("click", this.resume.bind(this), false); |
|
52 document.getElementById("contextmenu-cancel").addEventListener("click", this.cancel.bind(this), false); |
|
53 document.getElementById("contextmenu-removeall").addEventListener("click", this.removeAll.bind(this), false); |
|
54 this.items = [ |
|
55 { name: "open", states: [Downloads._dlmgr.DOWNLOAD_FINISHED] }, |
|
56 { name: "retry", states: [Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] }, |
|
57 { name: "remove", states: [Downloads._dlmgr.DOWNLOAD_FINISHED,Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] }, |
|
58 { name: "removeall", states: [Downloads._dlmgr.DOWNLOAD_FINISHED,Downloads._dlmgr.DOWNLOAD_FAILED, Downloads._dlmgr.DOWNLOAD_CANCELED] }, |
|
59 { name: "pause", states: [Downloads._dlmgr.DOWNLOAD_DOWNLOADING] }, |
|
60 { name: "resume", states: [Downloads._dlmgr.DOWNLOAD_PAUSED] }, |
|
61 { name: "cancel", states: [Downloads._dlmgr.DOWNLOAD_DOWNLOADING, Downloads._dlmgr.DOWNLOAD_NOTSTARTED, Downloads._dlmgr.DOWNLOAD_QUEUED, Downloads._dlmgr.DOWNLOAD_PAUSED] }, |
|
62 ]; |
|
63 }, |
|
64 |
|
65 handleEvent: function(event) { |
|
66 // store the target of context menu events so that we know which app to act on |
|
67 this.target = event.target; |
|
68 while (!this.target.hasAttribute("contextmenu")) { |
|
69 this.target = this.target.parentNode; |
|
70 } |
|
71 if (!this.target) |
|
72 return; |
|
73 |
|
74 let state = parseInt(this.target.getAttribute("state")); |
|
75 for (let i = 0; i < this.items.length; i++) { |
|
76 var item = this.items[i]; |
|
77 let enabled = (item.states.indexOf(state) > -1); |
|
78 if (enabled) |
|
79 document.getElementById("contextmenu-" + item.name).removeAttribute("hidden"); |
|
80 else |
|
81 document.getElementById("contextmenu-" + item.name).setAttribute("hidden", "true"); |
|
82 } |
|
83 }, |
|
84 |
|
85 // Open shown only for downloads that completed successfully |
|
86 open: function(event) { |
|
87 Downloads.openDownload(this.target); |
|
88 this.target = null; |
|
89 }, |
|
90 |
|
91 // Retry shown when its failed, canceled, blocked(covered in failed, see _getState()) |
|
92 retry: function (event) { |
|
93 Downloads.retryDownload(this.target); |
|
94 this.target = null; |
|
95 }, |
|
96 |
|
97 // Remove shown when its canceled, finished, failed(failed includes blocked and dirty, see _getState()) |
|
98 remove: function (event) { |
|
99 Downloads.removeDownload(this.target); |
|
100 this.target = null; |
|
101 }, |
|
102 |
|
103 // Pause shown when item is currently downloading |
|
104 pause: function (event) { |
|
105 Downloads.pauseDownload(this.target); |
|
106 this.target = null; |
|
107 }, |
|
108 |
|
109 // Resume shown for paused items only |
|
110 resume: function (event) { |
|
111 Downloads.resumeDownload(this.target); |
|
112 this.target = null; |
|
113 }, |
|
114 |
|
115 // Cancel shown when its downloading, notstarted, queued or paused |
|
116 cancel: function (event) { |
|
117 Downloads.cancelDownload(this.target); |
|
118 this.target = null; |
|
119 }, |
|
120 |
|
121 removeAll: function(event) { |
|
122 Downloads.removeAll(); |
|
123 this.target = null; |
|
124 } |
|
125 } |
|
126 |
|
127 |
|
128 let Downloads = { |
|
129 init: function dl_init() { |
|
130 function onClick(evt) { |
|
131 let target = evt.target; |
|
132 while (target.nodeName != "li") { |
|
133 target = target.parentNode; |
|
134 if (!target) |
|
135 return; |
|
136 } |
|
137 |
|
138 Downloads.openDownload(target); |
|
139 } |
|
140 |
|
141 this._normalList = document.getElementById("normal-downloads-list"); |
|
142 this._privateList = document.getElementById("private-downloads-list"); |
|
143 |
|
144 this._normalList.addEventListener("click", onClick, false); |
|
145 this._privateList.addEventListener("click", onClick, false); |
|
146 |
|
147 this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); |
|
148 this._dlmgr.addPrivacyAwareListener(this); |
|
149 |
|
150 Services.obs.addObserver(this, "last-pb-context-exited", false); |
|
151 Services.obs.addObserver(this, "download-manager-remove-download-guid", false); |
|
152 |
|
153 // If we have private downloads, show them all immediately. If we were to |
|
154 // add them asynchronously, there's a small chance we could get a |
|
155 // "last-pb-context-exited" notification before downloads are added to the |
|
156 // list, meaning we'd show private downloads without any private tabs open. |
|
157 let privateEntries = this.getDownloads({ isPrivate: true }); |
|
158 this._stepAddEntries(privateEntries, this._privateList, privateEntries.length); |
|
159 |
|
160 // Add non-private downloads |
|
161 let normalEntries = this.getDownloads({ isPrivate: false }); |
|
162 this._stepAddEntries(normalEntries, this._normalList, 1, this._scrollToSelectedDownload.bind(this)); |
|
163 ContextMenus.init(); |
|
164 }, |
|
165 |
|
166 uninit: function dl_uninit() { |
|
167 let contextmenus = gChromeWin.NativeWindow.contextmenus; |
|
168 contextmenus.remove(this.openMenuItem); |
|
169 contextmenus.remove(this.removeMenuItem); |
|
170 contextmenus.remove(this.pauseMenuItem); |
|
171 contextmenus.remove(this.resumeMenuItem); |
|
172 contextmenus.remove(this.retryMenuItem); |
|
173 contextmenus.remove(this.cancelMenuItem); |
|
174 contextmenus.remove(this.deleteAllMenuItem); |
|
175 |
|
176 this._dlmgr.removeListener(this); |
|
177 Services.obs.removeObserver(this, "last-pb-context-exited"); |
|
178 Services.obs.removeObserver(this, "download-manager-remove-download-guid"); |
|
179 }, |
|
180 |
|
181 onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, |
|
182 aCurTotalProgress, aMaxTotalProgress, aDownload) { }, |
|
183 onDownloadStateChange: function(aState, aDownload) { |
|
184 switch (aDownload.state) { |
|
185 case Ci.nsIDownloadManager.DOWNLOAD_FAILED: |
|
186 case Ci.nsIDownloadManager.DOWNLOAD_CANCELED: |
|
187 case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: |
|
188 case Ci.nsIDownloadManager.DOWNLOAD_DIRTY: |
|
189 case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: |
|
190 // For all "completed" states, move them after active downloads |
|
191 this._moveDownloadAfterActive(this._getElementForDownload(aDownload.guid)); |
|
192 |
|
193 // Fall-through the rest |
|
194 case Ci.nsIDownloadManager.DOWNLOAD_SCANNING: |
|
195 case Ci.nsIDownloadManager.DOWNLOAD_QUEUED: |
|
196 case Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING: |
|
197 let item = this._getElementForDownload(aDownload.guid); |
|
198 if (item) |
|
199 this._updateDownloadRow(item, aDownload); |
|
200 else |
|
201 this._insertDownloadRow(aDownload); |
|
202 break; |
|
203 } |
|
204 }, |
|
205 onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { }, |
|
206 onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { }, |
|
207 |
|
208 observe: function (aSubject, aTopic, aData) { |
|
209 switch (aTopic) { |
|
210 case "last-pb-context-exited": |
|
211 this._privateList.innerHTML = ""; |
|
212 break; |
|
213 case "download-manager-remove-download-guid": { |
|
214 let guid = aSubject.QueryInterface(Ci.nsISupportsCString).data; |
|
215 this._removeItem(this._getElementForDownload(guid)); |
|
216 break; |
|
217 } |
|
218 } |
|
219 }, |
|
220 |
|
221 _moveDownloadAfterActive: function dl_moveDownloadAfterActive(aItem) { |
|
222 // Move downloads that just reached a "completed" state below any active |
|
223 try { |
|
224 // Iterate down until we find a non-active download |
|
225 let next = aItem.nextElementSibling; |
|
226 while (next && this._inProgress(next.getAttribute("state"))) |
|
227 next = next.nextElementSibling; |
|
228 // Move the item |
|
229 aItem.parentNode.insertBefore(aItem, next); |
|
230 } catch (ex) { |
|
231 this.logError("_moveDownloadAfterActive() " + ex); |
|
232 } |
|
233 }, |
|
234 |
|
235 _inProgress: function dl_inProgress(aState) { |
|
236 return [ |
|
237 this._dlmgr.DOWNLOAD_NOTSTARTED, |
|
238 this._dlmgr.DOWNLOAD_QUEUED, |
|
239 this._dlmgr.DOWNLOAD_DOWNLOADING, |
|
240 this._dlmgr.DOWNLOAD_PAUSED, |
|
241 this._dlmgr.DOWNLOAD_SCANNING, |
|
242 ].indexOf(parseInt(aState)) != -1; |
|
243 }, |
|
244 |
|
245 _insertDownloadRow: function dl_insertDownloadRow(aDownload) { |
|
246 let updatedState = this._getState(aDownload.state); |
|
247 let item = this._createItem(downloadTemplate, { |
|
248 guid: aDownload.guid, |
|
249 target: aDownload.displayName, |
|
250 icon: "moz-icon://" + aDownload.displayName + "?size=64", |
|
251 date: DownloadUtils.getReadableDates(new Date())[0], |
|
252 domain: DownloadUtils.getURIHost(aDownload.source.spec)[0], |
|
253 size: this._getDownloadSize(aDownload.size), |
|
254 displayState: this._getStateString(updatedState), |
|
255 state: updatedState |
|
256 }); |
|
257 list = aDownload.isPrivate ? this._privateList : this._normalList; |
|
258 list.insertAdjacentHTML("afterbegin", item); |
|
259 }, |
|
260 |
|
261 _getDownloadSize: function dl_getDownloadSize(aSize) { |
|
262 if (aSize > 0) { |
|
263 let displaySize = DownloadUtils.convertByteUnits(aSize); |
|
264 return displaySize.join(""); // [0] is size, [1] is units |
|
265 } |
|
266 return gStrings.GetStringFromName("downloadState.unknownSize"); |
|
267 }, |
|
268 |
|
269 // Not all states are displayed as-is on mobile, some are translated to a generic state |
|
270 _getState: function dl_getState(aState) { |
|
271 let str; |
|
272 switch (aState) { |
|
273 // Downloading and Scanning states show up as "Downloading" |
|
274 case this._dlmgr.DOWNLOAD_DOWNLOADING: |
|
275 case this._dlmgr.DOWNLOAD_SCANNING: |
|
276 str = this._dlmgr.DOWNLOAD_DOWNLOADING; |
|
277 break; |
|
278 |
|
279 // Failed, Dirty and Blocked states show up as "Failed" |
|
280 case this._dlmgr.DOWNLOAD_FAILED: |
|
281 case this._dlmgr.DOWNLOAD_DIRTY: |
|
282 case this._dlmgr.DOWNLOAD_BLOCKED_POLICY: |
|
283 case this._dlmgr.DOWNLOAD_BLOCKED_PARENTAL: |
|
284 str = this._dlmgr.DOWNLOAD_FAILED; |
|
285 break; |
|
286 |
|
287 /* QUEUED and NOTSTARTED are not translated as they |
|
288 dont fall under a common state but we still need |
|
289 to display a common "status" on the UI */ |
|
290 |
|
291 default: |
|
292 str = aState; |
|
293 } |
|
294 return str; |
|
295 }, |
|
296 |
|
297 // Note: This doesn't cover all states as some of the states are translated in _getState() |
|
298 _getStateString: function dl_getStateString(aState) { |
|
299 let str; |
|
300 switch (aState) { |
|
301 case this._dlmgr.DOWNLOAD_DOWNLOADING: |
|
302 str = "downloadState.downloading"; |
|
303 break; |
|
304 case this._dlmgr.DOWNLOAD_CANCELED: |
|
305 str = "downloadState.canceled"; |
|
306 break; |
|
307 case this._dlmgr.DOWNLOAD_FAILED: |
|
308 str = "downloadState.failed"; |
|
309 break; |
|
310 case this._dlmgr.DOWNLOAD_PAUSED: |
|
311 str = "downloadState.paused"; |
|
312 break; |
|
313 |
|
314 // Queued and Notstarted show up as "Starting..." |
|
315 case this._dlmgr.DOWNLOAD_QUEUED: |
|
316 case this._dlmgr.DOWNLOAD_NOTSTARTED: |
|
317 str = "downloadState.starting"; |
|
318 break; |
|
319 |
|
320 default: |
|
321 return ""; |
|
322 } |
|
323 return gStrings.GetStringFromName(str); |
|
324 }, |
|
325 |
|
326 _updateItem: function dl_updateItem(aItem, aValues) { |
|
327 for (let i in aValues) { |
|
328 aItem.querySelector("." + i).textContent = aValues[i]; |
|
329 } |
|
330 }, |
|
331 |
|
332 _initStatement: function dv__initStatement(aIsPrivate) { |
|
333 let dbConn = aIsPrivate ? this._dlmgr.privateDBConnection : this._dlmgr.DBConnection; |
|
334 return dbConn.createStatement( |
|
335 "SELECT guid, name, source, state, startTime, endTime, referrer, " + |
|
336 "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " + |
|
337 "FROM moz_downloads " + |
|
338 "ORDER BY isActive DESC, endTime DESC, startTime DESC"); |
|
339 }, |
|
340 |
|
341 _createItem: function _createItem(aTemplate, aValues) { |
|
342 function htmlEscape(s) { |
|
343 s = s.replace(/&/g, "&"); |
|
344 s = s.replace(/>/g, ">"); |
|
345 s = s.replace(/</g, "<"); |
|
346 s = s.replace(/"/g, """); |
|
347 s = s.replace(/'/g, "'"); |
|
348 return s; |
|
349 } |
|
350 |
|
351 let t = aTemplate; |
|
352 for (let key in aValues) { |
|
353 if (aValues.hasOwnProperty(key)) { |
|
354 let regEx = new RegExp("{" + key + "}", "g"); |
|
355 let value = htmlEscape(aValues[key].toString()); |
|
356 t = t.replace(regEx, value); |
|
357 } |
|
358 } |
|
359 return t; |
|
360 }, |
|
361 |
|
362 _getEntry: function dv__getEntry(aStmt) { |
|
363 try { |
|
364 if (!aStmt.executeStep()) { |
|
365 return null; |
|
366 } |
|
367 |
|
368 let updatedState = this._getState(aStmt.row.state); |
|
369 // Try to get the attribute values from the statement |
|
370 |
|
371 return { |
|
372 guid: aStmt.row.guid, |
|
373 target: aStmt.row.name, |
|
374 icon: "moz-icon://" + aStmt.row.name + "?size=64", |
|
375 date: DownloadUtils.getReadableDates(new Date(aStmt.row.endTime / 1000))[0], |
|
376 domain: DownloadUtils.getURIHost(aStmt.row.source)[0], |
|
377 size: this._getDownloadSize(aStmt.row.maxBytes), |
|
378 displayState: this._getStateString(updatedState), |
|
379 state: updatedState |
|
380 }; |
|
381 |
|
382 } catch (e) { |
|
383 // Something went wrong when stepping or getting values, so clear and quit |
|
384 this.logError("_getEntry() " + e); |
|
385 aStmt.reset(); |
|
386 return null; |
|
387 } |
|
388 }, |
|
389 |
|
390 _stepAddEntries: function dv__stepAddEntries(aEntries, aList, aNumItems, aCallback) { |
|
391 |
|
392 if (aEntries.length == 0){ |
|
393 if (aCallback) |
|
394 aCallback(); |
|
395 |
|
396 return; |
|
397 } |
|
398 |
|
399 let attrs = aEntries.shift(); |
|
400 let item = this._createItem(downloadTemplate, attrs); |
|
401 aList.insertAdjacentHTML("beforeend", item); |
|
402 |
|
403 // Add another item to the list if we should; otherwise, let the UI update |
|
404 // and continue later |
|
405 if (aNumItems > 1) { |
|
406 this._stepAddEntries(aEntries, aList, aNumItems - 1, aCallback); |
|
407 } else { |
|
408 // Use a shorter delay for earlier downloads to display them faster |
|
409 let delay = Math.min(aList.itemCount * 10, 300); |
|
410 setTimeout(function () { |
|
411 this._stepAddEntries(aEntries, aList, 5, aCallback); |
|
412 }.bind(this), delay); |
|
413 } |
|
414 }, |
|
415 |
|
416 getDownloads: function dl_getDownloads(aParams) { |
|
417 aParams = aParams || {}; |
|
418 let stmt = this._initStatement(aParams.isPrivate); |
|
419 |
|
420 stmt.reset(); |
|
421 stmt.bindInt32Parameter(0, Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED); |
|
422 stmt.bindInt32Parameter(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING); |
|
423 stmt.bindInt32Parameter(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED); |
|
424 stmt.bindInt32Parameter(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED); |
|
425 stmt.bindInt32Parameter(4, Ci.nsIDownloadManager.DOWNLOAD_SCANNING); |
|
426 |
|
427 let entries = []; |
|
428 while (entry = this._getEntry(stmt)) { |
|
429 entries.push(entry); |
|
430 } |
|
431 |
|
432 stmt.finalize(); |
|
433 |
|
434 return entries; |
|
435 }, |
|
436 |
|
437 _getElementForDownload: function dl_getElementForDownload(aKey) { |
|
438 return document.body.querySelector("li[downloadGUID='" + aKey + "']"); |
|
439 }, |
|
440 |
|
441 _getDownloadForElement: function dl_getDownloadForElement(aElement, aCallback) { |
|
442 let guid = aElement.getAttribute("downloadGUID"); |
|
443 this._dlmgr.getDownloadByGUID(guid, function(status, download) { |
|
444 if (!Components.isSuccessCode(status)) { |
|
445 return; |
|
446 } |
|
447 aCallback(download); |
|
448 }); |
|
449 }, |
|
450 |
|
451 _removeItem: function dl_removeItem(aItem) { |
|
452 // Make sure we have an item to remove |
|
453 if (!aItem) |
|
454 return; |
|
455 |
|
456 aItem.parentNode.removeChild(aItem); |
|
457 }, |
|
458 |
|
459 openDownload: function dl_openDownload(aItem) { |
|
460 this._getDownloadForElement(aItem, function(aDownload) { |
|
461 if (aDownload.state !== Ci.nsIDownloadManager.DOWNLOAD_FINISHED) { |
|
462 // Do not open unfinished downloads. |
|
463 return; |
|
464 } |
|
465 try { |
|
466 let f = aDownload.targetFile; |
|
467 if (f) f.launch(); |
|
468 } catch (ex) { |
|
469 this.logError("openDownload() " + ex, aDownload); |
|
470 } |
|
471 }.bind(this)); |
|
472 }, |
|
473 |
|
474 removeDownload: function dl_removeDownload(aItem) { |
|
475 this._getDownloadForElement(aItem, function(aDownload) { |
|
476 if (aDownload.targetFile) { |
|
477 OS.File.remove(aDownload.targetFile.path).then(null, function onError(reason) { |
|
478 if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) { |
|
479 this.logError("removeDownload() " + reason, aDownload); |
|
480 } |
|
481 }.bind(this)); |
|
482 } |
|
483 |
|
484 aDownload.remove(); |
|
485 }.bind(this)); |
|
486 }, |
|
487 |
|
488 removeAll: function dl_removeAll() { |
|
489 let title = gStrings.GetStringFromName("downloadAction.deleteAll"); |
|
490 let messageForm = gStrings.GetStringFromName("downloadMessage.deleteAll"); |
|
491 let elements = document.body.querySelectorAll("li[state='" + this._dlmgr.DOWNLOAD_FINISHED + "']," + |
|
492 "li[state='" + this._dlmgr.DOWNLOAD_CANCELED + "']," + |
|
493 "li[state='" + this._dlmgr.DOWNLOAD_FAILED + "']"); |
|
494 let message = PluralForm.get(elements.length, messageForm) |
|
495 .replace("#1", elements.length); |
|
496 let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_OK + |
|
497 Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL; |
|
498 let choice = Services.prompt.confirmEx(null, title, message, flags, |
|
499 null, null, null, null, {}); |
|
500 if (choice == 0) { |
|
501 for (let i = 0; i < elements.length; i++) { |
|
502 this.removeDownload(elements[i]); |
|
503 } |
|
504 } |
|
505 }, |
|
506 |
|
507 pauseDownload: function dl_pauseDownload(aItem) { |
|
508 this._getDownloadForElement(aItem, function(aDownload) { |
|
509 try { |
|
510 aDownload.pause(); |
|
511 this._updateDownloadRow(aItem, aDownload); |
|
512 } catch (ex) { |
|
513 this.logError("Error: pauseDownload() " + ex, aDownload); |
|
514 } |
|
515 }.bind(this)); |
|
516 }, |
|
517 |
|
518 resumeDownload: function dl_resumeDownload(aItem) { |
|
519 this._getDownloadForElement(aItem, function(aDownload) { |
|
520 try { |
|
521 aDownload.resume(); |
|
522 this._updateDownloadRow(aItem, aDownload); |
|
523 } catch (ex) { |
|
524 this.logError("resumeDownload() " + ex, aDownload); |
|
525 } |
|
526 }.bind(this)); |
|
527 }, |
|
528 |
|
529 retryDownload: function dl_retryDownload(aItem) { |
|
530 this._getDownloadForElement(aItem, function(aDownload) { |
|
531 try { |
|
532 this._removeItem(aItem); |
|
533 aDownload.retry(); |
|
534 } catch (ex) { |
|
535 this.logError("retryDownload() " + ex, aDownload); |
|
536 } |
|
537 }.bind(this)); |
|
538 }, |
|
539 |
|
540 cancelDownload: function dl_cancelDownload(aItem) { |
|
541 this._getDownloadForElement(aItem, function(aDownload) { |
|
542 OS.File.remove(aDownload.targetFile.path).then(null, function onError(reason) { |
|
543 if (!(reason instanceof OS.File.Error && reason.becauseNoSuchFile)) { |
|
544 this.logError("cancelDownload() " + reason, aDownload); |
|
545 } |
|
546 }.bind(this)); |
|
547 |
|
548 aDownload.cancel(); |
|
549 |
|
550 this._updateDownloadRow(aItem, aDownload); |
|
551 }.bind(this)); |
|
552 }, |
|
553 |
|
554 _updateDownloadRow: function dl_updateDownloadRow(aItem, aDownload) { |
|
555 try { |
|
556 let updatedState = this._getState(aDownload.state); |
|
557 aItem.setAttribute("state", updatedState); |
|
558 this._updateItem(aItem, { |
|
559 size: this._getDownloadSize(aDownload.size), |
|
560 displayState: this._getStateString(updatedState), |
|
561 date: DownloadUtils.getReadableDates(new Date())[0] |
|
562 }); |
|
563 } catch (ex) { |
|
564 this.logError("_updateDownloadRow() " + ex, aDownload); |
|
565 } |
|
566 }, |
|
567 |
|
568 /** |
|
569 * In case a specific downloadId was passed while opening, scrolls the list to |
|
570 * the given elemenet |
|
571 */ |
|
572 |
|
573 _scrollToSelectedDownload : function dl_scrollToSelected() { |
|
574 let spec = document.location.href; |
|
575 let pos = spec.indexOf("?"); |
|
576 let query = ""; |
|
577 if (pos >= 0) |
|
578 query = spec.substring(pos + 1); |
|
579 |
|
580 // Just assume the query is "id=<id>" |
|
581 let id = query.substring(3); |
|
582 if (!id) { |
|
583 return; |
|
584 } |
|
585 downloadElement = this._getElementForDownload(id); |
|
586 if (!downloadElement) { |
|
587 return; |
|
588 } |
|
589 |
|
590 downloadElement.scrollIntoView(); |
|
591 }, |
|
592 |
|
593 /** |
|
594 * Logs the error to the console. |
|
595 * |
|
596 * @param aMessage error message to log |
|
597 * @param aDownload (optional) if given, and if the download is private, the |
|
598 * log message is suppressed |
|
599 */ |
|
600 logError: function dl_logError(aMessage, aDownload) { |
|
601 if (!aDownload || !aDownload.isPrivate) { |
|
602 console.log("Error: " + aMessage); |
|
603 } |
|
604 }, |
|
605 |
|
606 QueryInterface: function (aIID) { |
|
607 if (!aIID.equals(Ci.nsIDownloadProgressListener) && |
|
608 !aIID.equals(Ci.nsISupports)) |
|
609 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
610 return this; |
|
611 } |
|
612 } |
|
613 |
|
614 document.addEventListener("DOMContentLoaded", Downloads.init.bind(Downloads), true); |
|
615 window.addEventListener("unload", Downloads.uninit.bind(Downloads), false); |
|
616 |
|
617 |