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 download_manager_builtin_ui = Components
19 .classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
20 .getService(Ci.nsIDownloadManagerUI);
23 var unmanaged_download_info_list = [];
24 var id_to_download_info = {};
26 // Import these constants for convenience
27 const DOWNLOAD_NOTSTARTED = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
28 const DOWNLOAD_DOWNLOADING = Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
29 const DOWNLOAD_FINISHED = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
30 const DOWNLOAD_FAILED = Ci.nsIDownloadManager.DOWNLOAD_FAILED;
31 const DOWNLOAD_CANCELED = Ci.nsIDownloadManager.DOWNLOAD_CANCELED;
32 const DOWNLOAD_PAUSED = Ci.nsIDownloadManager.DOWNLOAD_PAUSED;
33 const DOWNLOAD_QUEUED = Ci.nsIDownloadManager.DOWNLOAD_QUEUED;
34 const DOWNLOAD_BLOCKED = Ci.nsIDownloadManager.DOWNLOAD_BLOCKED;
35 const DOWNLOAD_SCANNING = Ci.nsIDownloadManager.DOWNLOAD_SCANNING;
38 const DOWNLOAD_NOT_TEMPORARY = 0;
39 const DOWNLOAD_TEMPORARY_FOR_ACTION = 1;
40 const DOWNLOAD_TEMPORARY_FOR_COMMAND = 2;
42 function download_info (source_buffer, mozilla_info, target_file) {
43 this.source_buffer = source_buffer;
44 this.target_file = target_file;
45 if (mozilla_info != null)
46 this.attach(mozilla_info);
48 download_info.prototype = {
49 attach : function (mozilla_info) {
50 if (!this.target_file)
51 this.__defineGetter__("target_file", function(){
52 return this.mozilla_info.targetFile;
54 else if (this.target_file.path != mozilla_info.targetFile.path)
55 throw interactive_error("Download target file unexpected.");
56 this.mozilla_info = mozilla_info;
57 id_to_download_info[mozilla_info.id] = this;
58 download_added_hook.run(this);
65 shell_command_cwd : null,
67 temporary_status : DOWNLOAD_NOT_TEMPORARY,
69 action_description : null,
71 set_shell_command : function (str, cwd) {
72 this.shell_command = str;
73 this.shell_command_cwd = cwd;
74 if (this.mozilla_info)
75 download_shell_command_change_hook.run(this);
79 * None of the following members may be used until attach is called
82 // Reflectors to properties of nsIDownload
83 get state () { return this.mozilla_info.state; },
84 get display_name () { return this.mozilla_info.displayName; },
85 get amount_transferred () { return this.mozilla_info.amountTransferred; },
86 get percent_complete () { return this.mozilla_info.percentComplete; },
88 var s = this.mozilla_info.size;
89 /* nsIDownload.size is a PRUint64, and will have value
90 * LL_MAXUINT (2^64 - 1) to indicate an unknown size. Because
91 * JavaScript only has a double numerical type, this value
92 * cannot be represented exactly, so 2^36 is used instead as the cutoff. */
93 if (s < 68719476736 /* 2^36 */)
97 get source () { return this.mozilla_info.source; },
98 get start_time () { return this.mozilla_info.startTime; },
99 get speed () { return this.mozilla_info.speed; },
100 get MIME_info () { return this.mozilla_info.MIMEInfo; },
103 return this.MIME_info.MIMEType;
106 get id () { return this.mozilla_info.id; },
107 get referrer () { return this.mozilla_info.referrer; },
109 target_file_text : function () {
110 let target = this.target_file.path;
111 let display = this.display_name;
112 if (target.indexOf(display, target.length - display.length) == -1)
113 target += " (" + display + ")";
117 throw_if_removed : function () {
119 throw interactive_error("Download has already been removed from the download manager.");
122 throw_state_error : function () {
123 switch (this.state) {
124 case DOWNLOAD_DOWNLOADING:
125 throw interactive_error("Download is already in progress.");
126 case DOWNLOAD_FINISHED:
127 throw interactive_error("Download has already completed.");
128 case DOWNLOAD_FAILED:
129 throw interactive_error("Download has already failed.");
130 case DOWNLOAD_CANCELED:
131 throw interactive_error("Download has already been canceled.");
132 case DOWNLOAD_PAUSED:
133 throw interactive_error("Download has already been paused.");
134 case DOWNLOAD_QUEUED:
135 throw interactive_error("Download is queued.");
137 throw new Error("Download has unexpected state: " + this.state);
141 // Download manager operations
142 cancel : function () {
143 this.throw_if_removed();
144 switch (this.state) {
145 case DOWNLOAD_DOWNLOADING:
146 case DOWNLOAD_PAUSED:
147 case DOWNLOAD_QUEUED:
149 download_manager_service.cancelDownload(this.id);
151 throw interactive_error("Download cannot be canceled.");
155 this.throw_state_error();
159 retry : function () {
160 this.throw_if_removed();
161 switch (this.state) {
162 case DOWNLOAD_CANCELED:
163 case DOWNLOAD_FAILED:
165 download_manager_service.retryDownload(this.id);
167 throw interactive_error("Download cannot be retried.");
171 this.throw_state_error();
175 resume : function () {
176 this.throw_if_removed();
177 switch (this.state) {
178 case DOWNLOAD_PAUSED:
180 download_manager_service.resumeDownload(this.id);
182 throw interactive_error("Download cannot be resumed.");
186 this.throw_state_error();
190 pause : function () {
191 this.throw_if_removed();
192 switch (this.state) {
193 case DOWNLOAD_DOWNLOADING:
194 case DOWNLOAD_QUEUED:
196 download_manager_service.pauseDownload(this.id);
198 throw interactive_error("Download cannot be paused.");
202 this.throw_state_error();
206 remove : function () {
207 this.throw_if_removed();
208 switch (this.state) {
209 case DOWNLOAD_FAILED:
210 case DOWNLOAD_CANCELED:
211 case DOWNLOAD_FINISHED:
213 download_manager_service.removeDownload(this.id);
215 throw interactive_error("Download cannot be removed.");
219 throw interactive_error("Download is still in progress.");
223 delete_target : function () {
224 if (this.state != DOWNLOAD_FINISHED)
225 throw interactive_error("Download has not finished.");
227 this.target_file.remove(false);
231 case Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
232 throw interactive_error("File has already been deleted.");
233 case Cr.NS_ERROR_FILE_ACCESS_DENIED:
234 throw interactive_error("Access denied");
235 case Cr.NS_ERROR_FILE_DIR_NOT_EMPTY:
236 throw interactive_error("Failed to delete file.");
244 var define_download_local_hook = simple_local_hook_definer();
246 // FIXME: add more parameters
247 function register_download (buffer, source_uri, target_file) {
248 var info = new download_info(buffer, null, target_file);
249 info.registered_time_stamp = Date.now();
250 info.registered_source_uri = source_uri;
251 unmanaged_download_info_list.push(info);
255 function match_registered_download (mozilla_info) {
256 let list = unmanaged_download_info_list;
258 for (let i = 0; i < list.length; ++i) {
260 if (x.registered_source_uri == mozilla_info.source) {
264 if (t - x.registered_time_stamp > download_info_max_queue_delay) {
273 define_download_local_hook("download_added_hook");
274 define_download_local_hook("download_removed_hook");
275 define_download_local_hook("download_finished_hook");
276 define_download_local_hook("download_progress_change_hook");
277 define_download_local_hook("download_state_change_hook");
278 define_download_local_hook("download_shell_command_change_hook");
280 define_variable('delete_temporary_files_for_command', true,
281 'If this is set to true, temporary files downloaded to run a command '+
282 'on them will be deleted once the command completes. If not, the file '+
283 'will stay around forever unless deleted outside the browser.');
285 var download_info_max_queue_delay = 100;
287 var download_progress_listener = {
288 QueryInterface: generate_QI(Ci.nsIDownloadProgressListener),
290 onDownloadStateChange : function (state, download) {
292 /* FIXME: Determine if only new downloads will have this state
293 * as their previous state. */
295 dumpln("download state change: " + download.source.spec + ": " + state + ", " + download.state + ", " + download.id);
297 if (state == DOWNLOAD_NOTSTARTED) {
298 info = match_registered_download(download);
300 info = new download_info(null, download);
301 dumpln("error: encountered unknown new download");
303 info.attach(download);
306 info = id_to_download_info[download.id];
308 dumpln("Error: encountered unknown download");
311 info.mozilla_info = download;
312 download_state_change_hook.run(info);
313 if (info.state == DOWNLOAD_FINISHED) {
314 download_finished_hook.run(info);
316 if (info.shell_command != null) {
317 info.running_shell_command = true;
318 co_call(function () {
320 yield shell_command_with_argument(info.shell_command,
321 info.target_file.path,
322 $cwd = info.shell_command_cwd);
324 handle_interactive_error(info.source_buffer.window, e);
326 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
327 if(delete_temporary_files_for_command) {
328 info.target_file.remove(false /* not recursive */);
330 info.running_shell_command = false;
331 download_shell_command_change_hook.run(info);
334 download_shell_command_change_hook.run(info);
341 onProgressChange : function (progress, request, cur_self_progress, max_self_progress,
342 cur_total_progress, max_total_progress,
344 var info = id_to_download_info[download.id];
346 dumpln("error: encountered unknown download in progress change");
349 info.mozilla_info = download;
350 download_progress_change_hook.run(info);
351 //dumpln("download progress change: " + download.source.spec + ": " + cur_self_progress + "/" + max_self_progress + " "
352 // + cur_total_progress + "/" + max_total_progress + ", " + download.state + ", " + download.id);
355 onSecurityChange : function (progress, request, state, download) {
358 onStateChange : function (progress, request, state_flags, status, download) {
362 var download_observer = {
363 observe : function (subject, topic, data) {
365 case "download-manager-remove-download":
368 // Remove all downloads
369 for (let i in id_to_download_info)
372 let id = subject.QueryInterface(Ci.nsISupportsPRUint32);
373 /* FIXME: determine if this should really be an error */
374 if (!(id in id_to_download_info)) {
375 dumpln("Error: download-manager-remove-download event received for unknown download: " + id);
379 for each (let i in ids) {
380 dumpln("deleting download: " + i);
381 let d = id_to_download_info[i];
383 download_removed_hook.run(d);
384 delete id_to_download_info[i];
390 observer_service.addObserver(download_observer, "download-manager-remove-download", false);
392 download_manager_service.addListener(download_progress_listener);
394 define_variable("download_buffer_min_update_interval", 2000,
395 "Minimum interval (in milliseconds) between updates in download progress buffers.\n" +
396 "Lowering this interval will increase the promptness of the progress display at " +
397 "the cost of using additional processor time.");
399 function download_buffer_modality (buffer, element) {
400 buffer.keymaps.push(download_buffer_keymap);
403 define_keywords("$info");
404 function download_buffer (window, element) {
405 this.constructor_begin();
407 special_buffer.call(this, window, element, forward_keywords(arguments));
408 this.info = arguments.$info;
409 this.local.cwd = this.info.mozilla_info.targetFile.parent;
410 this.description = this.info.mozilla_info.source.spec;
413 this.progress_change_handler_fn = method_caller(this, this.handle_progress_change);
414 add_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
415 add_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
416 this.command_change_handler_fn = method_caller(this, this.update_command_field);
417 add_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
418 this.modalities.push(download_buffer_modality);
419 this.constructor_end();
421 download_buffer.prototype = {
422 __proto__: special_buffer.prototype,
424 handle_kill : function () {
425 special_buffer.prototype.handle_kill.call(this);
426 remove_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
427 remove_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
428 remove_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
430 // Remove all node references
431 delete this.status_textnode;
432 delete this.target_file_node;
433 delete this.transferred_div_node;
434 delete this.transferred_textnode;
435 delete this.progress_container_node;
436 delete this.progress_bar_node;
437 delete this.percent_textnode;
438 delete this.time_textnode;
439 delete this.command_div_node;
440 delete this.command_label_textnode;
441 delete this.command_textnode;
444 update_title : function () {
445 // FIXME: do this properly
447 var info = this.info;
448 var append_transfer_info = false;
449 var append_speed_info = true;
452 case DOWNLOAD_DOWNLOADING:
453 label = "Downloading";
454 append_transfer_info = true;
456 case DOWNLOAD_FINISHED:
457 label = "Download complete";
459 case DOWNLOAD_FAILED:
460 label = "Download failed";
461 append_transfer_info = true;
462 append_speed_info = false;
464 case DOWNLOAD_CANCELED:
465 label = "Download canceled";
466 append_transfer_info = true;
467 append_speed_info = false;
469 case DOWNLOAD_PAUSED:
470 label = "Download paused";
471 append_transfer_info = true;
472 append_speed_info = false;
474 case DOWNLOAD_QUEUED:
476 label = "Download queued";
480 if (append_transfer_info) {
481 if (append_speed_info)
482 new_title = label + " at " + pretty_print_file_size(info.speed).join(" ") + "/s: ";
484 new_title = label + ": ";
485 var trans = pretty_print_file_size(info.amount_transferred);
486 if (info.size >= 0) {
487 var total = pretty_print_file_size(info.size);
488 if (trans[1] == total[1])
489 new_title += trans[0] + "/" + total[0] + " " + total[1];
491 new_title += trans.join(" ") + "/" + total.join(" ");
493 new_title += trans.join(" ");
494 if (info.percent_complete >= 0)
495 new_title += " (" + info.percent_complete + "%)";
498 if (new_title != this.title) {
499 this.title = new_title;
505 handle_progress_change : function () {
506 var cur_time = Date.now();
507 if (this.last_update == null ||
508 (cur_time - this.last_update) > download_buffer_min_update_interval ||
509 this.info.state != this.previous_state) {
511 if (this.update_title())
512 buffer_title_change_hook.run(this);
514 if (this.generated) {
515 this.update_fields();
517 this.previous_status = this.info.status;
518 this.last_update = cur_time;
522 generate : function () {
523 var d = this.document;
524 var g = new dom_generator(d, XHTML_NS);
526 /* Warning: If any additional node references are saved in
527 * this function, appropriate code to delete the saved
528 * properties must be added to handle_kill. */
530 var info = this.info;
532 d.body.setAttribute("class", "download-buffer");
534 g.add_stylesheet("chrome://conkeror-gui/content/downloads.css");
537 var table = g.element("table", d.body);
539 row = g.element("tr", table, "class", "download-info", "id", "download-source");
540 cell = g.element("td", row, "class", "download-label");
541 this.status_textnode = g.text("", cell);
542 cell = g.element("td", row, "class", "download-value");
543 g.text(info.source.spec, cell);
545 row = g.element("tr", table, "class", "download-info", "id", "download-target");
546 cell = g.element("td", row, "class", "download-label");
548 if (info.temporary_status != DOWNLOAD_NOT_TEMPORARY)
549 target_label = "Temp. file:";
551 target_label = "Target:";
552 g.text(target_label, cell);
553 cell = g.element("td", row, "class", "download-value");
554 this.target_file_node = g.text("", cell);
556 row = g.element("tr", table, "class", "download-info", "id", "download-mime-type");
557 cell = g.element("td", row, "class", "download-label");
558 g.text("MIME type:", cell);
559 cell = g.element("td", row, "class", "download-value");
560 g.text(info.MIME_type || "unknown", cell);
562 this.transferred_div_node = row =
563 g.element("tr", table, "class", "download-info", "id", "download-transferred");
564 cell = g.element("td", row, "class", "download-label");
565 g.text("Transferred:", cell);
566 cell = g.element("td", row, "class", "download-value");
567 var sub_item = g.element("div", cell);
568 this.transferred_textnode = g.text("", sub_item);
569 sub_item = g.element("div", cell, "id", "download-percent");
570 this.percent_textnode = g.text("", sub_item);
571 this.progress_container_node = sub_item = g.element("div", cell, "id", "download-progress-container");
572 this.progress_bar_node = g.element("div", sub_item, "id", "download-progress-bar");
574 row = g.element("tr", table, "class", "download-info", "id", "download-time");
575 cell = g.element("td", row, "class", "download-label");
576 g.text("Time:", cell);
577 cell = g.element("td", row, "class", "download-value");
578 this.time_textnode = g.text("", cell);
580 if (info.action_description != null) {
581 row = g.element("tr", table, "class", "download-info", "id", "download-action");
582 cell = g.element("div", row, "class", "download-label");
583 g.text("Action:", cell);
584 cell = g.element("div", row, "class", "download-value");
585 g.text(info.action_description, cell);
588 this.command_div_node = row = g.element("tr", table, "class", "download-info", "id", "download-command");
589 cell = g.element("td", row, "class", "download-label");
590 this.command_label_textnode = g.text("Run command:", cell);
591 cell = g.element("td", row, "class", "download-value");
592 this.command_textnode = g.text("", cell);
595 this.update_fields();
596 this.update_command_field();
599 update_fields : function () {
602 var info = this.info;
605 case DOWNLOAD_DOWNLOADING:
606 label = "Downloading";
608 case DOWNLOAD_FINISHED:
611 case DOWNLOAD_FAILED:
614 case DOWNLOAD_CANCELED:
617 case DOWNLOAD_PAUSED:
620 case DOWNLOAD_QUEUED:
625 this.status_textnode.nodeValue = label + ":";
626 this.target_file_node.nodeValue = info.target_file_text();
627 this.update_time_field();
630 if (info.state == DOWNLOAD_FINISHED)
631 tran_text = pretty_print_file_size(info.size).join(" ");
633 var trans = pretty_print_file_size(info.amount_transferred);
634 if (info.size >= 0) {
635 var total = pretty_print_file_size(info.size);
636 if (trans[1] == total[1])
637 tran_text += trans[0] + "/" + total[0] + " " + total[1];
639 tran_text += trans.join(" ") + "/" + total.join(" ");
641 tran_text += trans.join(" ");
643 this.transferred_textnode.nodeValue = tran_text;
644 if (info.percent_complete >= 0) {
645 this.progress_container_node.style.display = "";
646 this.percent_textnode.nodeValue = info.percent_complete + "%";
647 this.progress_bar_node.style.width = info.percent_complete + "%";
649 this.percent_textnode.nodeValue = "";
650 this.progress_container_node.style.display = "none";
653 this.update_command_field();
656 update_time_field : function () {
657 var info = this.info;
658 var elapsed_text = pretty_print_time((Date.now() - info.start_time / 1000) / 1000) + " elapsed";
660 if (info.state == DOWNLOAD_DOWNLOADING) {
661 text = pretty_print_file_size(info.speed).join(" ") + "/s, ";
663 if (info.state == DOWNLOAD_DOWNLOADING &&
666 let remaining = (info.size - info.amount_transferred) / info.speed;
667 text += pretty_print_time(remaining) + " left (" + elapsed_text + ")";
671 this.time_textnode.nodeValue = text;
674 update_command_field : function () {
677 if (this.info.shell_command != null) {
678 this.command_div_node.style.display = "";
680 if (this.info.running_shell_command)
682 else if (this.info.state == DOWNLOAD_FINISHED)
683 label = "Ran command:";
685 label = "Run command:";
686 this.command_label_textnode.nodeValue = label;
687 this.command_textnode.nodeValue = this.info.shell_command;
689 this.command_div_node.style.display = "none";
694 function download_cancel (buffer) {
695 check_buffer(buffer, download_buffer);
696 var info = buffer.info;
698 buffer.window.minibuffer.message("Download canceled");
700 interactive("download-cancel",
701 "Cancel the current download.\n" +
702 "The download can later be retried using the `download-retry' command, but any " +
703 "data already transferred will be lost.",
705 let result = yield I.window.minibuffer.read_single_character_option(
706 $prompt = "Cancel this download? (y/n)",
707 $options = ["y", "n"]);
709 download_cancel(I.buffer);
712 function download_retry (buffer) {
713 check_buffer(buffer, download_buffer);
714 var info = buffer.info;
716 buffer.window.minibuffer.message("Download retried");
718 interactive("download-retry",
719 "Retry a failed or canceled download.\n" +
720 "This command can be used to retry a download that failed or was canceled using " +
721 "the `download-cancel' command. The download will begin from the start again.",
722 function (I) {download_retry(I.buffer);});
724 function download_pause (buffer) {
725 check_buffer(buffer, download_buffer);
727 buffer.window.minibuffer.message("Download paused");
729 interactive("download-pause",
730 "Pause the current download.\n" +
731 "The download can later be resumed using the `download-resume' command. The " +
732 "data already transferred will not be lost.",
733 function (I) {download_pause(I.buffer);});
735 function download_resume (buffer) {
736 check_buffer(buffer, download_buffer);
737 buffer.info.resume();
738 buffer.window.minibuffer.message("Download resumed");
740 interactive("download-resume",
741 "Resume the current download.\n" +
742 "This command can be used to resume a download paused using the `download-pause' command.",
743 function (I) { download_resume(I.buffer); });
745 function download_remove (buffer) {
746 check_buffer(buffer, download_buffer);
747 buffer.info.remove();
748 buffer.window.minibuffer.message("Download removed");
750 interactive("download-remove",
751 "Remove the current download from the download manager.\n" +
752 "This command can only be used on inactive (paused, canceled, "+
753 "completed, or failed) downloads.",
754 function (I) {download_remove(I.buffer);});
756 function download_retry_or_resume (buffer) {
757 check_buffer(buffer, download_buffer);
758 var info = buffer.info;
759 if (info.state == DOWNLOAD_PAUSED)
760 download_resume(buffer);
762 download_retry(buffer);
764 interactive("download-retry-or-resume",
765 "Retry or resume the current download.\n" +
766 "This command can be used to resume a download paused using the `download-pause' " +
767 "command or canceled using the `download-cancel' command.",
768 function (I) {download_retry_or_resume(I.buffer);});
770 function download_pause_or_resume (buffer) {
771 check_buffer(buffer, download_buffer);
772 var info = buffer.info;
773 if (info.state == DOWNLOAD_PAUSED)
774 download_resume(buffer);
776 download_pause(buffer);
778 interactive("download-pause-or-resume",
779 "Pause or resume the current download.\n" +
780 "This command toggles the paused state of the current download.",
781 function (I) {download_pause_or_resume(I.buffer);});
783 function download_delete_target (buffer) {
784 check_buffer(buffer, download_buffer);
785 var info = buffer.info;
786 info.delete_target();
787 buffer.window.minibuffer.message("Deleted file: " + info.target_file.path);
789 interactive("download-delete-target",
790 "Delete the target file of the current download.\n" +
791 "This command can only be used if the download has finished successfully.",
792 function (I) {download_delete_target(I.buffer);});
794 function download_shell_command (buffer, cwd, cmd) {
795 check_buffer(buffer, download_buffer);
796 var info = buffer.info;
797 if (info.state == DOWNLOAD_FINISHED) {
798 shell_command_with_argument_blind(cmd, info.target_file.path, $cwd = cwd);
801 if (info.state != DOWNLOAD_DOWNLOADING && info.state != DOWNLOAD_PAUSED && info.state != DOWNLOAD_QUEUED)
802 info.throw_state_error();
803 if (cmd == null || cmd.length == 0)
804 info.set_shell_command(null, cwd);
806 info.set_shell_command(cmd, cwd);
807 buffer.window.minibuffer.message("Queued shell command: " + cmd);
809 interactive("download-shell-command",
810 "Run a shell command on the target file of the current download.\n" +
811 "If the download is still in progress, the shell command will be queued " +
812 "to run when the download finishes.",
814 var buffer = check_buffer(I.buffer, download_buffer);
815 var cwd = buffer.info.shell_command_cwd || I.local.cwd;
816 var cmd = yield I.minibuffer.read_shell_command(
818 $initial_value = buffer.info.shell_command ||
819 external_content_handlers.get(buffer.info.MIME_type));
820 download_shell_command(buffer, cwd, cmd);
823 function download_manager_ui () {}
824 download_manager_ui.prototype = {
825 QueryInterface : XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
827 getAttention : function () {},
828 show : function () {},
833 function download_manager_show_builtin_ui (window) {
834 download_manager_builtin_ui.show(window);
836 interactive("download-manager-show-builtin-ui",
837 "Show the built-in (Firefox-style) download manager user interface.",
838 function (I) {download_manager_show_builtin_ui(I.window);});
841 define_variable("download_temporary_file_open_buffer_delay", 500,
842 "Delay (in milliseconds) before a download buffer is opened for "+
843 "temporary downloads. If the download completes before this amount "+
844 "of time, no download buffer will be opened. This variable takes "+
845 "effect only if `open_download_buffer_automatically' is in "+
846 "`download_added_hook', which is the case by default.");
849 define_variable("download_buffer_automatic_open_target",
850 [OPEN_NEW_WINDOW, OPEN_NEW_BUFFER_BACKGROUND],
851 "Target(s) for download buffers created by "+
852 "`open_download_buffer_automatically' and `download-show'.\n"+
853 "It can be a single target or an array of two targets. When it is an "+
854 "array, the `download-show' command will use the second target when "+
855 "called with universal-argument.");
858 function open_download_buffer_automatically (info, target) {
859 var buf = info.source_buffer;
860 if (target == null) {
861 if (typeof(download_buffer_automatic_open_target) == "object")
862 target = download_buffer_automatic_open_target[0];
864 target = download_buffer_automatic_open_target;
867 target = OPEN_NEW_WINDOW;
868 if (info.temporary_status == DOWNLOAD_NOT_TEMPORARY ||
869 download_temporary_file_open_buffer_delay == 0)
871 create_buffer(buf.window, buffer_creator(download_buffer, $info = info), target);
877 add_hook.call(info, "download_finished_hook", finish);
878 timer = call_after_timeout(function () {
879 remove_hook.call(info, "download_finished_hook", finish);
880 create_buffer(buf.window, buffer_creator(download_buffer, $info = info), target);
881 }, download_temporary_file_open_buffer_delay);
884 add_hook("download_added_hook", open_download_buffer_automatically);
891 minibuffer_auto_complete_preferences.download = true;
893 minibuffer.prototype.read_download = function () {
895 $prompt = "Download",
896 $completer = all_word_completer(
897 $completions = function (visitor) {
898 var dls = download_manager_service.activeDownloads;
899 while (dls.hasMoreElements()) {
900 let dl = dls.getNext();
901 visitor(id_to_download_info[dl.id]);
904 $get_string = function (x) x.display_name,
905 $get_description = function (x) x.source.spec,
906 $get_value = function (x) x),
907 $auto_complete = "download",
908 $auto_complete_initial = true,
909 $match_required = true);
910 var result = yield this.read(forward_keywords(arguments));
911 yield co_return(result);
914 interactive("download-show",
915 "Prompt for an ongoing download and open a download buffer showing "+
916 "its progress. When called with universal argument, the second "+
917 "target from `download_buffer_automatic_open_target' will be used.",
920 if (I.P && typeof(download_buffer_automatic_open_target) == "object")
921 target = download_buffer_automatic_open_target[1];
922 open_download_buffer_automatically(
923 (yield I.minibuffer.read_download($prompt = "Show download:")),
927 provide("download-manager");