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
15 var browser_object_classes = {};
18 * handler is a coroutine called as: handler(buffer, prompt)
20 define_keywords("$doc", "$action", "$label", "$handler", "$xpath_expression");
21 function define_browser_object_class(name) {
22 keywords(arguments, $xpath_expression = undefined);
23 var handler = arguments.$handler;
24 let xpath_expression = arguments.$xpath_expression;
25 if (handler === undefined && xpath_expression != undefined) {
26 handler = function (buf, prompt) {
27 var result = yield buf.window.minibuffer.read_hinted_element(
30 $hint_xpath_expression = xpath_expression);
31 yield co_return(result);
34 var base_obj = browser_object_classes[name];
36 base_obj = browser_object_classes[name] = {};
38 if (arguments.$action) {
39 name = name + "/" + arguments.$action;
40 obj = browser_object_classes[name];
42 obj = browser_object_classes[name] = {__proto__: base_obj};
45 if (arguments.$label !== undefined)
46 obj.label = arguments.$label;
47 if (arguments.$doc !== undefined)
48 obj.doc = arguments.$doc;
49 if (handler !== undefined)
50 obj.handler = handler;
53 define_browser_object_class("images",
55 $xpath_expression = "//img | //xhtml:img");
57 define_browser_object_class("frames", $label = "frame", $handler = function (buf, prompt) {
58 check_buffer(buf, content_buffer);
59 var doc = buf.document;
60 if (doc.getElementsByTagName("frame").length == 0 &&
61 doc.getElementsByTagName("iframe").length == 0)
63 // only one frame (the top-level one), no need to use the hints system
64 yield co_return(buf.top_frame);
67 var result = yield buf.window.minibuffer.read_hinted_element(
70 $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
71 yield co_return(result);
74 define_browser_object_class(
75 "links", $label = "link",
77 "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
79 "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " +
80 "//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand] | " +
81 "//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | " +
82 "//xhtml:button | //xhtml:select");
84 define_browser_object_class("mathml", $label = "MathML element", $xpath_expression = "//m:math");
86 define_browser_object_class("top", $handler = function (buf, prompt) { yield co_return(buf.top_frame); });
88 define_browser_object_class("url", $handler = function (buf, prompt) {
89 check_buffer (buf, content_buffer);
90 var result = yield buf.window.minibuffer.read_url ($prompt = prompt);
91 yield co_return (result);
95 "default_browser_object_classes",
102 view_source: "frames",
105 save_page_complete: "top",
106 save_page_as_text: "frames",
109 "Specifies the default object class for each operation.\n" +
110 "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.");
112 interactive_context.prototype.browser_object_class = function (action_name) {
114 this._browser_object_class ||
115 this.get("default_browser_object_classes")[action_name] ||
116 this.get("default_browser_object_classes")["default"];
120 function browser_object_class_selector(name) {
121 return function (ctx, active_keymap, overlay_keymap, top_keymap) {
122 ctx._browser_object_class = name;
123 ctx.overlay_keymap = top_keymap;
127 function lookup_browser_object_class(class_name, action) {
129 if (action != null) {
130 obj = browser_object_classes[class_name + "/" + action];
134 return browser_object_classes[class_name];
137 interactive_context.prototype.read_browser_object = function(action, action_name, target)
139 var object_class_name = this.browser_object_class(action);
140 var object_class = lookup_browser_object_class(object_class_name, action);
142 var prompt = action_name;
143 var label = object_class.label || object_class_name;
145 prompt += TARGET_PROMPTS[target];
146 prompt += " (select " + label + "):";
148 var result = yield object_class.handler.call(null, this.buffer, prompt);
149 yield co_return(result);
153 function is_dom_node_or_window(elem) {
154 if (elem instanceof Ci.nsIDOMNode)
156 if (elem instanceof Ci.nsIDOMWindow)
162 * This is a simple wrapper function that sets focus to elem, and
163 * bypasses the automatic focus prevention system, which might
164 * otherwise prevent this from happening.
166 function browser_set_element_focus(buffer, elem, prevent_scroll) {
167 if (!is_dom_node_or_window(elem))
170 buffer.last_user_input_received = Date.now();
172 set_focus_no_scroll(buffer.window, elem);
177 function browser_element_focus(buffer, elem)
179 if (!is_dom_node_or_window(elem))
182 if (elem instanceof Ci.nsIDOMXULTextBoxElement) {
183 // Focus the input field instead
184 elem = elem.wrappedJSObject.inputField;
187 browser_set_element_focus(buffer, elem);
188 if (elem instanceof Ci.nsIDOMWindow) {
191 // If it is not a window, it must be an HTML element
194 if (elem instanceof Ci.nsIDOMHTMLFrameElement || elem instanceof Ci.nsIDOMHTMLIFrameElement) {
195 elem.contentWindow.focus();
198 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
199 var coords = elem.getAttribute("coords").split(",");
200 x = Number(coords[0]);
201 y = Number(coords[1]);
204 var doc = elem.ownerDocument;
205 var evt = doc.createEvent("MouseEvents");
206 var doc = elem.ownerDocument;
208 evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
209 elem.dispatchEvent(evt);
212 function browser_element_follow(buffer, target, elem)
214 browser_set_element_focus(buffer, elem, true /* no scroll */);
216 var no_click = (is_load_spec(elem) ||
217 (elem instanceof Ci.nsIDOMWindow) ||
218 (elem instanceof Ci.nsIDOMHTMLFrameElement) ||
219 (elem instanceof Ci.nsIDOMHTMLIFrameElement) ||
220 (elem instanceof Ci.nsIDOMHTMLLinkElement) ||
221 (elem instanceof Ci.nsIDOMHTMLImageElement &&
222 !elem.hasAttribute("onmousedown") && !elem.hasAttribute("onclick")));
224 if (target == FOLLOW_DEFAULT && !no_click) {
226 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
227 var coords = elem.getAttribute("coords").split(",");
228 if (coords.length >= 2) {
229 x = Number(coords[0]) + 1;
230 y = Number(coords[1]) + 1;
233 browser_follow_link_with_click(buffer, elem, x, y);
237 var spec = element_get_load_spec(elem);
239 throw interactive_error("Element has no associated URL");
243 if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
244 // This URL won't work
245 throw interactive_error("Can't load javascript URL");
249 case FOLLOW_CURRENT_FRAME:
250 var current_frame = load_spec_source_frame(spec);
251 if (current_frame && current_frame != buffer.top_frame) {
252 var target_obj = get_web_navigation_for_frame(current_frame);
253 apply_load_spec(target_obj, spec);
257 case FOLLOW_TOP_FRAME:
258 case OPEN_CURRENT_BUFFER:
261 case OPEN_NEW_WINDOW:
262 case OPEN_NEW_BUFFER:
263 case OPEN_NEW_BUFFER_BACKGROUND:
264 create_buffer(buffer.window,
265 buffer_creator(content_buffer,
267 $configuration = buffer.configuration),
273 * Follow a link-like element by generating fake mouse events.
275 function browser_follow_link_with_click(buffer, elem, x, y) {
276 var doc = elem.ownerDocument;
277 var view = doc.defaultView;
279 var evt = doc.createEvent("MouseEvents");
280 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
281 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
282 elem.dispatchEvent(evt);
284 evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
285 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
286 elem.dispatchEvent(evt);
289 function element_get_load_spec(elem) {
291 if (is_load_spec(elem))
296 if (elem instanceof Ci.nsIDOMWindow)
297 spec = load_spec({document: elem.document});
299 else if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
300 elem instanceof Ci.nsIDOMHTMLIFrameElement)
301 spec = load_spec({document: elem.contentDocument});
307 if (elem instanceof Ci.nsIDOMHTMLAnchorElement ||
308 elem instanceof Ci.nsIDOMHTMLAreaElement ||
309 elem instanceof Ci.nsIDOMHTMLLinkElement) {
310 if (!elem.hasAttribute("href"))
311 return null; // nothing can be done, as no nesting within these elements is allowed
313 title = elem.title || elem.textContent;
315 else if (elem instanceof Ci.nsIDOMHTMLImageElement) {
317 title = elem.title || elem.alt;
321 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
322 node = node.parentNode;
323 if (node && !node.hasAttribute("href"))
331 if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
332 url = linkNode.getAttributeNS(XLINK_NS, "href");
335 node = node.parentNode;
338 url = makeURLAbsolute(node.baseURI, url);
339 title = node.title || node.textContent;
342 if (url && url.length > 0) {
343 if (title && title.length == 0)
345 spec = load_spec({uri: url, source_frame: elem.ownerDocument.defaultView, title: title});
351 interactive("follow", function (I) {
352 var target = I.browse_target("follow");
353 var element = yield I.read_browser_object("follow", "Follow", target);
354 browser_element_follow(I.buffer, target, element);
357 interactive("follow-top", function (I) {
358 var target = I.browse_target("follow-top");
359 var element = yield I.read_browser_object("follow_top", "Follow", target);
360 browser_element_follow(I.buffer, target, element);
363 interactive("focus", function (I) {
364 var element = yield I.read_browser_object("focus", "Focus");
365 browser_element_focus(I.buffer, element);
368 function element_get_load_target_label(element) {
369 if (element instanceof Ci.nsIDOMWindow)
371 if (element instanceof Ci.nsIDOMHTMLFrameElement)
373 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
378 function element_get_operation_label(element, op_name, suffix) {
379 var target_label = element_get_load_target_label(element);
380 if (target_label != null)
381 target_label = " " + target_label;
386 suffix = " " + suffix;
390 return op_name + target_label + suffix + ":";
393 interactive("save", function (I) {
394 var element = yield I.read_browser_object("save", "Save");
396 var spec = element_get_load_spec(element);
398 throw interactive_error("Element has no associated URI");
401 panel = create_info_panel(I.window, "download-panel",
403 element_get_operation_label(element, "Saving"),
404 load_spec_uri_string(spec)],
405 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
408 var file = yield I.minibuffer.read_file_check_overwrite(
409 $prompt = "Save as:",
410 $initial_value = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer),
422 function browser_element_copy(buffer, elem)
424 var spec = element_get_load_spec(elem);
427 text = load_spec_uri_string(spec);
429 if (!(elem instanceof Ci.nsIDOMNode))
430 throw interactive_error("Element has no associated text to copy.");
431 switch (elem.localName) {
437 if (elem.selectedIndex >= 0)
438 text = elem.item(elem.selectedIndex).text;
441 text = elem.textContent;
445 writeToClipboard (text);
446 buffer.window.minibuffer.message ("Copied: " + text);
450 interactive("copy", function (I) {
451 var element = yield I.read_browser_object("copy", "Copy");
452 browser_element_copy(I.buffer, element);
455 var view_source_use_external_editor = false, view_source_function = null;
456 function browser_element_view_source(buffer, target, elem)
458 if (view_source_use_external_editor || view_source_function)
460 var spec = element_get_load_spec(elem);
462 throw interactive_error("Element has no associated URL");
466 let [file, temp] = yield download_as_temporary(spec,
468 $action = "View source");
469 if (view_source_use_external_editor)
470 yield open_file_with_external_editor(file, $temporary = temp);
472 yield view_source_function(file, $temporary = temp);
477 var window = buffer.window;
478 if (elem.localName) {
479 switch (elem.localName.toLowerCase()) {
480 case "frame": case "iframe":
481 win = elem.contentWindow;
484 view_mathml_source (window, charset, elem);
487 throw new Error("Invalid browser element");
493 var url_s = win.location.href;
494 if (url_s.substring (0,12) != "view-source:") {
496 open_in_browser(buffer, target, "view-source:" + url_s);
497 } catch(e) { dump_error(e); }
499 window.minibuffer.message ("Already viewing source");
503 interactive("view-source", function (I) {
504 var target = I.browse_target("follow");
505 var element = yield I.read_browser_object("view_source", "View source", target);
506 yield browser_element_view_source(I.buffer, target, element);
509 interactive("shell-command-on-url", function (I) {
511 var element = yield I.read_browser_object("shell_command_url", "URL shell command");
512 var spec = element_get_load_spec(element);
514 throw interactive_error("Unable to obtain URI from element");
516 var uri = load_spec_uri_string(spec);
519 panel = create_info_panel(I.window, "download-panel",
521 element_get_operation_label(element, "Running on", "URI"),
522 load_spec_uri_string(spec)],
523 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
526 var cmd = yield I.minibuffer.read_shell_command(
528 $initial_value = load_spec_default_shell_command(spec));
533 shell_command_with_argument_blind(cmd, uri, $cwd = cwd);
536 function browser_element_shell_command(buffer, elem, command) {
537 var spec = element_get_load_spec(elem);
539 throw interactive_error("Element has no associated URL");
542 yield download_as_temporary(spec,
544 $shell_command = command,
545 $shell_command_cwd = buffer.cwd);
548 interactive("shell-command-on-file", function (I) {
550 var element = yield I.read_browser_object("shell_command", "Shell command");
552 var spec = element_get_load_spec(element);
554 throw interactive_error("Unable to obtain URI from element");
556 var uri = load_spec_uri_string(spec);
559 panel = create_info_panel(I.window, "download-panel",
561 element_get_operation_label(element, "Running on"),
562 load_spec_uri_string(spec)],
563 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
567 var cmd = yield I.minibuffer.read_shell_command(
569 $initial_value = load_spec_default_shell_command(spec));
574 /* FIXME: specify cwd as well */
575 yield browser_element_shell_command(I.buffer, element, cmd);
578 interactive("bookmark", function (I) {
579 var element = yield I.read_browser_object("bookmark", "Bookmark");
580 var spec = element_get_load_spec(element);
582 throw interactive_error("Element has no associated URI");
583 var uri_string = load_spec_uri_string(spec);
585 panel = create_info_panel(I.window, "bookmark-panel",
587 element_get_operation_label(element, "Bookmarking"),
590 var title = yield I.minibuffer.read($prompt = "Bookmark with title:", $initial_value = load_spec_title(spec) || "");
594 add_bookmark(uri_string, title);
595 I.minibuffer.message("Added bookmark: " + uri_string + " - " + title);
598 interactive("save-page", function (I) {
599 check_buffer(I.buffer, content_buffer);
600 var element = yield I.read_browser_object("save_page", "Save page");
601 var spec = element_get_load_spec(element);
602 if (!spec || !load_spec_document(spec))
603 throw interactive_error("Element is not associated with a document.");
604 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
607 panel = create_info_panel(I.window, "download-panel",
609 element_get_operation_label(element, "Saving"),
610 load_spec_uri_string(spec)],
611 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
614 var file = yield I.minibuffer.read_file_check_overwrite(
615 $prompt = "Save page as:",
617 $initial_value = suggested_path);
622 save_uri(spec, file, $buffer = I.buffer);
625 interactive("save-page-as-text", function (I) {
626 check_buffer(I.buffer, content_buffer);
627 var element = yield I.read_browser_object("save_page_as_text", "Save page as text");
628 var spec = element_get_load_spec(element);
630 if (!spec || !(doc = load_spec_document(spec)))
631 throw interactive_error("Element is not associated with a document.");
632 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec, "txt"), I.buffer);
635 panel = create_info_panel(I.window, "download-panel",
637 element_get_operation_label(element, "Saving", "as text"),
638 load_spec_uri_string(spec)],
639 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
642 var file = yield I.minibuffer.read_file_check_overwrite(
643 $prompt = "Save page as text:",
645 $initial_value = suggested_path);
650 save_document_as_text(doc, file, $buffer = I.buffer);
653 interactive("save-page-complete", function (I) {
654 check_buffer(I.buffer, content_buffer);
655 var element = yield I.read_browser_object("save_page_complete", "Save page complete");
656 var spec = element_get_load_spec(element);
658 if (!spec || !(doc = load_spec_document(spec)))
659 throw interactive_error("Element is not associated with a document.");
660 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
663 panel = create_info_panel(I.window, "download-panel",
665 element_get_operation_label(element, "Saving complete"),
666 load_spec_uri_string(spec)],
667 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
670 var file = yield I.minibuffer.read_file_check_overwrite(
671 $prompt = "Save page complete:",
673 $initial_value = suggested_path);
674 // FIXME: use proper read function
675 var dir = yield I.minibuffer.read_file(
676 $prompt = "Data Directory:",
678 $initial_value = file.path + ".support");
683 save_document_complete(doc, file, dir, $buffer = I.buffer);