2 * (C) Copyright 2008 Jeremy Maitin-Shepard
3 * (C) Copyright 2009 John Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
11 require("special-buffer.js");
12 require("mime-type-override.js");
13 require("minibuffer-read-mime-type.js");
15 var download_manager_service = Cc["@mozilla.org/download-manager;1"]
16 .getService(Ci.nsIDownloadManager);
18 var unmanaged_download_info_list = [];
19 var id_to_download_info = {};
21 // Import these constants for convenience
22 const DOWNLOAD_NOTSTARTED = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
23 const DOWNLOAD_DOWNLOADING = Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
24 const DOWNLOAD_FINISHED = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
25 const DOWNLOAD_FAILED = Ci.nsIDownloadManager.DOWNLOAD_FAILED;
26 const DOWNLOAD_CANCELED = Ci.nsIDownloadManager.DOWNLOAD_CANCELED;
27 const DOWNLOAD_PAUSED = Ci.nsIDownloadManager.DOWNLOAD_PAUSED;
28 const DOWNLOAD_QUEUED = Ci.nsIDownloadManager.DOWNLOAD_QUEUED;
29 const DOWNLOAD_BLOCKED = Ci.nsIDownloadManager.DOWNLOAD_BLOCKED;
30 const DOWNLOAD_SCANNING = Ci.nsIDownloadManager.DOWNLOAD_SCANNING;
33 const DOWNLOAD_NOT_TEMPORARY = 0;
34 const DOWNLOAD_TEMPORARY_FOR_ACTION = 1;
35 const DOWNLOAD_TEMPORARY_FOR_COMMAND = 2;
37 function download_info (source_buffer, mozilla_info, target_file) {
38 this.source_buffer = source_buffer;
39 this.target_file = target_file;
40 if (mozilla_info != null)
41 this.attach(mozilla_info);
43 download_info.prototype = {
44 constructor: download_info,
45 attach: function (mozilla_info) {
46 if (!this.target_file)
47 this.__defineGetter__("target_file", function () {
48 return this.mozilla_info.targetFile;
50 else if (this.target_file.path != mozilla_info.targetFile.path)
51 throw interactive_error("Download target file unexpected.");
52 this.mozilla_info = mozilla_info;
53 id_to_download_info[mozilla_info.id] = this;
54 download_added_hook.run(this);
61 shell_command_cwd: null,
63 temporary_status: DOWNLOAD_NOT_TEMPORARY,
65 action_description: null,
67 set_shell_command: function (str, cwd) {
68 this.shell_command = str;
69 this.shell_command_cwd = cwd;
70 if (this.mozilla_info)
71 download_shell_command_change_hook.run(this);
75 * None of the following members may be used until attach is called
78 // Reflectors to properties of nsIDownload
79 get state () { return this.mozilla_info.state; },
80 get display_name () { return this.mozilla_info.displayName; },
81 get amount_transferred () { return this.mozilla_info.amountTransferred; },
82 get percent_complete () { return this.mozilla_info.percentComplete; },
84 var s = this.mozilla_info.size;
85 /* nsIDownload.size is a PRUint64, and will have value
86 * LL_MAXUINT (2^64 - 1) to indicate an unknown size. Because
87 * JavaScript only has a double numerical type, this value
88 * cannot be represented exactly, so 2^36 is used instead as the cutoff. */
89 if (s < 68719476736 /* 2^36 */)
93 get source () { return this.mozilla_info.source; },
94 get start_time () { return this.mozilla_info.startTime; },
95 get speed () { return this.mozilla_info.speed; },
96 get MIME_info () { return this.mozilla_info.MIMEInfo; },
99 return this.MIME_info.MIMEType;
102 get id () { return this.mozilla_info.id; },
103 get referrer () { return this.mozilla_info.referrer; },
105 target_file_text: function () {
106 let target = this.target_file.path;
107 let display = this.display_name;
108 if (target.indexOf(display, target.length - display.length) == -1)
109 target += " (" + display + ")";
113 throw_if_removed: function () {
115 throw interactive_error("Download has already been removed from the download manager.");
118 throw_state_error: function () {
119 switch (this.state) {
120 case DOWNLOAD_DOWNLOADING:
121 throw interactive_error("Download is already in progress.");
122 case DOWNLOAD_FINISHED:
123 throw interactive_error("Download has already completed.");
124 case DOWNLOAD_FAILED:
125 throw interactive_error("Download has already failed.");
126 case DOWNLOAD_CANCELED:
127 throw interactive_error("Download has already been canceled.");
128 case DOWNLOAD_PAUSED:
129 throw interactive_error("Download has already been paused.");
130 case DOWNLOAD_QUEUED:
131 throw interactive_error("Download is queued.");
133 throw new Error("Download has unexpected state: " + this.state);
137 // Download manager operations
138 cancel: function () {
139 this.throw_if_removed();
140 switch (this.state) {
141 case DOWNLOAD_DOWNLOADING:
142 case DOWNLOAD_PAUSED:
143 case DOWNLOAD_QUEUED:
145 download_manager_service.cancelDownload(this.id);
147 throw interactive_error("Download cannot be canceled.");
151 this.throw_state_error();
156 this.throw_if_removed();
157 switch (this.state) {
158 case DOWNLOAD_CANCELED:
159 case DOWNLOAD_FAILED:
161 download_manager_service.retryDownload(this.id);
163 throw interactive_error("Download cannot be retried.");
167 this.throw_state_error();
171 resume: function () {
172 this.throw_if_removed();
173 switch (this.state) {
174 case DOWNLOAD_PAUSED:
176 download_manager_service.resumeDownload(this.id);
178 throw interactive_error("Download cannot be resumed.");
182 this.throw_state_error();
187 this.throw_if_removed();
188 switch (this.state) {
189 case DOWNLOAD_DOWNLOADING:
190 case DOWNLOAD_QUEUED:
192 download_manager_service.pauseDownload(this.id);
194 throw interactive_error("Download cannot be paused.");
198 this.throw_state_error();
202 remove: function () {
203 this.throw_if_removed();
204 switch (this.state) {
205 case DOWNLOAD_FAILED:
206 case DOWNLOAD_CANCELED:
207 case DOWNLOAD_FINISHED:
209 download_manager_service.removeDownload(this.id);
211 throw interactive_error("Download cannot be removed.");
215 throw interactive_error("Download is still in progress.");
219 delete_target: function () {
220 if (this.state != DOWNLOAD_FINISHED)
221 throw interactive_error("Download has not finished.");
223 this.target_file.remove(false);
227 case Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
228 throw interactive_error("File has already been deleted.");
229 case Cr.NS_ERROR_FILE_ACCESS_DENIED:
230 throw interactive_error("Access denied");
231 case Cr.NS_ERROR_FILE_DIR_NOT_EMPTY:
232 throw interactive_error("Failed to delete file.");
240 var define_download_local_hook = simple_local_hook_definer();
242 // FIXME: add more parameters
243 function register_download (buffer, source_uri, target_file) {
244 var info = new download_info(buffer, null, target_file);
245 info.registered_time_stamp = Date.now();
246 info.registered_source_uri = source_uri;
247 unmanaged_download_info_list.push(info);
251 function match_registered_download (mozilla_info) {
252 let list = unmanaged_download_info_list;
254 for (let i = 0; i < list.length; ++i) {
256 if (x.registered_source_uri == mozilla_info.source) {
260 if (t - x.registered_time_stamp > download_info_max_queue_delay) {
269 define_download_local_hook("download_added_hook");
270 define_download_local_hook("download_removed_hook");
271 define_download_local_hook("download_finished_hook");
272 define_download_local_hook("download_progress_change_hook");
273 define_download_local_hook("download_state_change_hook");
274 define_download_local_hook("download_shell_command_change_hook");
276 define_variable('delete_temporary_files_for_command', true,
277 'If this is set to true, temporary files downloaded to run a command '+
278 'on them will be deleted once the command completes. If not, the file '+
279 'will stay around forever unless deleted outside the browser.');
281 var download_info_max_queue_delay = 100;
283 var download_progress_listener = {
284 QueryInterface: generate_QI(Ci.nsIDownloadProgressListener),
286 onDownloadStateChange: function (state, download) {
288 /* FIXME: Determine if only new downloads will have this state
289 * as their previous state. */
291 dumpln("download state change: " + download.source.spec + ": " + state + ", " + download.state + ", " + download.id);
293 if (state == DOWNLOAD_NOTSTARTED) {
294 info = match_registered_download(download);
296 info = new download_info(null, download);
297 dumpln("error: encountered unknown new download");
299 info.attach(download);
302 info = id_to_download_info[download.id];
304 dumpln("Error: encountered unknown download");
307 info.mozilla_info = download;
308 download_state_change_hook.run(info);
309 if (info.state == DOWNLOAD_FINISHED) {
310 download_finished_hook.run(info);
312 if (info.shell_command != null) {
313 info.running_shell_command = true;
314 co_call(function () {
316 yield shell_command_with_argument(info.shell_command,
317 info.target_file.path,
318 $cwd = info.shell_command_cwd);
320 handle_interactive_error(info.source_buffer.window, e);
322 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
323 if(delete_temporary_files_for_command) {
324 info.target_file.remove(false /* not recursive */);
326 info.running_shell_command = false;
327 download_shell_command_change_hook.run(info);
330 download_shell_command_change_hook.run(info);
337 onProgressChange: function (progress, request, cur_self_progress, max_self_progress,
338 cur_total_progress, max_total_progress,
340 var info = id_to_download_info[download.id];
342 dumpln("error: encountered unknown download in progress change");
345 info.mozilla_info = download;
346 download_progress_change_hook.run(info);
347 //dumpln("download progress change: " + download.source.spec + ": " + cur_self_progress + "/" + max_self_progress + " "
348 // + cur_total_progress + "/" + max_total_progress + ", " + download.state + ", " + download.id);
351 onSecurityChange: function (progress, request, state, download) {
354 onStateChange: function (progress, request, state_flags, status, download) {
358 var download_observer = {
359 observe: function (subject, topic, data) {
361 case "download-manager-remove-download":
364 // Remove all downloads
365 for (let i in id_to_download_info)
368 let id = subject.QueryInterface(Ci.nsISupportsPRUint32);
369 /* FIXME: determine if this should really be an error */
370 if (!(id in id_to_download_info)) {
371 dumpln("Error: download-manager-remove-download event received for unknown download: " + id);
375 for each (let i in ids) {
376 dumpln("deleting download: " + i);
377 let d = id_to_download_info[i];
379 download_removed_hook.run(d);
380 delete id_to_download_info[i];
386 observer_service.addObserver(download_observer, "download-manager-remove-download", false);
388 download_manager_service.addListener(download_progress_listener);
390 define_variable("download_buffer_min_update_interval", 2000,
391 "Minimum interval (in milliseconds) between updates in download progress buffers.\n" +
392 "Lowering this interval will increase the promptness of the progress display at " +
393 "the cost of using additional processor time.");
395 function download_buffer_modality (buffer, element) {
396 buffer.keymaps.push(download_buffer_keymap);
399 define_keywords("$info");
400 function download_buffer (window) {
401 this.constructor_begin();
403 special_buffer.call(this, window, forward_keywords(arguments));
404 this.info = arguments.$info;
405 this.local.cwd = this.info.mozilla_info.targetFile.parent;
406 this.description = this.info.mozilla_info.source.spec;
409 this.progress_change_handler_fn = method_caller(this, this.handle_progress_change);
410 add_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
411 add_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
412 this.command_change_handler_fn = method_caller(this, this.update_command_field);
413 add_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
414 this.modalities.push(download_buffer_modality);
415 this.constructor_end();
417 download_buffer.prototype = {
418 constructor: download_buffer,
419 __proto__: special_buffer.prototype,
421 destroy: function () {
422 remove_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
423 remove_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
424 remove_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
426 // Remove all node references
427 delete this.status_textnode;
428 delete this.target_file_node;
429 delete this.transferred_div_node;
430 delete this.transferred_textnode;
431 delete this.progress_container_node;
432 delete this.progress_bar_node;
433 delete this.percent_textnode;
434 delete this.time_textnode;
435 delete this.command_div_node;
436 delete this.command_label_textnode;
437 delete this.command_textnode;
439 special_buffer.prototype.destroy.call(this);
442 update_title: function () {
443 // FIXME: do this properly
445 var info = this.info;
446 var append_transfer_info = false;
447 var append_speed_info = true;
450 case DOWNLOAD_DOWNLOADING:
451 label = "Downloading";
452 append_transfer_info = true;
454 case DOWNLOAD_FINISHED:
455 label = "Download complete";
457 case DOWNLOAD_FAILED:
458 label = "Download failed";
459 append_transfer_info = true;
460 append_speed_info = false;
462 case DOWNLOAD_CANCELED:
463 label = "Download canceled";
464 append_transfer_info = true;
465 append_speed_info = false;
467 case DOWNLOAD_PAUSED:
468 label = "Download paused";
469 append_transfer_info = true;
470 append_speed_info = false;
472 case DOWNLOAD_QUEUED:
474 label = "Download queued";
478 if (append_transfer_info) {
479 if (append_speed_info)
480 new_title = label + " at " + pretty_print_file_size(info.speed).join(" ") + "/s: ";
482 new_title = label + ": ";
483 var trans = pretty_print_file_size(info.amount_transferred);
484 if (info.size >= 0) {
485 var total = pretty_print_file_size(info.size);
486 if (trans[1] == total[1])
487 new_title += trans[0] + "/" + total[0] + " " + total[1];
489 new_title += trans.join(" ") + "/" + total.join(" ");
491 new_title += trans.join(" ");
492 if (info.percent_complete >= 0)
493 new_title += " (" + info.percent_complete + "%)";
496 if (new_title != this.title) {
497 this.title = new_title;
503 handle_progress_change: function () {
504 var cur_time = Date.now();
505 if (this.last_update == null ||
506 (cur_time - this.last_update) > download_buffer_min_update_interval ||
507 this.info.state != this.previous_state) {
509 if (this.update_title())
510 buffer_title_change_hook.run(this);
512 if (this.generated) {
513 this.update_fields();
515 this.previous_status = this.info.status;
516 this.last_update = cur_time;
520 generate: function () {
521 var d = this.document;
522 var g = new dom_generator(d, XHTML_NS);
524 /* Warning: If any additional node references are saved in
525 * this function, appropriate code to delete the saved
526 * properties must be added to destroy method. */
528 var info = this.info;
530 d.body.setAttribute("class", "download-buffer");
532 g.add_stylesheet("chrome://conkeror-gui/content/downloads.css");
535 var table = g.element("table", d.body);
537 row = g.element("tr", table, "class", "download-info", "id", "download-source");
538 cell = g.element("td", row, "class", "download-label");
539 this.status_textnode = g.text("", cell);
540 cell = g.element("td", row, "class", "download-value");
541 g.text(info.source.spec, cell);
543 row = g.element("tr", table, "class", "download-info", "id", "download-target");
544 cell = g.element("td", row, "class", "download-label");
546 if (info.temporary_status != DOWNLOAD_NOT_TEMPORARY)
547 target_label = "Temp. file:";
549 target_label = "Target:";
550 g.text(target_label, cell);
551 cell = g.element("td", row, "class", "download-value");
552 this.target_file_node = g.text("", cell);
554 row = g.element("tr", table, "class", "download-info", "id", "download-mime-type");
555 cell = g.element("td", row, "class", "download-label");
556 g.text("MIME type:", cell);
557 cell = g.element("td", row, "class", "download-value");
558 g.text(info.MIME_type || "unknown", cell);
560 this.transferred_div_node = row =
561 g.element("tr", table, "class", "download-info", "id", "download-transferred");
562 cell = g.element("td", row, "class", "download-label");
563 g.text("Transferred:", cell);
564 cell = g.element("td", row, "class", "download-value");
565 var sub_item = g.element("div", cell);
566 this.transferred_textnode = g.text("", sub_item);
567 sub_item = g.element("div", cell, "id", "download-percent");
568 this.percent_textnode = g.text("", sub_item);
569 this.progress_container_node = sub_item = g.element("div", cell, "id", "download-progress-container");
570 this.progress_bar_node = g.element("div", sub_item, "id", "download-progress-bar");
572 row = g.element("tr", table, "class", "download-info", "id", "download-time");
573 cell = g.element("td", row, "class", "download-label");
574 g.text("Time:", cell);
575 cell = g.element("td", row, "class", "download-value");
576 this.time_textnode = g.text("", cell);
578 if (info.action_description != null) {
579 row = g.element("tr", table, "class", "download-info", "id", "download-action");
580 cell = g.element("div", row, "class", "download-label");
581 g.text("Action:", cell);
582 cell = g.element("div", row, "class", "download-value");
583 g.text(info.action_description, cell);
586 this.command_div_node = row = g.element("tr", table, "class", "download-info", "id", "download-command");
587 cell = g.element("td", row, "class", "download-label");
588 this.command_label_textnode = g.text("Run command:", cell);
589 cell = g.element("td", row, "class", "download-value");
590 this.command_textnode = g.text("", cell);
592 this.update_fields();
593 this.update_command_field();
596 update_fields: function () {
599 var info = this.info;
601 switch (info.state) {
602 case DOWNLOAD_DOWNLOADING:
603 label = "Downloading";
605 case DOWNLOAD_FINISHED:
608 case DOWNLOAD_FAILED:
611 case DOWNLOAD_CANCELED:
614 case DOWNLOAD_PAUSED:
617 case DOWNLOAD_QUEUED:
622 this.status_textnode.nodeValue = label + ":";
623 this.target_file_node.nodeValue = info.target_file_text();
624 this.update_time_field();
627 if (info.state == DOWNLOAD_FINISHED)
628 tran_text = pretty_print_file_size(info.size).join(" ");
630 var trans = pretty_print_file_size(info.amount_transferred);
631 if (info.size >= 0) {
632 var total = pretty_print_file_size(info.size);
633 if (trans[1] == total[1])
634 tran_text += trans[0] + "/" + total[0] + " " + total[1];
636 tran_text += trans.join(" ") + "/" + total.join(" ");
638 tran_text += trans.join(" ");
640 this.transferred_textnode.nodeValue = tran_text;
641 if (info.percent_complete >= 0) {
642 this.progress_container_node.style.display = "";
643 this.percent_textnode.nodeValue = info.percent_complete + "%";
644 this.progress_bar_node.style.width = info.percent_complete + "%";
646 this.percent_textnode.nodeValue = "";
647 this.progress_container_node.style.display = "none";
650 this.update_command_field();
653 update_time_field: function () {
654 var info = this.info;
655 var elapsed_text = pretty_print_time((Date.now() - info.start_time / 1000) / 1000) + " elapsed";
657 if (info.state == DOWNLOAD_DOWNLOADING)
658 text = pretty_print_file_size(info.speed).join(" ") + "/s, ";
659 if (info.state == DOWNLOAD_DOWNLOADING &&
663 let remaining = (info.size - info.amount_transferred) / info.speed;
664 text += pretty_print_time(remaining) + " left (" + elapsed_text + ")";
667 this.time_textnode.nodeValue = text;
670 update_command_field: function () {
673 if (this.info.shell_command != null) {
674 this.command_div_node.style.display = "";
676 if (this.info.running_shell_command)
678 else if (this.info.state == DOWNLOAD_FINISHED)
679 label = "Ran command:";
681 label = "Run command:";
682 this.command_label_textnode.nodeValue = label;
683 this.command_textnode.nodeValue = this.info.shell_command;
685 this.command_div_node.style.display = "none";
689 function download_cancel (buffer) {
690 check_buffer(buffer, download_buffer);
691 var info = buffer.info;
693 buffer.window.minibuffer.message("Download canceled");
695 interactive("download-cancel",
696 "Cancel the current download.\n" +
697 "The download can later be retried using the `download-retry' "+
698 "command, but any data already transferred will be lost.",
700 let result = yield I.window.minibuffer.read_single_character_option(
701 $prompt = "Cancel this download? (y/n)",
702 $options = ["y", "n"]);
704 download_cancel(I.buffer);
707 function download_retry (buffer) {
708 check_buffer(buffer, download_buffer);
709 var info = buffer.info;
711 buffer.window.minibuffer.message("Download retried");
713 interactive("download-retry",
714 "Retry a failed or canceled download.\n" +
715 "This command can be used to retry a download that failed or "+
716 "was canceled using the `download-cancel' command. The download "+
717 "will begin from the start again.",
718 function (I) { download_retry(I.buffer); });
720 function download_pause (buffer) {
721 check_buffer(buffer, download_buffer);
723 buffer.window.minibuffer.message("Download paused");
725 interactive("download-pause",
726 "Pause the current download.\n" +
727 "The download can later be resumed using the `download-resume' command. "+
728 "The data already transferred will not be lost.",
729 function (I) { download_pause(I.buffer); });
731 function download_resume (buffer) {
732 check_buffer(buffer, download_buffer);
733 buffer.info.resume();
734 buffer.window.minibuffer.message("Download resumed");
736 interactive("download-resume",
737 "Resume the current download.\n" +
738 "This command can be used to resume a download paused using the "+
739 "`download-pause' command.",
740 function (I) { download_resume(I.buffer); });
742 function download_remove (buffer) {
743 check_buffer(buffer, download_buffer);
744 buffer.info.remove();
745 buffer.window.minibuffer.message("Download removed");
747 interactive("download-remove",
748 "Remove the current download from the download manager.\n" +
749 "This command can only be used on inactive (paused, canceled, "+
750 "completed, or failed) downloads.",
751 function (I) { download_remove(I.buffer); });
753 function download_retry_or_resume (buffer) {
754 check_buffer(buffer, download_buffer);
755 var info = buffer.info;
756 if (info.state == DOWNLOAD_PAUSED)
757 download_resume(buffer);
759 download_retry(buffer);
761 interactive("download-retry-or-resume",
762 "Retry or resume the current download.\n" +
763 "This command can be used to resume a download paused using the " +
764 "`download-pause' command or canceled using the `download-cancel' "+
766 function (I) { download_retry_or_resume(I.buffer); });
768 function download_pause_or_resume (buffer) {
769 check_buffer(buffer, download_buffer);
770 var info = buffer.info;
771 if (info.state == DOWNLOAD_PAUSED)
772 download_resume(buffer);
774 download_pause(buffer);
776 interactive("download-pause-or-resume",
777 "Pause or resume the current download.\n" +
778 "This command toggles the paused state of the current download.",
779 function (I) { download_pause_or_resume(I.buffer); });
781 function download_delete_target (buffer) {
782 check_buffer(buffer, download_buffer);
783 var info = buffer.info;
784 info.delete_target();
785 buffer.window.minibuffer.message("Deleted file: " + info.target_file.path);
787 interactive("download-delete-target",
788 "Delete the target file of the current download.\n" +
789 "This command can only be used if the download has finished successfully.",
790 function (I) { download_delete_target(I.buffer); });
792 function download_shell_command (buffer, cwd, cmd) {
793 check_buffer(buffer, download_buffer);
794 var info = buffer.info;
795 if (info.state == DOWNLOAD_FINISHED) {
796 shell_command_with_argument_blind(cmd, info.target_file.path, $cwd = cwd);
799 if (info.state != DOWNLOAD_DOWNLOADING && info.state != DOWNLOAD_PAUSED && info.state != DOWNLOAD_QUEUED)
800 info.throw_state_error();
801 if (cmd == null || cmd.length == 0)
802 info.set_shell_command(null, cwd);
804 info.set_shell_command(cmd, cwd);
805 buffer.window.minibuffer.message("Queued shell command: " + cmd);
807 interactive("download-shell-command",
808 "Run a shell command on the target file of the current download.\n"+
809 "If the download is still in progress, the shell command will be queued "+
810 "to run when the download finishes.",
812 var buffer = check_buffer(I.buffer, download_buffer);
813 var cwd = buffer.info.shell_command_cwd || I.local.cwd;
814 var cmd = yield I.minibuffer.read_shell_command(
816 $initial_value = buffer.info.shell_command ||
817 external_content_handlers.get(buffer.info.MIME_type));
818 download_shell_command(buffer, cwd, cmd);
821 function download_manager_ui () {}
822 download_manager_ui.prototype = {
823 constructor: download_manager_ui,
824 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
826 getAttention: function () {},
827 show: function () {},
832 interactive("download-manager-show-builtin-ui",
833 "Show the built-in (Firefox-style) download manager window.",
835 Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
836 .getService(Ci.nsIDownloadManagerUI)
845 define_variable("download_temporary_file_open_buffer_delay", 500,
846 "Delay (in milliseconds) before a download buffer is opened for "+
847 "temporary downloads. If the download completes before this amount "+
848 "of time, no download buffer will be opened. This variable takes "+
849 "effect only if `open_download_buffer_automatically' is in "+
850 "`download_added_hook', which is the case by default.");
852 define_variable("download_buffer_automatic_open_target", OPEN_NEW_WINDOW,
853 "Target(s) for download buffers created by "+
854 "`open_download_buffer_automatically'.");
856 minibuffer_auto_complete_preferences.download = true;
857 minibuffer.prototype.read_download = function () {
859 $prompt = "Download",
860 $completer = all_word_completer(
861 $completions = function (visitor) {
862 var dls = download_manager_service.activeDownloads;
863 while (dls.hasMoreElements()) {
864 let dl = dls.getNext();
865 visitor(id_to_download_info[dl.id]);
868 $get_string = function (x) x.display_name,
869 $get_description = function (x) x.source.spec,
870 $get_value = function (x) x),
871 $auto_complete = "download",
872 $auto_complete_initial = true,
873 $match_required = true);
874 var result = yield this.read(forward_keywords(arguments));
875 yield co_return(result);
878 function download_show (window, target, info) {
880 target = OPEN_NEW_WINDOW;
881 create_buffer(window, buffer_creator(download_buffer, $info = info), target);
884 function download_show_new_window (I) {
885 var info = yield I.minibuffer.read_download($prompt = "Show download:");
886 download_show(I.window, OPEN_NEW_WINDOW, info);
889 function download_show_new_buffer (I) {
890 var info = yield I.minibuffer.read_download($prompt = "Show download:");
891 download_show(I.window, OPEN_NEW_BUFFER, info);
894 function download_show_new_buffer_background (I) {
895 var info = yield I.minibuffer.read_download($prompt = "Show download:");
896 download_show(I.window, OPEN_NEW_BUFFER_BACKGROUND, info);
899 function open_download_buffer_automatically (info) {
900 var buf = info.source_buffer;
901 var target = download_buffer_automatic_open_target;
902 if (info.temporary_status == DOWNLOAD_NOT_TEMPORARY ||
903 download_temporary_file_open_buffer_delay == 0)
905 download_show(buf.window, target, info);
911 add_hook.call(info, "download_finished_hook", finish);
912 timer = call_after_timeout(function () {
913 remove_hook.call(info, "download_finished_hook", finish);
914 download_show(buf.window, target, info);
915 }, download_temporary_file_open_buffer_delay);
918 add_hook("download_added_hook", open_download_buffer_automatically);
920 interactive("download-show",
921 "Prompt for an ongoing download and open a download buffer showing "+
923 alternates(download_show_new_buffer,
924 download_show_new_window));
926 provide("download-manager");