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");
250 case FOLLOW_CURRENT_FRAME:
251 var current_frame = load_spec_source_frame(spec);
252 if (current_frame && current_frame != buffer.top_frame) {
253 var target_obj = get_web_navigation_for_frame(current_frame);
254 apply_load_spec(target_obj, spec);
258 case FOLLOW_TOP_FRAME:
259 case OPEN_CURRENT_BUFFER:
262 case OPEN_NEW_WINDOW:
263 case OPEN_NEW_BUFFER:
264 case OPEN_NEW_BUFFER_BACKGROUND:
265 create_buffer(buffer.window,
266 buffer_creator(content_buffer,
268 $configuration = buffer.configuration),
274 * Follow a link-like element by generating fake mouse events.
276 function browser_follow_link_with_click(buffer, elem, x, y) {
277 var doc = elem.ownerDocument;
278 var view = doc.defaultView;
280 var evt = doc.createEvent("MouseEvents");
281 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
282 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
283 elem.dispatchEvent(evt);
285 evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
286 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
287 elem.dispatchEvent(evt);
290 function element_get_load_spec(elem) {
292 if (is_load_spec(elem))
297 if (elem instanceof Ci.nsIDOMWindow)
298 spec = load_spec({document: elem.document});
300 else if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
301 elem instanceof Ci.nsIDOMHTMLIFrameElement)
302 spec = load_spec({document: elem.contentDocument});
308 if (elem instanceof Ci.nsIDOMHTMLAnchorElement ||
309 elem instanceof Ci.nsIDOMHTMLAreaElement ||
310 elem instanceof Ci.nsIDOMHTMLLinkElement) {
311 if (!elem.hasAttribute("href"))
312 return null; // nothing can be done, as no nesting within these elements is allowed
314 title = elem.title || elem.textContent;
316 else if (elem instanceof Ci.nsIDOMHTMLImageElement) {
318 title = elem.title || elem.alt;
322 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
323 node = node.parentNode;
324 if (node && !node.hasAttribute("href"))
332 if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
333 url = linkNode.getAttributeNS(XLINK_NS, "href");
336 node = node.parentNode;
339 url = makeURLAbsolute(node.baseURI, url);
340 title = node.title || node.textContent;
343 if (url && url.length > 0) {
344 if (title && title.length == 0)
346 spec = load_spec({uri: url, source_frame: elem.ownerDocument.defaultView, title: title});
352 interactive("follow", null, function (I) {
353 var target = I.browse_target("follow");
354 var element = yield I.read_browser_object("follow", "Follow", target);
355 browser_element_follow(I.buffer, target, element);
358 interactive("follow-top", null, function (I) {
359 var target = I.browse_target("follow-top");
360 var element = yield I.read_browser_object("follow_top", "Follow", target);
361 browser_element_follow(I.buffer, target, element);
364 interactive("focus", null, function (I) {
365 var element = yield I.read_browser_object("focus", "Focus");
366 browser_element_focus(I.buffer, element);
369 function element_get_load_target_label(element) {
370 if (element instanceof Ci.nsIDOMWindow)
372 if (element instanceof Ci.nsIDOMHTMLFrameElement)
374 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
379 function element_get_operation_label(element, op_name, suffix) {
380 var target_label = element_get_load_target_label(element);
381 if (target_label != null)
382 target_label = " " + target_label;
387 suffix = " " + suffix;
391 return op_name + target_label + suffix + ":";
394 interactive("save", null, function (I) {
395 var element = yield I.read_browser_object("save", "Save");
397 var spec = element_get_load_spec(element);
399 throw interactive_error("Element has no associated URI");
402 panel = create_info_panel(I.window, "download-panel",
404 element_get_operation_label(element, "Saving"),
405 load_spec_uri_string(spec)],
406 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
409 var file = yield I.minibuffer.read_file_check_overwrite(
410 $prompt = "Save as:",
411 $initial_value = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer),
423 function browser_element_copy(buffer, elem)
425 var spec = element_get_load_spec(elem);
428 text = load_spec_uri_string(spec);
430 if (!(elem instanceof Ci.nsIDOMNode))
431 throw interactive_error("Element has no associated text to copy.");
432 switch (elem.localName) {
438 if (elem.selectedIndex >= 0)
439 text = elem.item(elem.selectedIndex).text;
442 text = elem.textContent;
446 browser_set_element_focus(buffer, elem);
447 writeToClipboard (text);
448 buffer.window.minibuffer.message ("Copied: " + text);
452 interactive("copy", null, function (I) {
453 var element = yield I.read_browser_object("copy", "Copy");
454 browser_element_copy(I.buffer, element);
457 var view_source_use_external_editor = false, view_source_function = null;
458 function browser_element_view_source(buffer, target, elem)
460 if (view_source_use_external_editor || view_source_function)
462 var spec = element_get_load_spec(elem);
464 throw interactive_error("Element has no associated URL");
468 let [file, temp] = yield download_as_temporary(spec,
470 $action = "View source");
471 if (view_source_use_external_editor)
472 yield open_file_with_external_editor(file, $temporary = temp);
474 yield view_source_function(file, $temporary = temp);
479 var window = buffer.window;
480 if (elem.localName) {
481 switch (elem.localName.toLowerCase()) {
482 case "frame": case "iframe":
483 win = elem.contentWindow;
486 view_mathml_source (window, charset, elem);
489 throw new Error("Invalid browser element");
495 var url_s = win.location.href;
496 if (url_s.substring (0,12) != "view-source:") {
498 open_in_browser(buffer, target, "view-source:" + url_s);
499 } catch(e) { dump_error(e); }
501 window.minibuffer.message ("Already viewing source");
505 interactive("view-source", null, function (I) {
506 var target = I.browse_target("follow");
507 var element = yield I.read_browser_object("view_source", "View source", target);
508 yield browser_element_view_source(I.buffer, target, element);
511 interactive("shell-command-on-url", null, function (I) {
513 var element = yield I.read_browser_object("shell_command_url", "URL shell command");
514 var spec = element_get_load_spec(element);
516 throw interactive_error("Unable to obtain URI from element");
518 var uri = load_spec_uri_string(spec);
521 panel = create_info_panel(I.window, "download-panel",
523 element_get_operation_label(element, "Running on", "URI"),
524 load_spec_uri_string(spec)],
525 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
528 var cmd = yield I.minibuffer.read_shell_command(
530 $initial_value = load_spec_default_shell_command(spec));
535 shell_command_with_argument_blind(cmd, uri, $cwd = cwd);
538 function browser_element_shell_command(buffer, elem, command) {
539 var spec = element_get_load_spec(elem);
541 throw interactive_error("Element has no associated URL");
544 yield download_as_temporary(spec,
546 $shell_command = command,
547 $shell_command_cwd = buffer.cwd);
550 interactive("shell-command-on-file", null, function (I) {
552 var element = yield I.read_browser_object("shell_command", "Shell command");
554 var spec = element_get_load_spec(element);
556 throw interactive_error("Unable to obtain URI from element");
558 var uri = load_spec_uri_string(spec);
561 panel = create_info_panel(I.window, "download-panel",
563 element_get_operation_label(element, "Running on"),
564 load_spec_uri_string(spec)],
565 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
569 var cmd = yield I.minibuffer.read_shell_command(
571 $initial_value = load_spec_default_shell_command(spec));
576 /* FIXME: specify cwd as well */
577 yield browser_element_shell_command(I.buffer, element, cmd);
580 interactive("bookmark", null, function (I) {
581 var element = yield I.read_browser_object("bookmark", "Bookmark");
582 var spec = element_get_load_spec(element);
584 throw interactive_error("Element has no associated URI");
585 var uri_string = load_spec_uri_string(spec);
587 panel = create_info_panel(I.window, "bookmark-panel",
589 element_get_operation_label(element, "Bookmarking"),
592 var title = yield I.minibuffer.read($prompt = "Bookmark with title:", $initial_value = load_spec_title(spec) || "");
596 add_bookmark(uri_string, title);
597 I.minibuffer.message("Added bookmark: " + uri_string + " - " + title);
600 interactive("save-page", null, function (I) {
601 check_buffer(I.buffer, content_buffer);
602 var element = yield I.read_browser_object("save_page", "Save page");
603 var spec = element_get_load_spec(element);
604 if (!spec || !load_spec_document(spec))
605 throw interactive_error("Element is not associated with a document.");
606 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
609 panel = create_info_panel(I.window, "download-panel",
611 element_get_operation_label(element, "Saving"),
612 load_spec_uri_string(spec)],
613 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
616 var file = yield I.minibuffer.read_file_check_overwrite(
617 $prompt = "Save page as:",
619 $initial_value = suggested_path);
624 save_uri(spec, file, $buffer = I.buffer);
627 interactive("save-page-as-text", null, function (I) {
628 check_buffer(I.buffer, content_buffer);
629 var element = yield I.read_browser_object("save_page_as_text", "Save page as text");
630 var spec = element_get_load_spec(element);
632 if (!spec || !(doc = load_spec_document(spec)))
633 throw interactive_error("Element is not associated with a document.");
634 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec, "txt"), I.buffer);
637 panel = create_info_panel(I.window, "download-panel",
639 element_get_operation_label(element, "Saving", "as text"),
640 load_spec_uri_string(spec)],
641 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
644 var file = yield I.minibuffer.read_file_check_overwrite(
645 $prompt = "Save page as text:",
647 $initial_value = suggested_path);
652 save_document_as_text(doc, file, $buffer = I.buffer);
655 interactive("save-page-complete", null, function (I) {
656 check_buffer(I.buffer, content_buffer);
657 var element = yield I.read_browser_object("save_page_complete", "Save page complete");
658 var spec = element_get_load_spec(element);
660 if (!spec || !(doc = load_spec_document(spec)))
661 throw interactive_error("Element is not associated with a document.");
662 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
665 panel = create_info_panel(I.window, "download-panel",
667 element_get_operation_label(element, "Saving complete"),
668 load_spec_uri_string(spec)],
669 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
672 var file = yield I.minibuffer.read_file_check_overwrite(
673 $prompt = "Save page complete:",
675 $initial_value = suggested_path);
676 // FIXME: use proper read function
677 var dir = yield I.minibuffer.read_file(
678 $prompt = "Data Directory:",
680 $initial_value = file.path + ".support");
685 save_document_complete(doc, file, dir, $buffer = I.buffer);
688 default_browse_targets["view-as-mime-type"] = [FOLLOW_CURRENT_FRAME, OPEN_CURRENT_BUFFER,
689 OPEN_NEW_BUFFER, OPEN_NEW_WINDOW];
690 interactive("view-as-mime-type",
691 "Display a browser object in the browser using the specified MIME type.",
693 var element = yield I.read_browser_object("view_as_mime_type", "View in browser as mime type");
694 var spec = element_get_load_spec(element);
696 var target = I.browse_target("view-as-mime-type");
699 throw interactive_error("Element is not associated with a URI");
701 if (!can_override_mime_type_for_uri(load_spec_uri(spec)))
702 throw interactive_error("Overriding the MIME type is not currently supported for non-HTTP URLs.");
706 var mime_type = load_spec_mime_type(spec);
707 panel = create_info_panel(I.window, "download-panel",
709 element_get_operation_label(element, "View in browser"),
710 load_spec_uri_string(spec)],
711 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
715 let suggested_type = mime_type;
716 if (gecko_viewable_mime_type_list.indexOf(suggested_type) == -1)
717 suggested_type = "text/plain";
718 mime_type = yield I.minibuffer.read_gecko_viewable_mime_type(
719 $prompt = "View internally as",
720 $initial_value = suggested_type,
722 override_mime_type_for_next_load(load_spec_uri(spec), mime_type);
723 browser_element_follow(I.buffer, target, spec);