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", $xpath_expression = "//img | //xhtml:img");
55 define_browser_object_class("frames", $handler = function (buf, prompt) {
56 check_buffer(buf, content_buffer);
57 var doc = buf.document;
58 if (doc.getElementsByTagName("frame").length == 0 &&
59 doc.getElementsByTagName("iframe").length == 0)
61 // only one frame (the top-level one), no need to use the hints system
62 yield co_return(buf.top_frame);
65 var result = yield buf.window.minibuffer.read_hinted_element(
68 $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
69 yield co_return(result);
72 define_browser_object_class(
75 "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
76 "@class='lk' or @class='s' or @role='link'] | " +
77 "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " +
78 "//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " +
79 "//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | " +
80 "//xhtml:button | //xhtml:select");
82 define_browser_object_class("mathml", $label = "MathML", $xpath_expression = "//m:math");
84 define_browser_object_class("top", $handler = function (buf, prompt) { yield co_return(buf.top_frame); });
88 "default_browser_object_classes",
95 view_source: "frames",
98 save_page_complete: "top",
99 save_page_as_text: "frames",
102 "Specifies the default object class for each operation.\n" +
103 "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.");
105 interactive_context.prototype.browser_object_class = function (action_name) {
107 this._browser_object_class ||
108 this.get("default_browser_object_classes")[action_name] ||
109 this.get("default_browser_object_classes")["default"];
113 function browser_object_class_selector(name) {
114 return function (ctx, active_keymap, overlay_keymap) {
115 ctx._browser_object_class = name;
116 ctx.overlay_keymap = overlay_keymap || active_keymap;
120 function lookup_browser_object_class(class_name, action) {
122 if (action != null) {
123 obj = browser_object_classes[class_name + "/" + action];
127 return browser_object_classes[class_name];
130 interactive_context.prototype.read_browser_object = function(action, action_name, default_class, target)
132 var object_class_name = this.browser_object_class(action);
133 var object_class = lookup_browser_object_class(object_class_name, action);
135 var prompt = action_name;
137 prompt += TARGET_PROMPTS[target];
138 if (object_class_name != default_class) {
139 var label = object_class.label || object_class_name;
140 prompt += " (" + label + ")";
144 var result = yield object_class.handler.call(null, this.buffer, prompt);
145 yield co_return(result);
149 function is_dom_node_or_window(elem) {
150 if (elem instanceof Ci.nsIDOMNode)
152 if (elem instanceof Ci.nsIDOMWindow)
158 * This is a simple wrapper function that sets focus to elem, and
159 * bypasses the automatic focus prevention system, which might
160 * otherwise prevent this from happening.
162 function browser_set_element_focus(buffer, elem, prevent_scroll) {
163 if (!is_dom_node_or_window(elem))
166 buffer.last_user_input_received = Date.now();
168 set_focus_no_scroll(buffer.window, elem);
173 function browser_element_focus(buffer, elem)
175 if (!is_dom_node_or_window(elem))
178 if (elem instanceof Ci.nsIDOMXULTextBoxElement) {
179 // Focus the input field instead
180 elem = elem.wrappedJSObject.inputField;
183 browser_set_element_focus(buffer, elem);
184 if (elem instanceof Ci.nsIDOMWindow) {
187 // If it is not a window, it must be an HTML element
190 if (elem instanceof Ci.nsIDOMHTMLFrameElement || elem instanceof Ci.nsIDOMHTMLIFrameElement) {
191 elem.contentWindow.focus();
194 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
195 var coords = elem.getAttribute("coords").split(",");
196 x = Number(coords[0]);
197 y = Number(coords[1]);
200 var doc = elem.ownerDocument;
201 var evt = doc.createEvent("MouseEvents");
202 var doc = elem.ownerDocument;
204 evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
205 elem.dispatchEvent(evt);
208 function browser_element_follow(buffer, target, elem)
210 browser_set_element_focus(buffer, elem, true /* no scroll */);
212 var no_click = (is_load_spec(elem) ||
213 (elem instanceof Ci.nsIDOMWindow) ||
214 (elem instanceof Ci.nsIDOMHTMLFrameElement) ||
215 (elem instanceof Ci.nsIDOMHTMLIFrameElement) ||
216 (elem instanceof Ci.nsIDOMHTMLLinkElement) ||
217 (elem instanceof Ci.nsIDOMHTMLImageElement &&
218 !elem.hasAttribute("onmousedown") && !elem.hasAttribute("onclick")));
220 if (target == FOLLOW_DEFAULT && !no_click) {
222 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
223 var coords = elem.getAttribute("coords").split(",");
224 if (coords.length >= 2) {
225 x = Number(coords[0]) + 1;
226 y = Number(coords[1]) + 1;
229 browser_follow_link_with_click(buffer, elem, x, y);
233 var spec = element_get_load_spec(elem);
235 throw interactive_error("Element has no associated URL");
239 if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
240 // This URL won't work
241 throw interactive_error("Can't load javascript URL");
245 case FOLLOW_CURRENT_FRAME:
246 var current_frame = load_spec_source_frame(spec);
247 if (current_frame && current_frame != buffer.top_frame) {
248 var target_obj = get_web_navigation_for_frame(current_frame);
249 apply_load_spec(target_obj, spec);
253 case FOLLOW_TOP_FRAME:
254 case OPEN_CURRENT_BUFFER:
257 case OPEN_NEW_WINDOW:
258 case OPEN_NEW_BUFFER:
259 case OPEN_NEW_BUFFER_BACKGROUND:
260 create_buffer(buffer.window,
261 buffer_creator(content_buffer,
263 $configuration = buffer.configuration),
269 * Follow a link-like element by generating fake mouse events.
271 function browser_follow_link_with_click(buffer, elem, x, y) {
272 var doc = elem.ownerDocument;
273 var view = doc.defaultView;
275 var evt = doc.createEvent("MouseEvents");
276 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
277 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
278 elem.dispatchEvent(evt);
280 evt.initMouseEvent("click", 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);
285 function element_get_load_spec(elem) {
287 if (is_load_spec(elem))
292 if (elem instanceof Ci.nsIDOMWindow)
293 spec = load_spec({document: elem.document});
295 else if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
296 elem instanceof Ci.nsIDOMHTMLIFrameElement)
297 spec = load_spec({document: elem.contentDocument});
303 if (elem instanceof Ci.nsIDOMHTMLAnchorElement ||
304 elem instanceof Ci.nsIDOMHTMLAreaElement ||
305 elem instanceof Ci.nsIDOMHTMLLinkElement) {
306 if (!elem.hasAttribute("href"))
307 return null; // nothing can be done, as no nesting within these elements is allowed
309 title = elem.title || elem.textContent;
311 else if (elem instanceof Ci.nsIDOMHTMLImageElement) {
313 title = elem.title || elem.alt;
317 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
318 node = node.parentNode;
319 if (node && !node.hasAttribute("href"))
327 if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
328 url = linkNode.getAttributeNS(XLINK_NS, "href");
331 node = node.parentNode;
334 url = makeURLAbsolute(node.baseURI, url);
335 title = node.title || node.textContent;
338 if (url && url.length > 0) {
339 if (title && title.length == 0)
341 spec = load_spec({uri: url, source_frame: elem.ownerDocument.defaultView, title: title});
347 interactive("follow", function (I) {
348 var target = I.browse_target("follow");
349 var element = yield I.read_browser_object("follow", "Follow", "links", target);
350 browser_element_follow(I.buffer, target, element);
353 interactive("follow-top", function (I) {
354 var target = I.browse_target("follow-top");
355 var element = yield I.read_browser_object("follow_top", "Follow", "links", target);
356 browser_element_follow(I.buffer, target, element);
359 interactive("focus", function (I) {
360 var element = yield I.read_browser_object("focus", "Focus");
361 browser_element_focus(I.buffer, element);
364 function element_get_load_target_label(element) {
365 if (element instanceof Ci.nsIDOMWindow)
367 if (element instanceof Ci.nsIDOMHTMLFrameElement)
369 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
374 function element_get_operation_label(element, op_name, suffix) {
375 var target_label = element_get_load_target_label(element);
376 if (target_label != null)
377 target_label = " " + target_label;
382 suffix = " " + suffix;
386 return op_name + target_label + suffix + ":";
389 interactive("save", function (I) {
390 var element = yield I.read_browser_object("save", "Save", "links");
392 var spec = element_get_load_spec(element);
394 throw interactive_error("Element has no associated URI");
397 panel = create_info_panel(I.window, "download-panel",
399 element_get_operation_label(element, "Saving"),
400 load_spec_uri_string(spec)],
401 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
404 var file = yield I.minibuffer.read_file_check_overwrite(
405 $prompt = "Save as:",
406 $initial_value = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer),
418 function browser_element_copy(buffer, elem)
420 var spec = element_get_load_spec(elem);
423 text = load_spec_uri_string(spec);
425 if (!(elem instanceof Ci.nsIDOMNode))
426 throw interactive_error("Element has no associated text to copy.");
427 switch (elem.localName) {
433 if (elem.selectedIndex >= 0)
434 text = elem.item(elem.selectedIndex).text;
437 text = elem.textContent;
441 writeToClipboard (text);
442 buffer.window.minibuffer.message ("Copied: " + text);
446 interactive("copy", function (I) {
447 var element = yield I.read_browser_object("copy", "Copy", "links");
448 browser_element_copy(I.buffer, element);
451 var view_source_use_external_editor = false, view_source_function = null;
452 function browser_element_view_source(buffer, target, elem)
454 if (view_source_use_external_editor || view_source_function)
456 var spec = element_get_load_spec(elem);
458 throw interactive_error("Element has no associated URL");
462 let [file, temp] = yield download_as_temporary(spec,
464 $action = "View source");
465 if (view_source_use_external_editor)
466 yield open_file_with_external_editor(file, $temporary = temp);
468 yield view_source_function(file, $temporary = temp);
473 var window = buffer.window;
474 if (elem.localName) {
475 switch (elem.localName.toLowerCase()) {
476 case "frame": case "iframe":
477 win = elem.contentWindow;
480 view_mathml_source (window, charset, elem);
483 throw new Error("Invalid browser element");
489 var url_s = win.location.href;
490 if (url_s.substring (0,12) != "view-source:") {
492 open_in_browser(buffer, target, "view-source:" + url_s);
493 } catch(e) { dump_error(e); }
495 window.minibuffer.message ("Already viewing source");
499 interactive("view-source", function (I) {
500 var target = I.browse_target("follow");
501 var element = yield I.read_browser_object("view_source", "View source", "frames", target);
502 yield browser_element_view_source(I.buffer, target, element);
505 interactive("shell-command-on-url", function (I) {
507 var element = yield I.read_browser_object("shell_command_url", "URL shell command target", "links");
508 var spec = element_get_load_spec(element);
510 throw interactive_error("Unable to obtain URI from element");
512 var uri = load_spec_uri_string(spec);
515 panel = create_info_panel(I.window, "download-panel",
517 element_get_operation_label(element, "Running on", "URI"),
518 load_spec_uri_string(spec)],
519 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
522 var cmd = yield I.minibuffer.read_shell_command(
524 $initial_value = load_spec_default_shell_command(spec));
529 shell_command_with_argument_blind(cmd, uri, $cwd = cwd);
532 function browser_element_shell_command(buffer, elem, command) {
533 var spec = element_get_load_spec(elem);
535 throw interactive_error("Element has no associated URL");
538 yield download_as_temporary(spec,
540 $shell_command = command,
541 $shell_command_cwd = buffer.cwd);
544 interactive("shell-command-on-file", function (I) {
546 var element = yield I.read_browser_object("shell_command", "Shell command target", "links");
548 var spec = element_get_load_spec(element);
550 throw interactive_error("Unable to obtain URI from element");
552 var uri = load_spec_uri_string(spec);
555 panel = create_info_panel(I.window, "download-panel",
557 element_get_operation_label(element, "Running on"),
558 load_spec_uri_string(spec)],
559 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
563 var cmd = yield I.minibuffer.read_shell_command(
565 $initial_value = load_spec_default_shell_command(spec));
570 /* FIXME: specify cwd as well */
571 yield browser_element_shell_command(I.buffer, element, cmd);
574 interactive("bookmark", function (I) {
575 var element = yield I.read_browser_object("bookmark", "Bookmark", "frames");
576 var spec = element_get_load_spec(element);
578 throw interactive_error("Element has no associated URI");
579 var uri_string = load_spec_uri_string(spec);
581 panel = create_info_panel(I.window, "bookmark-panel",
583 element_get_operation_label(element, "Bookmarking"),
586 var title = yield I.minibuffer.read($prompt = "Bookmark with title:", $initial_value = load_spec_title(spec) || "");
590 add_bookmark(uri_string, title);
591 I.minibuffer.message("Added bookmark: " + uri_string + " - " + title);
594 interactive("save-page", function (I) {
595 check_buffer(I.buffer, content_buffer);
596 var element = yield I.read_browser_object("save_page", "Save page", "frames");
597 var spec = element_get_load_spec(element);
598 if (!spec || !load_spec_document(spec))
599 throw interactive_error("Element is not associated with a document.");
600 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
603 panel = create_info_panel(I.window, "download-panel",
605 element_get_operation_label(element, "Saving"),
606 load_spec_uri_string(spec)],
607 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
610 var file = yield I.minibuffer.read_file_check_overwrite(
611 $prompt = "Save page as:",
613 $initial_value = suggested_path);
618 save_uri(spec, file, $buffer = I.buffer);
621 interactive("save-page-as-text", function (I) {
622 check_buffer(I.buffer, content_buffer);
623 var element = yield I.read_browser_object("save_page_as_text", "Save page as text", "frames");
624 var spec = element_get_load_spec(element);
626 if (!spec || !(doc = load_spec_document(spec)))
627 throw interactive_error("Element is not associated with a document.");
628 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec, "txt"), I.buffer);
631 panel = create_info_panel(I.window, "download-panel",
633 element_get_operation_label(element, "Saving", "as text"),
634 load_spec_uri_string(spec)],
635 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
638 var file = yield I.minibuffer.read_file_check_overwrite(
639 $prompt = "Save page as text:",
641 $initial_value = suggested_path);
646 save_document_as_text(doc, file, $buffer = I.buffer);
649 interactive("save-page-complete", function (I) {
650 check_buffer(I.buffer, content_buffer);
651 var element = yield I.read_browser_object("save_page_complete", "Save page complete", "frames");
652 var spec = element_get_load_spec(element);
654 if (!spec || !(doc = load_spec_document(spec)))
655 throw interactive_error("Element is not associated with a document.");
656 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
659 panel = create_info_panel(I.window, "download-panel",
661 element_get_operation_label(element, "Saving complete"),
662 load_spec_uri_string(spec)],
663 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
666 var file = yield I.minibuffer.read_file_check_overwrite(
667 $prompt = "Save page complete:",
669 $initial_value = suggested_path);
670 // FIXME: use proper read function
671 var dir = yield I.minibuffer.read_file(
672 $prompt = "Data Directory:",
674 $initial_value = file.path + ".support");
679 save_document_complete(doc, file, dir, $buffer = I.buffer);