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
9 require("special-buffer.js");
10 require("mime-type-override.js");
11 require("minibuffer-read-mime-type.js");
13 var download_manager_service = Cc["@mozilla.org/download-manager;1"]
14 .getService(Ci.nsIDownloadManager);
16 var unmanaged_download_info_list = [];
17 var id_to_download_info = {};
19 // Import these constants for convenience
20 const DOWNLOAD_NOTSTARTED = Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
21 const DOWNLOAD_DOWNLOADING = Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
22 const DOWNLOAD_FINISHED = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
23 const DOWNLOAD_FAILED = Ci.nsIDownloadManager.DOWNLOAD_FAILED;
24 const DOWNLOAD_CANCELED = Ci.nsIDownloadManager.DOWNLOAD_CANCELED;
25 const DOWNLOAD_PAUSED = Ci.nsIDownloadManager.DOWNLOAD_PAUSED;
26 const DOWNLOAD_QUEUED = Ci.nsIDownloadManager.DOWNLOAD_QUEUED;
27 const DOWNLOAD_BLOCKED = Ci.nsIDownloadManager.DOWNLOAD_BLOCKED;
28 const DOWNLOAD_SCANNING = Ci.nsIDownloadManager.DOWNLOAD_SCANNING;
31 const DOWNLOAD_NOT_TEMPORARY = 0;
32 const DOWNLOAD_TEMPORARY_FOR_ACTION = 1;
33 const DOWNLOAD_TEMPORARY_FOR_COMMAND = 2;
35 function download_info (source_buffer, mozilla_info, target_file) {
36 this.source_buffer = source_buffer;
37 this.target_file = target_file;
38 if (mozilla_info != null)
39 this.attach(mozilla_info);
41 download_info.prototype = {
42 constructor: download_info,
43 attach: function (mozilla_info, existing) {
44 if (!this.target_file)
45 this.__defineGetter__("target_file", function () {
46 return this.mozilla_info.targetFile;
48 else if (this.target_file.path != mozilla_info.targetFile.path)
49 throw interactive_error("Download target file unexpected.");
50 this.mozilla_info = mozilla_info;
51 id_to_download_info[mozilla_info.id] = this;
53 existing_download_added_hook.run(this);
55 download_added_hook.run(this);
59 shell_command_cwd: null,
60 temporary_status: DOWNLOAD_NOT_TEMPORARY,
61 action_description: null,
62 set_shell_command: function (str, cwd) {
63 this.shell_command = str;
64 this.shell_command_cwd = cwd;
65 if (this.mozilla_info)
66 download_shell_command_change_hook.run(this);
70 * None of the following members may be used until attach is called
73 // Reflectors to properties of nsIDownload
74 get state () { return this.mozilla_info.state; },
75 get display_name () { return this.mozilla_info.displayName; },
76 get amount_transferred () { return this.mozilla_info.amountTransferred; },
77 get percent_complete () { return this.mozilla_info.percentComplete; },
79 var s = this.mozilla_info.size;
80 /* nsIDownload.size is a PRUint64, and will have value
81 * LL_MAXUINT (2^64 - 1) to indicate an unknown size. Because
82 * JavaScript only has a double numerical type, this value
83 * cannot be represented exactly, so 2^36 is used instead as the cutoff. */
84 if (s < 68719476736 /* 2^36 */)
88 get source () { return this.mozilla_info.source; },
89 get start_time () { return this.mozilla_info.startTime; },
90 get speed () { return this.mozilla_info.speed; },
91 get MIME_info () { return this.mozilla_info.MIMEInfo; },
94 return this.MIME_info.MIMEType;
97 get id () { return this.mozilla_info.id; },
98 get referrer () { return this.mozilla_info.referrer; },
100 target_file_text: function () {
101 let target = this.target_file.path;
102 let display = this.display_name;
103 if (target.indexOf(display, target.length - display.length) == -1)
104 target += " (" + display + ")";
108 throw_if_removed: function () {
110 throw interactive_error("Download has already been removed from the download manager.");
113 throw_state_error: function () {
114 switch (this.state) {
115 case DOWNLOAD_DOWNLOADING:
116 throw interactive_error("Download is already in progress.");
117 case DOWNLOAD_FINISHED:
118 throw interactive_error("Download has already completed.");
119 case DOWNLOAD_FAILED:
120 throw interactive_error("Download has already failed.");
121 case DOWNLOAD_CANCELED:
122 throw interactive_error("Download has already been canceled.");
123 case DOWNLOAD_PAUSED:
124 throw interactive_error("Download has already been paused.");
125 case DOWNLOAD_QUEUED:
126 throw interactive_error("Download is queued.");
128 throw new Error("Download has unexpected state: " + this.state);
132 // Download manager operations
133 cancel: function () {
134 this.throw_if_removed();
135 switch (this.state) {
136 case DOWNLOAD_DOWNLOADING:
137 case DOWNLOAD_PAUSED:
138 case DOWNLOAD_QUEUED:
140 download_manager_service.cancelDownload(this.id);
142 throw interactive_error("Download cannot be canceled.");
146 this.throw_state_error();
151 this.throw_if_removed();
152 switch (this.state) {
153 case DOWNLOAD_CANCELED:
154 case DOWNLOAD_FAILED:
156 download_manager_service.retryDownload(this.id);
158 throw interactive_error("Download cannot be retried.");
162 this.throw_state_error();
166 resume: function () {
167 this.throw_if_removed();
168 switch (this.state) {
169 case DOWNLOAD_PAUSED:
171 download_manager_service.resumeDownload(this.id);
173 throw interactive_error("Download cannot be resumed.");
177 this.throw_state_error();
182 this.throw_if_removed();
183 switch (this.state) {
184 case DOWNLOAD_DOWNLOADING:
185 case DOWNLOAD_QUEUED:
187 download_manager_service.pauseDownload(this.id);
189 throw interactive_error("Download cannot be paused.");
193 this.throw_state_error();
197 remove: function () {
198 this.throw_if_removed();
199 switch (this.state) {
200 case DOWNLOAD_FAILED:
201 case DOWNLOAD_CANCELED:
202 case DOWNLOAD_FINISHED:
204 download_manager_service.removeDownload(this.id);
206 throw interactive_error("Download cannot be removed.");
210 throw interactive_error("Download is still in progress.");
214 delete_target: function () {
215 if (this.state != DOWNLOAD_FINISHED)
216 throw interactive_error("Download has not finished.");
218 this.target_file.remove(false);
222 case Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
223 throw interactive_error("File has already been deleted.");
224 case Cr.NS_ERROR_FILE_ACCESS_DENIED:
225 throw interactive_error("Access denied");
226 case Cr.NS_ERROR_FILE_DIR_NOT_EMPTY:
227 throw interactive_error("Failed to delete file.");
235 var define_download_local_hook = simple_local_hook_definer();
237 function register_download (buffer, source_uri, target_file) {
238 var info = new download_info(buffer, null, target_file);
239 info.registered_time_stamp = Date.now();
240 info.registered_source_uri = source_uri;
241 unmanaged_download_info_list.push(info);
245 function match_registered_download (mozilla_info) {
246 let list = unmanaged_download_info_list;
248 for (let i = 0; i < list.length; ++i) {
250 if (x.registered_source_uri == mozilla_info.source) {
254 if (t - x.registered_time_stamp > download_info_max_queue_delay) {
263 define_download_local_hook("existing_download_added_hook");
264 define_download_local_hook("download_added_hook");
265 define_download_local_hook("download_removed_hook");
266 define_download_local_hook("download_finished_hook");
267 define_download_local_hook("download_progress_change_hook");
268 define_download_local_hook("download_state_change_hook");
269 define_download_local_hook("download_shell_command_change_hook");
271 define_variable('delete_temporary_files_for_command', true,
272 'If this is set to true, temporary files downloaded to run a command '+
273 'on them will be deleted once the command completes. If not, the file '+
274 'will stay around forever unless deleted outside the browser.');
276 var download_info_max_queue_delay = 100;
278 var download_progress_listener = {
279 QueryInterface: generate_QI(Ci.nsIDownloadProgressListener),
281 onDownloadStateChange: function (state, download) {
283 /* FIXME: Determine if only new downloads will have this state
284 * as their previous state. */
286 dumpln("download state change: " + download.source.spec + ": " + state + ", " + download.state + ", " + download.id);
288 if (state == DOWNLOAD_NOTSTARTED) {
289 info = match_registered_download(download);
291 info = new download_info(null, download);
292 dumpln("error: encountered unknown new download");
294 info.attach(download);
297 info = id_to_download_info[download.id];
299 dumpln("Error: encountered unknown download");
302 info.mozilla_info = download;
303 download_state_change_hook.run(info);
304 if (info.state == DOWNLOAD_FINISHED) {
305 download_finished_hook.run(info);
307 if (info.shell_command != null) {
308 info.running_shell_command = true;
309 co_call(function () {
311 yield shell_command_with_argument(info.shell_command,
312 info.target_file.path,
313 $cwd = info.shell_command_cwd);
315 handle_interactive_error(info.source_buffer.window, e);
317 if (info.temporary_status == DOWNLOAD_TEMPORARY_FOR_COMMAND)
318 if(delete_temporary_files_for_command) {
319 info.target_file.remove(false /* not recursive */);
321 info.running_shell_command = false;
322 download_shell_command_change_hook.run(info);
325 download_shell_command_change_hook.run(info);
332 onProgressChange: function (progress, request, cur_self_progress, max_self_progress,
333 cur_total_progress, max_total_progress,
335 var info = id_to_download_info[download.id];
337 dumpln("error: encountered unknown download in progress change");
340 info.mozilla_info = download;
341 download_progress_change_hook.run(info);
342 //dumpln("download progress change: " + download.source.spec + ": " + cur_self_progress + "/" + max_self_progress + " "
343 // + cur_total_progress + "/" + max_total_progress + ", " + download.state + ", " + download.id);
346 onSecurityChange: function (progress, request, state, download) {
349 onStateChange: function (progress, request, state_flags, status, download) {
353 var download_observer = {
354 observe: function (subject, topic, data) {
356 case "download-manager-remove-download":
359 // Remove all downloads
360 for (let i in id_to_download_info)
363 let id = subject.QueryInterface(Ci.nsISupportsPRUint32);
364 /* FIXME: determine if this should really be an error */
365 if (!(id in id_to_download_info)) {
366 dumpln("Error: download-manager-remove-download event received for unknown download: " + id);
370 for each (let i in ids) {
371 dumpln("deleting download: " + i);
372 let d = id_to_download_info[i];
374 download_removed_hook.run(d);
375 delete id_to_download_info[i];
381 observer_service.addObserver(download_observer, "download-manager-remove-download", false);
383 download_manager_service.addListener(download_progress_listener);
385 define_variable("download_buffer_min_update_interval", 2000,
386 "Minimum interval (in milliseconds) between updates in download progress buffers.\n" +
387 "Lowering this interval will increase the promptness of the progress display at " +
388 "the cost of using additional processor time.");
390 function download_buffer_modality (buffer, element) {
391 buffer.keymaps.push(download_buffer_keymap);
394 define_keywords("$info");
395 function download_buffer (window) {
396 this.constructor_begin();
398 special_buffer.call(this, window, forward_keywords(arguments));
399 this.info = arguments.$info;
400 this.local.cwd = this.info.mozilla_info.targetFile.parent;
401 this.description = this.info.mozilla_info.source.spec;
404 this.progress_change_handler_fn = method_caller(this, this.handle_progress_change);
405 add_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
406 add_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
407 this.command_change_handler_fn = method_caller(this, this.update_command_field);
408 add_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
409 this.modalities.push(download_buffer_modality);
410 this.constructor_end();
412 download_buffer.prototype = {
413 constructor: download_buffer,
414 __proto__: special_buffer.prototype,
415 toString: function () "#<download_buffer>",
417 destroy: function () {
418 remove_hook.call(this.info, "download_progress_change_hook", this.progress_change_handler_fn);
419 remove_hook.call(this.info, "download_state_change_hook", this.progress_change_handler_fn);
420 remove_hook.call(this.info, "download_shell_command_change_hook", this.command_change_handler_fn);
422 // Remove all node references
423 delete this.status_textnode;
424 delete this.target_file_node;
425 delete this.transferred_div_node;
426 delete this.transferred_textnode;
427 delete this.progress_container_node;
428 delete this.progress_bar_node;
429 delete this.percent_textnode;
430 delete this.time_textnode;
431 delete this.command_div_node;
432 delete this.command_label_textnode;
433 delete this.command_textnode;
435 special_buffer.prototype.destroy.call(this);
438 update_title: function () {
439 // FIXME: do this properly
441 var info = this.info;
442 var append_transfer_info = false;
443 var append_speed_info = true;
446 case DOWNLOAD_DOWNLOADING:
447 label = "Downloading";
448 append_transfer_info = true;
450 case DOWNLOAD_FINISHED:
451 label = "Download complete";
453 case DOWNLOAD_FAILED:
454 label = "Download failed";
455 append_transfer_info = true;
456 append_speed_info = false;
458 case DOWNLOAD_CANCELED:
459 label = "Download canceled";
460 append_transfer_info = true;
461 append_speed_info = false;
463 case DOWNLOAD_PAUSED:
464 label = "Download paused";
465 append_transfer_info = true;
466 append_speed_info = false;
468 case DOWNLOAD_QUEUED:
470 label = "Download queued";
474 if (append_transfer_info) {
475 if (append_speed_info)
476 new_title = label + " at " + pretty_print_file_size(info.speed).join(" ") + "/s: ";
478 new_title = label + ": ";
479 var trans = pretty_print_file_size(info.amount_transferred);
480 if (info.size >= 0) {
481 var total = pretty_print_file_size(info.size);
482 if (trans[1] == total[1])
483 new_title += trans[0] + "/" + total[0] + " " + total[1];
485 new_title += trans.join(" ") + "/" + total.join(" ");
487 new_title += trans.join(" ");
488 if (info.percent_complete >= 0)
489 new_title += " (" + info.percent_complete + "%)";
492 if (new_title != this.title) {
493 this.title = new_title;
499 handle_progress_change: function () {
500 var cur_time = Date.now();
501 if (this.last_update == null ||
502 (cur_time - this.last_update) > download_buffer_min_update_interval ||
503 this.info.state != this.previous_state) {
505 if (this.update_title())
506 buffer_title_change_hook.run(this);
508 if (this.generated) {
509 this.update_fields();
511 this.previous_status = this.info.status;
512 this.last_update = cur_time;
516 generate: function () {
517 var d = this.document;
518 var g = new dom_generator(d, XHTML_NS);
520 /* Warning: If any additional node references are saved in
521 * this function, appropriate code to delete the saved
522 * properties must be added to destroy method. */
524 var info = this.info;
526 d.body.setAttribute("class", "download-buffer");
528 g.add_stylesheet("chrome://conkeror-gui/content/downloads.css");
531 var table = g.element("table", d.body);
533 row = g.element("tr", table, "class", "download-info", "id", "download-source");
534 cell = g.element("td", row, "class", "download-label");
535 this.status_textnode = g.text("", cell);
536 cell = g.element("td", row, "class", "download-value");
537 g.text(info.source.spec, cell);
539 row = g.element("tr", table, "class", "download-info", "id", "download-target");
540 cell = g.element("td", row, "class", "download-label");
542 if (info.temporary_status != DOWNLOAD_NOT_TEMPORARY)
543 target_label = "Temp. file:";
545 target_label = "Target:";
546 g.text(target_label, cell);
547 cell = g.element("td", row, "class", "download-value");
548 this.target_file_node = g.text("", cell);
550 row = g.element("tr", table, "class", "download-info", "id", "download-mime-type");
551 cell = g.element("td", row, "class", "download-label");
552 g.text("MIME type:", cell);
553 cell = g.element("td", row, "class", "download-value");
554 g.text(info.MIME_type || "unknown", cell);
556 this.transferred_div_node = row =
557 g.element("tr", table, "class", "download-info", "id", "download-transferred");
558 cell = g.element("td", row, "class", "download-label");
559 g.text("Transferred:", cell);
560 cell = g.element("td", row, "class", "download-value");
561 var sub_item = g.element("div", cell);
562 this.transferred_textnode = g.text("", sub_item);
563 sub_item = g.element("div", cell, "id", "download-percent");
564 this.percent_textnode = g.text("", sub_item);
565 this.progress_container_node = sub_item = g.element("div", cell, "id", "download-progress-container");
566 this.progress_bar_node = g.element("div", sub_item, "id", "download-progress-bar");
568 row = g.element("tr", table, "class", "download-info", "id", "download-time");
569 cell = g.element("td", row, "class", "download-label");
570 g.text("Time:", cell);
571 cell = g.element("td", row, "class", "download-value");
572 this.time_textnode = g.text("", cell);
574 if (info.action_description != null) {
575 row = g.element("tr", table, "class", "download-info", "id", "download-action");
576 cell = g.element("div", row, "class", "download-label");
577 g.text("Action:", cell);
578 cell = g.element("div", row, "class", "download-value");
579 g.text(info.action_description, cell);
582 this.command_div_node = row = g.element("tr", table, "class", "download-info", "id", "download-command");
583 cell = g.element("td", row, "class", "download-label");
584 this.command_label_textnode = g.text("Run command:", cell);
585 cell = g.element("td", row, "class", "download-value");
586 this.command_textnode = g.text("", cell);
588 this.update_fields();
589 this.update_command_field();
592 update_fields: function () {
595 var info = this.info;
597 switch (info.state) {
598 case DOWNLOAD_DOWNLOADING:
599 label = "Downloading";
601 case DOWNLOAD_FINISHED:
604 case DOWNLOAD_FAILED:
607 case DOWNLOAD_CANCELED:
610 case DOWNLOAD_PAUSED:
613 case DOWNLOAD_QUEUED:
618 this.status_textnode.nodeValue = label + ":";
619 this.target_file_node.nodeValue = info.target_file_text();
620 this.update_time_field();
623 if (info.state == DOWNLOAD_FINISHED)
624 tran_text = pretty_print_file_size(info.size).join(" ");
626 var trans = pretty_print_file_size(info.amount_transferred);
627 if (info.size >= 0) {
628 var total = pretty_print_file_size(info.size);
629 if (trans[1] == total[1])
630 tran_text += trans[0] + "/" + total[0] + " " + total[1];
632 tran_text += trans.join(" ") + "/" + total.join(" ");
634 tran_text += trans.join(" ");
636 this.transferred_textnode.nodeValue = tran_text;
637 if (info.percent_complete >= 0) {
638 this.progress_container_node.style.display = "";
639 this.percent_textnode.nodeValue = info.percent_complete + "%";
640 this.progress_bar_node.style.width = info.percent_complete + "%";
642 this.percent_textnode.nodeValue = "";
643 this.progress_container_node.style.display = "none";
646 this.update_command_field();
649 update_time_field: function () {
650 var info = this.info;
651 var elapsed_text = pretty_print_time((Date.now() - info.start_time / 1000) / 1000) + " elapsed";
653 if (info.state == DOWNLOAD_DOWNLOADING)
654 text = pretty_print_file_size(info.speed).join(" ") + "/s, ";
655 if (info.state == DOWNLOAD_DOWNLOADING &&
659 let remaining = (info.size - info.amount_transferred) / info.speed;
660 text += pretty_print_time(remaining) + " left (" + elapsed_text + ")";
663 this.time_textnode.nodeValue = text;
666 update_command_field: function () {
669 if (this.info.shell_command != null) {
670 this.command_div_node.style.display = "";
672 if (this.info.running_shell_command)
674 else if (this.info.state == DOWNLOAD_FINISHED)
675 label = "Ran command:";
677 label = "Run command:";
678 this.command_label_textnode.nodeValue = label;
679 this.command_textnode.nodeValue = this.info.shell_command;
681 this.command_div_node.style.display = "none";
685 function download_cancel (buffer) {
686 check_buffer(buffer, download_buffer);
687 var info = buffer.info;
689 buffer.window.minibuffer.message("Download canceled");
691 interactive("download-cancel",
692 "Cancel the current download.\n" +
693 "The download can later be retried using the `download-retry' "+
694 "command, but any data already transferred will be lost.",
696 let result = yield I.window.minibuffer.read_single_character_option(
697 $prompt = "Cancel this download? (y/n)",
698 $options = ["y", "n"]);
700 download_cancel(I.buffer);
703 function download_retry (buffer) {
704 check_buffer(buffer, download_buffer);
705 var info = buffer.info;
707 buffer.window.minibuffer.message("Download retried");
709 interactive("download-retry",
710 "Retry a failed or canceled download.\n" +
711 "This command can be used to retry a download that failed or "+
712 "was canceled using the `download-cancel' command. The download "+
713 "will begin from the start again.",
714 function (I) { download_retry(I.buffer); });
716 function download_pause (buffer) {
717 check_buffer(buffer, download_buffer);
719 buffer.window.minibuffer.message("Download paused");
721 interactive("download-pause",
722 "Pause the current download.\n" +
723 "The download can later be resumed using the `download-resume' command. "+
724 "The data already transferred will not be lost.",
725 function (I) { download_pause(I.buffer); });
727 function download_resume (buffer) {
728 check_buffer(buffer, download_buffer);
729 buffer.info.resume();
730 buffer.window.minibuffer.message("Download resumed");
732 interactive("download-resume",
733 "Resume the current download.\n" +
734 "This command can be used to resume a download paused using the "+
735 "`download-pause' command.",
736 function (I) { download_resume(I.buffer); });
738 function download_remove (buffer) {
739 check_buffer(buffer, download_buffer);
740 buffer.info.remove();
741 buffer.window.minibuffer.message("Download removed");
743 interactive("download-remove",
744 "Remove the current download from the download manager.\n" +
745 "This command can only be used on inactive (paused, canceled, "+
746 "completed, or failed) downloads.",
747 function (I) { download_remove(I.buffer); });
749 function download_retry_or_resume (buffer) {
750 check_buffer(buffer, download_buffer);
751 var info = buffer.info;
752 if (info.state == DOWNLOAD_PAUSED)
753 download_resume(buffer);
755 download_retry(buffer);
757 interactive("download-retry-or-resume",
758 "Retry or resume the current download.\n" +
759 "This command can be used to resume a download paused using the " +
760 "`download-pause' command or canceled using the `download-cancel' "+
762 function (I) { download_retry_or_resume(I.buffer); });
764 function download_pause_or_resume (buffer) {
765 check_buffer(buffer, download_buffer);
766 var info = buffer.info;
767 if (info.state == DOWNLOAD_PAUSED)
768 download_resume(buffer);
770 download_pause(buffer);
772 interactive("download-pause-or-resume",
773 "Pause or resume the current download.\n" +
774 "This command toggles the paused state of the current download.",
775 function (I) { download_pause_or_resume(I.buffer); });
777 function download_delete_target (buffer) {
778 check_buffer(buffer, download_buffer);
779 var info = buffer.info;
780 info.delete_target();
781 buffer.window.minibuffer.message("Deleted file: " + info.target_file.path);
783 interactive("download-delete-target",
784 "Delete the target file of the current download.\n" +
785 "This command can only be used if the download has finished successfully.",
786 function (I) { download_delete_target(I.buffer); });
788 function download_shell_command (buffer, cwd, cmd) {
789 check_buffer(buffer, download_buffer);
790 var info = buffer.info;
791 if (info.state == DOWNLOAD_FINISHED) {
792 shell_command_with_argument_blind(cmd, info.target_file.path, $cwd = cwd);
795 if (info.state != DOWNLOAD_DOWNLOADING && info.state != DOWNLOAD_PAUSED && info.state != DOWNLOAD_QUEUED)
796 info.throw_state_error();
797 if (cmd == null || cmd.length == 0)
798 info.set_shell_command(null, cwd);
800 info.set_shell_command(cmd, cwd);
801 buffer.window.minibuffer.message("Queued shell command: " + cmd);
803 interactive("download-shell-command",
804 "Run a shell command on the target file of the current download.\n"+
805 "If the download is still in progress, the shell command will be queued "+
806 "to run when the download finishes.",
808 var buffer = check_buffer(I.buffer, download_buffer);
809 var cwd = buffer.info.shell_command_cwd || I.local.cwd;
810 var cmd = yield I.minibuffer.read_shell_command(
812 $initial_value = buffer.info.shell_command ||
813 external_content_handlers.get(buffer.info.MIME_type));
814 download_shell_command(buffer, cwd, cmd);
817 function download_manager_ui () {}
818 download_manager_ui.prototype = {
819 constructor: download_manager_ui,
820 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
822 getAttention: function () {},
823 show: function () {},
828 interactive("download-manager-show-builtin-ui",
829 "Show the built-in (Firefox-style) download manager window.",
831 Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
832 .getService(Ci.nsIDownloadManagerUI)
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.");
848 define_variable("download_buffer_automatic_open_target", OPEN_NEW_WINDOW,
849 "Target(s) for download buffers created by "+
850 "`open_download_buffer_automatically'.");
852 minibuffer_auto_complete_preferences.download = true;
853 minibuffer.prototype.read_download = function () {
855 $prompt = "Download",
856 $completer = all_word_completer(
857 $completions = function (visitor) {
858 var dls = download_manager_service.activeDownloads;
859 while (dls.hasMoreElements()) {
860 let dl = dls.getNext();
864 $get_string = function (x) x.displayName,
865 $get_description = function (x) x.source.spec,
866 $get_value = function (x) x),
867 $auto_complete = "download",
868 $auto_complete_initial = true,
869 $match_required = true);
870 var result = yield this.read(forward_keywords(arguments));
871 yield co_return(result);
874 function download_show (window, target, mozilla_info) {
876 target = OPEN_NEW_WINDOW;
877 if (mozilla_info.id in id_to_download_info) {
878 var info = id_to_download_info[mozilla_info.id];
880 var info = new download_info(null, null);
881 info.attach(mozilla_info, true /* existing */);
883 create_buffer(window, buffer_creator(download_buffer, $info = info), target);
886 function download_show_new_window (I) {
887 var mozilla_info = yield I.minibuffer.read_download($prompt = "Show download:");
888 download_show(I.window, OPEN_NEW_WINDOW, mozilla_info);
891 function download_show_new_buffer (I) {
892 var mozilla_info = yield I.minibuffer.read_download($prompt = "Show download:");
893 download_show(I.window, OPEN_NEW_BUFFER, mozilla_info);
896 function download_show_new_buffer_background (I) {
897 var mozilla_info = yield I.minibuffer.read_download($prompt = "Show download:");
898 download_show(I.window, OPEN_NEW_BUFFER_BACKGROUND, mozilla_info);
901 function open_download_buffer_automatically (info) {
902 var buf = info.source_buffer;
903 var target = download_buffer_automatic_open_target;
904 if (info.temporary_status == DOWNLOAD_NOT_TEMPORARY ||
905 download_temporary_file_open_buffer_delay == 0)
907 download_show(buf.window, target, info);
913 add_hook.call(info, "download_finished_hook", finish);
914 timer = call_after_timeout(function () {
915 remove_hook.call(info, "download_finished_hook", finish);
916 download_show(buf.window, target, info);
917 }, download_temporary_file_open_buffer_delay);
920 add_hook("download_added_hook", open_download_buffer_automatically);
922 interactive("download-show",
923 "Prompt for an ongoing download and open a download buffer showing "+
925 alternates(download_show_new_buffer,
926 download_show_new_window));
928 provide("download-manager");