2 * (C) Copyright 2007-2008 John J. Foerch
3 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
5 * Portions of this file are derived from Vimperator,
6 * (C) Copyright 2006-2007 Martin Stubenschrott.
8 * Use, modification, and distribution are subject to the terms specified in the
14 require("mime-type-override.js");
15 require("minibuffer-read-mime-type.js");
17 var browser_object_classes
= {};
20 * handler is a coroutine called as: handler(buffer, prompt)
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(
32 $hint_xpath_expression
= xpath_expression
);
33 yield co_return(result
);
36 var base_obj
= browser_object_classes
[name
];
38 base_obj
= browser_object_classes
[name
] = {};
40 if (arguments
.$action
) {
41 name
= name
+ "/" + arguments
.$action
;
42 obj
= browser_object_classes
[name
];
44 obj
= browser_object_classes
[name
] = {__proto__
: 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
;
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
; },
61 define_browser_object_class("images",
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)
71 // only one frame (the top-level one), no need to use the hints system
72 yield co_return(buf
.top_frame
);
75 var result
= yield buf
.window
.minibuffer
.read_hinted_element(
78 $hint_xpath_expression
= "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
79 yield co_return(result
);
82 define_browser_object_class(
83 "links", $label
= "link",
85 "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
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
);
103 "default_browser_object_classes",
106 follow_top
: "frames",
110 view_source
: "frames",
113 save_page_complete
: "top",
114 save_page_as_text
: "frames",
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
) {
122 this._browser_object_class
||
123 this.get("default_browser_object_classes")[action_name
] ||
124 this.get("default_browser_object_classes")["default"];
128 function lookup_browser_object_class(class_name
, action
) {
130 if (action
!= null) {
131 obj
= browser_object_classes
[class_name
+ "/" + action
];
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
;
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
)
157 if (elem
instanceof Ci
.nsIDOMWindow
)
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.
167 function browser_set_element_focus(buffer
, elem
, prevent_scroll
) {
168 if (!is_dom_node_or_window(elem
))
171 buffer
.last_user_input_received
= Date
.now();
173 set_focus_no_scroll(buffer
.window
, elem
);
178 function browser_element_focus(buffer
, elem
)
180 if (!is_dom_node_or_window(elem
))
183 if (elem
instanceof Ci
.nsIDOMXULTextBoxElement
) {
184 // Focus the input field instead
185 elem
= elem
.wrappedJSObject
.inputField
;
188 browser_set_element_focus(buffer
, elem
);
189 if (elem
instanceof Ci
.nsIDOMWindow
) {
192 // If it is not a window, it must be an HTML element
195 if (elem
instanceof Ci
.nsIDOMHTMLFrameElement
|| elem
instanceof Ci
.nsIDOMHTMLIFrameElement
) {
196 elem
.contentWindow
.focus();
199 if (elem
instanceof Ci
.nsIDOMHTMLAreaElement
) {
200 var coords
= elem
.getAttribute("coords").split(",");
201 x
= Number(coords
[0]);
202 y
= Number(coords
[1]);
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
) {
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;
234 browser_follow_link_with_click(buffer
, elem
, x
, y
);
238 var spec
= element_get_load_spec(elem
);
240 throw interactive_error("Element has no associated URL");
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");
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
;
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
);
265 case FOLLOW_TOP_FRAME
:
266 case OPEN_CURRENT_BUFFER
:
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
,
275 $configuration
= buffer
.configuration
),
281 * Follow a link-like element by generating fake mouse events.
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
))
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
});
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
321 title
= elem
.title
|| elem
.textContent
;
323 else if (elem
instanceof Ci
.nsIDOMHTMLImageElement
) {
325 title
= elem
.title
|| elem
.alt
;
329 while (node
&& !(node
instanceof Ci
.nsIDOMHTMLAnchorElement
))
330 node
= node
.parentNode
;
331 if (node
&& !node
.hasAttribute("href"))
339 if (node
.nodeType
== Ci
.nsIDOMNode
.ELEMENT_NODE
) {
340 url
= linkNode
.getAttributeNS(XLINK_NS
, "href");
343 node
= node
.parentNode
;
346 url
= makeURLAbsolute(node
.baseURI
, url
);
347 title
= node
.title
|| node
.textContent
;
350 if (url
&& url
.length
> 0) {
351 if (title
&& title
.length
== 0)
353 spec
= load_spec({uri
: url
, source_frame
: elem
.ownerDocument
.defaultView
, title
: title
});
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
)
379 if (element
instanceof Ci
.nsIDOMHTMLFrameElement
)
381 if (element
instanceof Ci
.nsIDOMHTMLIFrameElement
)
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
;
394 suffix
= " " + 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
);
406 throw interactive_error("Element has no associated URI");
409 panel
= create_info_panel(I
.window
, "download-panel",
411 element_get_operation_label(element
, "Saving"),
412 load_spec_uri_string(spec
)],
413 ["mime-type", "Mime type:", load_spec_mime_type(spec
)]]);
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
),
430 function browser_element_copy(buffer
, elem
)
432 var spec
= element_get_load_spec(elem
);
435 text
= load_spec_uri_string(spec
);
437 if (!(elem
instanceof Ci
.nsIDOMNode
))
438 throw interactive_error("Element has no associated text to copy.");
439 switch (elem
.localName
) {
445 if (elem
.selectedIndex
>= 0)
446 text
= elem
.item(elem
.selectedIndex
).text
;
449 text
= elem
.textContent
;
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
)
469 var spec
= element_get_load_spec(elem
);
471 throw interactive_error("Element has no associated URL");
475 let [file
, temp
] = yield download_as_temporary(spec
,
477 $action
= "View source");
478 if (view_source_use_external_editor
)
479 yield open_file_with_external_editor(file
, $temporary
= temp
);
481 yield view_source_function(file
, $temporary
= temp
);
486 var window
= buffer
.window
;
487 if (elem
.localName
) {
488 switch (elem
.localName
.toLowerCase()) {
489 case "frame": case "iframe":
490 win
= elem
.contentWindow
;
493 view_mathml_source (window
, charset
, elem
);
496 throw new Error("Invalid browser element");
502 var url_s
= win
.location
.href
;
503 if (url_s
.substring (0,12) != "view-source:") {
505 open_in_browser(buffer
, target
, "view-source:" + url_s
);
506 } catch(e
) { dump_error(e
); }
508 window
.minibuffer
.message ("Already viewing source");
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
) {
520 var element
= yield I
.read_browser_object("shell_command_url", "URL shell command");
521 var spec
= element_get_load_spec(element
);
523 throw interactive_error("Unable to obtain URI from element");
525 var uri
= load_spec_uri_string(spec
);
528 panel
= create_info_panel(I
.window
, "download-panel",
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
)]]);
535 var cmd
= yield I
.minibuffer
.read_shell_command(
537 $initial_value
= load_spec_default_shell_command(spec
));
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
);
548 throw interactive_error("Element has no associated URL");
551 yield download_as_temporary(spec
,
553 $shell_command
= command
,
554 $shell_command_cwd
= buffer
.cwd
);
557 interactive("shell-command-on-file", null, function (I
) {
559 var element
= yield I
.read_browser_object("shell_command", "Shell command");
561 var spec
= element_get_load_spec(element
);
563 throw interactive_error("Unable to obtain URI from element");
565 var uri
= load_spec_uri_string(spec
);
568 panel
= create_info_panel(I
.window
, "download-panel",
570 element_get_operation_label(element
, "Running on"),
571 load_spec_uri_string(spec
)],
572 ["mime-type", "Mime type:", load_spec_mime_type(spec
)]]);
576 var cmd
= yield I
.minibuffer
.read_shell_command(
578 $initial_value
= load_spec_default_shell_command(spec
));
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
);
591 throw interactive_error("Element has no associated URI");
592 var uri_string
= load_spec_uri_string(spec
);
594 panel
= create_info_panel(I
.window
, "bookmark-panel",
596 element_get_operation_label(element
, "Bookmarking"),
599 var title
= yield I
.minibuffer
.read($prompt
= "Bookmark with title:", $initial_value
= load_spec_title(spec
) || "");
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
);
616 panel
= create_info_panel(I
.window
, "download-panel",
618 element_get_operation_label(element
, "Saving"),
619 load_spec_uri_string(spec
)],
620 ["mime-type", "Mime type:", load_spec_mime_type(spec
)]]);
623 var file
= yield I
.minibuffer
.read_file_check_overwrite(
624 $prompt
= "Save page as:",
626 $initial_value
= suggested_path
);
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
);
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
);
644 panel
= create_info_panel(I
.window
, "download-panel",
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
)]]);
651 var file
= yield I
.minibuffer
.read_file_check_overwrite(
652 $prompt
= "Save page as text:",
654 $initial_value
= suggested_path
);
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
);
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
);
672 panel
= create_info_panel(I
.window
, "download-panel",
674 element_get_operation_label(element
, "Saving complete"),
675 load_spec_uri_string(spec
)],
676 ["mime-type", "Mime type:", load_spec_mime_type(spec
)]]);
679 var file
= yield I
.minibuffer
.read_file_check_overwrite(
680 $prompt
= "Save page complete:",
682 $initial_value
= suggested_path
);
683 // FIXME: use proper read function
684 var dir
= yield I
.minibuffer
.read_file(
685 $prompt
= "Data Directory:",
687 $initial_value
= file
.path
+ ".support");
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.",
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");
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.");
713 var mime_type
= load_spec_mime_type(spec
);
714 panel
= create_info_panel(I
.window
, "download-panel",
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
)]]);
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
,
729 override_mime_type_for_next_load(load_spec_uri(spec
), mime_type
);
730 browser_element_follow(I
.buffer
, target
, spec
);