keywords.js: make write_keywords public and more flexible
[conkeror.git] / modules / element.js
blob61ae2792b53c6bb8aabf185d104150573b659f5f
1 /**
2  * (C) Copyright 2007-2008 John J. Foerch
3  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
4  *
5  * Portions of this file are derived from Vimperator,
6  * (C) Copyright 2006-2007 Martin Stubenschrott.
7  *
8  * Use, modification, and distribution are subject to the terms specified in the
9  * COPYING file.
10 **/
12 require("hints.js");
13 require("save.js");
14 require("mime-type-override.js");
15 require("minibuffer-read-mime-type.js");
17 var browser_object_classes = {};
19 /**
20  * handler is a coroutine called as: handler(buffer, prompt)
21  */
22 define_keywords("$doc", "$action", "$label", "$handler", "$xpath_expression");
23 function define_browser_object_class(name) {
24     keywords(arguments, $xpath_expression = undefined);
25     var handler = arguments.$handler;
26     let xpath_expression = arguments.$xpath_expression;
27     if (handler === undefined && xpath_expression != undefined) {
28         handler = function (buf, prompt) {
29             var result = yield buf.window.minibuffer.read_hinted_element(
30                 $buffer = buf,
31                 $prompt = prompt,
32                 $hint_xpath_expression = xpath_expression);
33             yield co_return(result);
34         };
35     }
36     var base_obj = browser_object_classes[name];
37     if (base_obj == null)
38         base_obj = browser_object_classes[name] = {};
39     var obj;
40     if (arguments.$action) {
41         name = name + "/" + arguments.$action;
42         obj = browser_object_classes[name];
43         if (obj == null)
44             obj = browser_object_classes[name] = {__proto__: base_obj};
45     } else
46         obj = base_obj;
47     if (arguments.$label !== undefined)
48         obj.label = arguments.$label;
49     if (arguments.$doc !== undefined)
50         obj.doc = arguments.$doc;
51     if (handler !== undefined)
52         obj.handler = handler;
53     interactive(
54         "browser-object-class-"+name,
55         "A prefix command to specify that the following command operate "+
56             "on objects of type: "+name+".",
57         function (ctx) { ctx._browser_object_class = name; },
58         $prefix = true);
61 define_browser_object_class("images",
62                             $label = "image",
63                             $xpath_expression = "//img | //xhtml:img");
65 define_browser_object_class("frames", $label = "frame", $handler = function (buf, prompt) {
66     check_buffer(buf, content_buffer);
67     var doc = buf.document;
68     if (doc.getElementsByTagName("frame").length == 0 &&
69         doc.getElementsByTagName("iframe").length == 0)
70     {
71         // only one frame (the top-level one), no need to use the hints system
72         yield co_return(buf.top_frame);
73     }
75     var result = yield buf.window.minibuffer.read_hinted_element(
76         $buffer = buf,
77         $prompt = prompt,
78         $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
79     yield co_return(result);
80 });
82 define_browser_object_class(
83     "links", $label = "link",
84     $xpath_expression =
85         "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
86         "@role='link'] | " +
87         "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | //label | " +
88         "//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand] | " +
89         "//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | " +
90         "//xhtml:button | //xhtml:select");
92 define_browser_object_class("mathml", $label = "MathML element", $xpath_expression = "//m:math");
94 define_browser_object_class("top", $handler = function (buf, prompt) { yield co_return(buf.top_frame); });
96 define_browser_object_class("url", $handler = function (buf, prompt) {
97                                 check_buffer (buf, content_buffer);
98                                 var result = yield buf.window.minibuffer.read_url ($prompt = prompt);
99                                 yield co_return (result);
100                             });
102 define_variable(
103     "default_browser_object_classes",
104     {
105         follow: "links",
106         follow_top: "frames",
107         focus: "frames",
108         save: "links",
109         copy: "links",
110         view_source: "frames",
111         bookmark: "frames",
112         save_page: "frames",
113         save_page_complete: "top",
114         save_page_as_text: "frames",
115         default: "links"
116     },
117     "Specifies the default object class for each operation.\n" +
118         "This variable should be an object literal with string-valued properties that specify one of the defined browser object classes.  If a property named after the operation is not present, the \"default\" property is consulted instead.");
120 interactive_context.prototype.browser_object_class = function (action_name) {
121     var cls =
122         this._browser_object_class ||
123         this.get("default_browser_object_classes")[action_name] ||
124         this.get("default_browser_object_classes")["default"];
125     return cls;
128 function lookup_browser_object_class(class_name, action) {
129     var obj;
130     if (action != null) {
131         obj = browser_object_classes[class_name + "/" + action];
132         if (obj)
133             return obj;
134     }
135     return browser_object_classes[class_name];
138 interactive_context.prototype.read_browser_object = function(action, action_name, target)
140     var object_class_name = this.browser_object_class(action);
141     var object_class = lookup_browser_object_class(object_class_name, action);
143     var prompt = action_name;
144     var label = object_class.label || object_class_name;
145     if (target != null)
146         prompt += TARGET_PROMPTS[target];
147     prompt += " (select " + label + "):";
149     var result = yield object_class.handler.call(null, this.buffer, prompt);
150     yield co_return(result);
154 function is_dom_node_or_window(elem) {
155     if (elem instanceof Ci.nsIDOMNode)
156         return true;
157     if (elem instanceof Ci.nsIDOMWindow)
158         return true;
159     return false;
163  * This is a simple wrapper function that sets focus to elem, and
164  * bypasses the automatic focus prevention system, which might
165  * otherwise prevent this from happening.
166  */
167 function browser_set_element_focus(buffer, elem, prevent_scroll) {
168     if (!is_dom_node_or_window(elem))
169         return;
171     buffer.last_user_input_received = Date.now();
172     if (prevent_scroll)
173         set_focus_no_scroll(buffer.window, elem);
174     else
175         elem.focus();
178 function browser_element_focus(buffer, elem)
180     if (!is_dom_node_or_window(elem))
181         return;
183     if (elem instanceof Ci.nsIDOMXULTextBoxElement)  {
184         // Focus the input field instead
185         elem = elem.wrappedJSObject.inputField;
186     }
188     browser_set_element_focus(buffer, elem);
189     if (elem instanceof Ci.nsIDOMWindow) {
190         return;
191     }
192     // If it is not a window, it must be an HTML element
193     var x = 0;
194     var y = 0;
195     if (elem instanceof Ci.nsIDOMHTMLFrameElement || elem instanceof Ci.nsIDOMHTMLIFrameElement) {
196         elem.contentWindow.focus();
197         return;
198     }
199     if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
200         var coords = elem.getAttribute("coords").split(",");
201         x = Number(coords[0]);
202         y = Number(coords[1]);
203     }
205     var doc = elem.ownerDocument;
206     var evt = doc.createEvent("MouseEvents");
207     var doc = elem.ownerDocument;
209     evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
210     elem.dispatchEvent(evt);
213 function browser_element_follow(buffer, target, elem)
215     browser_set_element_focus(buffer, elem, true /* no scroll */);
217     var no_click = (is_load_spec(elem) ||
218                     (elem instanceof Ci.nsIDOMWindow) ||
219                     (elem instanceof Ci.nsIDOMHTMLFrameElement) ||
220                     (elem instanceof Ci.nsIDOMHTMLIFrameElement) ||
221                     (elem instanceof Ci.nsIDOMHTMLLinkElement) ||
222                     (elem instanceof Ci.nsIDOMHTMLImageElement &&
223                      !elem.hasAttribute("onmousedown") && !elem.hasAttribute("onclick")));
225     if (target == FOLLOW_DEFAULT && !no_click) {
226         var x = 1, y = 1;
227         if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
228             var coords = elem.getAttribute("coords").split(",");
229             if (coords.length >= 2) {
230                 x = Number(coords[0]) + 1;
231                 y = Number(coords[1]) + 1;
232             }
233         }
234         browser_follow_link_with_click(buffer, elem, x, y);
235         return;
236     }
238     var spec = element_get_load_spec(elem);
239     if (spec == null) {
240         throw interactive_error("Element has no associated URL");
241         return;
242     }
244     if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
245         // This URL won't work
246         throw interactive_error("Can't load javascript URL");
247     }
249     if (!(buffer instanceof content_buffer) &&
250         (target == FOLLOW_CURRENT_FRAME ||
251          target == FOLLOW_DEFAULT ||
252          target == FOLLOW_TOP_FRAME ||
253          target == OPEN_CURRENT_BUFFER))
254         target = OPEN_NEW_BUFFER;
256     switch (target) {
257     case FOLLOW_CURRENT_FRAME:
258         var current_frame = load_spec_source_frame(spec);
259         if (current_frame && current_frame != buffer.top_frame) {
260             var target_obj = get_web_navigation_for_frame(current_frame);
261             apply_load_spec(target_obj, spec);
262             break;
263         }
264     case FOLLOW_DEFAULT:
265     case FOLLOW_TOP_FRAME:
266     case OPEN_CURRENT_BUFFER:
267         buffer.load(spec);
268         break;
269     case OPEN_NEW_WINDOW:
270     case OPEN_NEW_BUFFER:
271     case OPEN_NEW_BUFFER_BACKGROUND:
272         create_buffer(buffer.window,
273                       buffer_creator(content_buffer,
274                                      $load = spec,
275                                      $configuration = buffer.configuration),
276                       target);
277     }
281  * Follow a link-like element by generating fake mouse events.
282  */
283 function browser_follow_link_with_click(buffer, elem, x, y) {
284     var doc = elem.ownerDocument;
285     var view = doc.defaultView;
287     var evt = doc.createEvent("MouseEvents");
288     evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
289                        /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
290     elem.dispatchEvent(evt);
292     evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
293                        /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
294     elem.dispatchEvent(evt);
297 function element_get_load_spec(elem) {
299     if (is_load_spec(elem))
300         return elem;
302     var spec = null;
304     if (elem instanceof Ci.nsIDOMWindow)
305         spec = load_spec({document: elem.document});
307     else if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
308              elem instanceof Ci.nsIDOMHTMLIFrameElement)
309         spec = load_spec({document: elem.contentDocument});
311     else {
312         var url = null;
313         var title = null;
315         if (elem instanceof Ci.nsIDOMHTMLAnchorElement ||
316             elem instanceof Ci.nsIDOMHTMLAreaElement ||
317             elem instanceof Ci.nsIDOMHTMLLinkElement) {
318             if (!elem.hasAttribute("href"))
319                 return null; // nothing can be done, as no nesting within these elements is allowed
320             url = elem.href;
321             title = elem.title || elem.textContent;
322         }
323         else if (elem instanceof Ci.nsIDOMHTMLImageElement) {
324             url = elem.src;
325             title = elem.title || elem.alt;
326         }
327         else {
328             var node = elem;
329             while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
330                 node = node.parentNode;
331             if (node && !node.hasAttribute("href"))
332                 node = null;
333             else
334                 url = node.href;
335             if (!node) {
336                 // Try simple XLink
337                 node = elem;
338                 while (node) {
339                     if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
340                         url = linkNode.getAttributeNS(XLINK_NS, "href");
341                         break;
342                     }
343                     node = node.parentNode;
344                 }
345                 if (url)
346                     url = makeURLAbsolute(node.baseURI, url);
347                 title = node.title || node.textContent;
348             }
349         }
350         if (url && url.length > 0) {
351             if (title && title.length == 0)
352                 title = null;
353             spec = load_spec({uri: url, source_frame: elem.ownerDocument.defaultView, title: title});
354         }
355     }
356     return spec;
359 interactive("follow", null, function (I) {
360     var target = I.browse_target("follow");
361     var element = yield I.read_browser_object("follow", "Follow", target);
362     browser_element_follow(I.buffer, target, element);
365 interactive("follow-top", null, function (I) {
366     var target = I.browse_target("follow-top");
367     var element = yield I.read_browser_object("follow_top", "Follow", target);
368     browser_element_follow(I.buffer, target, element);
371 interactive("focus", null, function (I) {
372     var element = yield I.read_browser_object("focus", "Focus");
373     browser_element_focus(I.buffer, element);
376 function element_get_load_target_label(element) {
377     if (element instanceof Ci.nsIDOMWindow)
378         return "page";
379     if (element instanceof Ci.nsIDOMHTMLFrameElement)
380         return "frame";
381     if (element instanceof Ci.nsIDOMHTMLIFrameElement)
382         return "iframe";
383     return null;
386 function element_get_operation_label(element, op_name, suffix) {
387     var target_label = element_get_load_target_label(element);
388     if (target_label != null)
389         target_label = " " + target_label;
390     else
391         target_label = "";
393     if (suffix != null)
394         suffix = " " + suffix;
395     else
396         suffix = "";
398     return op_name + target_label + suffix + ":";
401 interactive("save", null, function (I) {
402     var element = yield I.read_browser_object("save", "Save");
404     var spec = element_get_load_spec(element);
405     if (spec == null)
406         throw interactive_error("Element has no associated URI");
408     var panel;
409     panel = create_info_panel(I.window, "download-panel",
410                               [["downloading",
411                                 element_get_operation_label(element, "Saving"),
412                                 load_spec_uri_string(spec)],
413                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
415     try {
416         var file = yield I.minibuffer.read_file_check_overwrite(
417             $prompt = "Save as:",
418             $initial_value = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer),
419             $history = "save");
421     } finally {
422         panel.destroy();
423     }
425     save_uri(spec, file,
426              $buffer = I.buffer,
427              $use_cache = false);
430 function browser_element_copy(buffer, elem)
432     var spec = element_get_load_spec(elem);
433     var text = null;
434     if (spec)
435         text = load_spec_uri_string(spec);
436     else  {
437         if (!(elem instanceof Ci.nsIDOMNode))
438             throw interactive_error("Element has no associated text to copy.");
439         switch (elem.localName) {
440         case "INPUT":
441         case "TEXTAREA":
442             text = elem.value;
443             break;
444         case "SELECT":
445             if (elem.selectedIndex >= 0)
446                 text = elem.item(elem.selectedIndex).text;
447             break;
448         default:
449             text = elem.textContent;
450             break;
451         }
452     }
453     browser_set_element_focus(buffer, elem);
454     writeToClipboard (text);
455     buffer.window.minibuffer.message ("Copied: " + text);
459 interactive("copy", null, function (I) {
460     var element = yield I.read_browser_object("copy", "Copy");
461     browser_element_copy(I.buffer, element);
464 var view_source_use_external_editor = false, view_source_function = null;
465 function browser_element_view_source(buffer, target, elem)
467     if (view_source_use_external_editor || view_source_function)
468     {
469         var spec = element_get_load_spec(elem);
470         if (spec == null) {
471             throw interactive_error("Element has no associated URL");
472             return;
473         }
475         let [file, temp] = yield download_as_temporary(spec,
476                                                        $buffer = buffer,
477                                                        $action = "View source");
478         if (view_source_use_external_editor)
479             yield open_file_with_external_editor(file, $temporary = temp);
480         else
481             yield view_source_function(file, $temporary = temp);
482         return;
483     }
485     var win = null;
486     var window = buffer.window;
487     if (elem.localName) {
488         switch (elem.localName.toLowerCase()) {
489         case "frame": case "iframe":
490             win = elem.contentWindow;
491             break;
492         case "math":
493             view_mathml_source (window, charset, elem);
494             return;
495         default:
496             throw new Error("Invalid browser element");
497         }
498     } else
499         win = elem;
500     win.focus();
502     var url_s = win.location.href;
503     if (url_s.substring (0,12) != "view-source:") {
504         try {
505             open_in_browser(buffer, target, "view-source:" + url_s);
506         } catch(e) { dump_error(e); }
507     } else {
508         window.minibuffer.message ("Already viewing source");
509     }
512 interactive("view-source", null, function (I) {
513     var target = I.browse_target("follow");
514     var element = yield I.read_browser_object("view_source", "View source", target);
515     yield browser_element_view_source(I.buffer, target, element);
518 interactive("shell-command-on-url", null, function (I) {
519     var cwd = I.cwd;
520     var element = yield I.read_browser_object("shell_command_url", "URL shell command");
521     var spec = element_get_load_spec(element);
522     if (spec == null)
523         throw interactive_error("Unable to obtain URI from element");
525     var uri = load_spec_uri_string(spec);
527     var panel;
528     panel = create_info_panel(I.window, "download-panel",
529                               [["downloading",
530                                 element_get_operation_label(element, "Running on", "URI"),
531                                 load_spec_uri_string(spec)],
532                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
534     try {
535         var cmd = yield I.minibuffer.read_shell_command(
536             $cwd = cwd,
537             $initial_value = load_spec_default_shell_command(spec));
538     } finally {
539         panel.destroy();
540     }
542     shell_command_with_argument_blind(cmd, uri, $cwd = cwd);
545 function browser_element_shell_command(buffer, elem, command) {
546     var spec = element_get_load_spec(elem);
547     if (spec == null) {
548         throw interactive_error("Element has no associated URL");
549         return;
550     }
551     yield download_as_temporary(spec,
552                                 $buffer = buffer,
553                                 $shell_command = command,
554                                 $shell_command_cwd = buffer.cwd);
557 interactive("shell-command-on-file", null, function (I) {
558     var cwd = I.cwd;
559     var element = yield I.read_browser_object("shell_command", "Shell command");
561     var spec = element_get_load_spec(element);
562     if (spec == null)
563         throw interactive_error("Unable to obtain URI from element");
565     var uri = load_spec_uri_string(spec);
567     var panel;
568     panel = create_info_panel(I.window, "download-panel",
569                               [["downloading",
570                                 element_get_operation_label(element, "Running on"),
571                                 load_spec_uri_string(spec)],
572                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
574     try {
576         var cmd = yield I.minibuffer.read_shell_command(
577             $cwd = cwd,
578             $initial_value = load_spec_default_shell_command(spec));
579     } finally {
580         panel.destroy();
581     }
583     /* FIXME: specify cwd as well */
584     yield browser_element_shell_command(I.buffer, element, cmd);
587 interactive("bookmark", null, function (I) {
588     var element = yield I.read_browser_object("bookmark", "Bookmark");
589     var spec = element_get_load_spec(element);
590     if (!spec)
591         throw interactive_error("Element has no associated URI");
592     var uri_string = load_spec_uri_string(spec);
593     var panel;
594     panel = create_info_panel(I.window, "bookmark-panel",
595                               [["bookmarking",
596                                 element_get_operation_label(element, "Bookmarking"),
597                                 uri_string]]);
598     try {
599         var title = yield I.minibuffer.read($prompt = "Bookmark with title:", $initial_value = load_spec_title(spec) || "");
600     } finally {
601         panel.destroy();
602     }
603     add_bookmark(uri_string, title);
604     I.minibuffer.message("Added bookmark: " + uri_string + " - " + title);
607 interactive("save-page", null, function (I) {
608     check_buffer(I.buffer, content_buffer);
609     var element = yield I.read_browser_object("save_page", "Save page");
610     var spec = element_get_load_spec(element);
611     if (!spec || !load_spec_document(spec))
612         throw interactive_error("Element is not associated with a document.");
613     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
615     var panel;
616     panel = create_info_panel(I.window, "download-panel",
617                               [["downloading",
618                                 element_get_operation_label(element, "Saving"),
619                                 load_spec_uri_string(spec)],
620                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
622     try {
623         var file = yield I.minibuffer.read_file_check_overwrite(
624             $prompt = "Save page as:",
625             $history = "save",
626             $initial_value = suggested_path);
627     } finally {
628         panel.destroy();
629     }
631     save_uri(spec, file, $buffer = I.buffer);
634 interactive("save-page-as-text", null, function (I) {
635     check_buffer(I.buffer, content_buffer);
636     var element = yield I.read_browser_object("save_page_as_text", "Save page as text");
637     var spec = element_get_load_spec(element);
638     var doc;
639     if (!spec || !(doc = load_spec_document(spec)))
640         throw interactive_error("Element is not associated with a document.");
641     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec, "txt"), I.buffer);
643     var panel;
644     panel = create_info_panel(I.window, "download-panel",
645                               [["downloading",
646                                 element_get_operation_label(element, "Saving", "as text"),
647                                 load_spec_uri_string(spec)],
648                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
650     try {
651         var file = yield I.minibuffer.read_file_check_overwrite(
652             $prompt = "Save page as text:",
653             $history = "save",
654             $initial_value = suggested_path);
655     } finally {
656         panel.destroy();
657     }
659     save_document_as_text(doc, file, $buffer = I.buffer);
662 interactive("save-page-complete", null, function (I) {
663     check_buffer(I.buffer, content_buffer);
664     var element = yield I.read_browser_object("save_page_complete", "Save page complete");
665     var spec = element_get_load_spec(element);
666     var doc;
667     if (!spec || !(doc = load_spec_document(spec)))
668         throw interactive_error("Element is not associated with a document.");
669     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
671     var panel;
672     panel = create_info_panel(I.window, "download-panel",
673                               [["downloading",
674                                 element_get_operation_label(element, "Saving complete"),
675                                 load_spec_uri_string(spec)],
676                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
678     try {
679         var file = yield I.minibuffer.read_file_check_overwrite(
680             $prompt = "Save page complete:",
681             $history = "save",
682             $initial_value = suggested_path);
683         // FIXME: use proper read function
684         var dir = yield I.minibuffer.read_file(
685             $prompt = "Data Directory:",
686             $history = "save",
687             $initial_value = file.path + ".support");
688     } finally {
689         panel.destroy();
690     }
692     save_document_complete(doc, file, dir, $buffer = I.buffer);
695 default_browse_targets["view-as-mime-type"] = [FOLLOW_CURRENT_FRAME, OPEN_CURRENT_BUFFER,
696                                                OPEN_NEW_BUFFER, OPEN_NEW_WINDOW];
697 interactive("view-as-mime-type",
698             "Display a browser object in the browser using the specified MIME type.",
699             function (I) {
700                 var element = yield I.read_browser_object("view_as_mime_type", "View in browser as mime type");
701                 var spec = element_get_load_spec(element);
703                 var target = I.browse_target("view-as-mime-type");
705                 if (!spec)
706                     throw interactive_error("Element is not associated with a URI");
708                 if (!can_override_mime_type_for_uri(load_spec_uri(spec)))
709                     throw interactive_error("Overriding the MIME type is not currently supported for non-HTTP URLs.");
711                 var panel;
713                 var mime_type = load_spec_mime_type(spec);
714                 panel = create_info_panel(I.window, "download-panel",
715                                           [["downloading",
716                                             element_get_operation_label(element, "View in browser"),
717                                             load_spec_uri_string(spec)],
718                                            ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
721                 try {
722                     let suggested_type = mime_type;
723                     if (gecko_viewable_mime_type_list.indexOf(suggested_type) == -1)
724                         suggested_type = "text/plain";
725                     mime_type = yield I.minibuffer.read_gecko_viewable_mime_type(
726                         $prompt = "View internally as",
727                         $initial_value = suggested_type,
728                         $select);
729                     override_mime_type_for_next_load(load_spec_uri(spec), mime_type);
730                     browser_element_follow(I.buffer, target, spec);
731                 } finally {
732                     panel.destroy();
733                 }
734             });