4 var browser_object_classes = {};
7 * handler is a coroutine called as: handler(buffer, prompt)
9 define_keywords("$doc", "$action", "$label", "$handler", "$xpath_expression");
10 function define_browser_object_class(name) {
11 keywords(arguments, $xpath_expression = undefined);
12 var handler = arguments.$handler;
13 let xpath_expression = arguments.$xpath_expression;
14 if (handler === undefined && xpath_expression != undefined) {
15 handler = function (buf, prompt) {
16 var result = yield buf.window.minibuffer.read_hinted_element(
19 $hint_xpath_expression = xpath_expression);
20 yield co_return(result);
23 var base_obj = browser_object_classes[name];
25 base_obj = browser_object_classes[name] = {};
27 if (arguments.$action) {
28 name = name + "/" + arguments.$action;
29 obj = browser_object_classes[name];
31 obj = browser_object_classes[name] = {__proto__: base_obj};
34 if (arguments.$label !== undefined)
35 obj.label = arguments.$label;
36 if (arguments.$doc !== undefined)
37 obj.doc = arguments.$doc;
38 if (handler !== undefined)
39 obj.handler = handler;
42 define_browser_object_class("images", $xpath_expression = "//img | //xhtml:img");
44 define_browser_object_class("frames", $handler = function (buf, prompt) {
45 check_buffer(buf, content_buffer);
46 var doc = buf.document;
47 if (doc.getElementsByTagName("frame").length == 0 &&
48 doc.getElementsByTagName("iframe").length == 0)
50 // only one frame (the top-level one), no need to use the hints system
51 yield co_return(buf.top_frame);
54 var result = yield buf.window.minibuffer.read_hinted_element(
57 $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
58 yield co_return(result);
61 define_browser_object_class(
64 "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
65 "@class='lk' or @class='s' or @role='link'] | " +
66 "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " +
67 "//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " +
68 "//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | " +
69 "//xhtml:button | //xhtml:select");
71 define_browser_object_class("mathml", $label = "MathML", $xpath_expression = "//m:math");
73 define_browser_object_class("top", $handler = function (buf, prompt) { yield co_return(buf.top_frame); });
77 "default_browser_object_classes",
84 view_source: "frames",
87 save_page_complete: "top",
88 save_page_as_text: "frames",
91 "Specifies the default object class for each operation.\n" +
92 "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.");
94 interactive_context.prototype.browser_object_class = function (action_name) {
96 this._browser_object_class ||
97 this.get("default_browser_object_classes")[action_name] ||
98 this.get("default_browser_object_classes")["default"];
102 function browser_object_class_selector(name) {
103 return function (ctx, active_keymap, overlay_keymap) {
104 ctx._browser_object_class = name;
105 ctx.overlay_keymap = overlay_keymap || active_keymap;
109 function lookup_browser_object_class(class_name, action) {
111 if (action != null) {
112 obj = browser_object_classes[class_name + "/" + action];
116 return browser_object_classes[class_name];
119 interactive_context.prototype.read_browser_object = function(action, action_name, default_class, target)
121 var object_class_name = this.browser_object_class(action);
122 var object_class = lookup_browser_object_class(object_class_name, action);
124 var prompt = action_name;
126 prompt += TARGET_PROMPTS[target];
127 if (object_class_name != default_class) {
128 var label = object_class.label || object_class_name;
129 prompt += " (" + label + ")";
133 var result = yield object_class.handler.call(null, this.buffer, prompt);
134 yield co_return(result);
138 function is_dom_node_or_window(elem) {
139 if (elem instanceof Ci.nsIDOMNode)
141 if (elem instanceof Ci.nsIDOMWindow)
147 * This is a simple wrapper function that sets focus to elem, and
148 * bypasses the automatic focus prevention system, which might
149 * otherwise prevent this from happening.
151 function browser_set_element_focus(buffer, elem, prevent_scroll) {
152 if (!is_dom_node_or_window(elem))
155 buffer.last_user_input_received = Date.now();
157 set_focus_no_scroll(buffer.window, elem);
162 function browser_element_focus(buffer, elem)
164 if (!is_dom_node_or_window(elem))
167 if (elem instanceof Ci.nsIDOMXULTextBoxElement) {
168 // Focus the input field instead
169 elem = elem.wrappedJSObject.inputField;
172 browser_set_element_focus(buffer, elem);
173 if (elem instanceof Ci.nsIDOMWindow) {
176 // If it is not a window, it must be an HTML element
179 if (elem instanceof Ci.nsIDOMHTMLFrameElement || elem instanceof Ci.nsIDOMHTMLIFrameElement) {
180 elem.contentWindow.focus();
183 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
184 var coords = elem.getAttribute("coords").split(",");
185 x = Number(coords[0]);
186 y = Number(coords[1]);
189 var doc = elem.ownerDocument;
190 var evt = doc.createEvent("MouseEvents");
191 var doc = elem.ownerDocument;
193 evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
194 elem.dispatchEvent(evt);
197 function browser_element_follow(buffer, target, elem)
199 browser_set_element_focus(buffer, elem, true /* no scroll */);
201 var no_click = (is_load_spec(elem) ||
202 (elem instanceof Ci.nsIDOMWindow) ||
203 (elem instanceof Ci.nsIDOMHTMLFrameElement) ||
204 (elem instanceof Ci.nsIDOMHTMLIFrameElement) ||
205 (elem instanceof Ci.nsIDOMHTMLLinkElement) ||
206 (elem instanceof Ci.nsIDOMHTMLImageElement &&
207 !elem.hasAttribute("onmousedown") && !elem.hasAttribute("onclick")));
209 if (target == FOLLOW_DEFAULT && !no_click) {
211 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
212 var coords = elem.getAttribute("coords").split(",");
213 if (coords.length >= 2) {
214 x = Number(coords[0]) + 1;
215 y = Number(coords[1]) + 1;
218 browser_follow_link_with_click(buffer, elem, x, y);
222 var spec = element_get_load_spec(elem);
224 throw interactive_error("Element has no associated URL");
228 if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
229 // This URL won't work
230 throw interactive_error("Can't load javascript URL");
234 case FOLLOW_CURRENT_FRAME:
235 var current_frame = load_spec_source_frame(spec);
236 if (current_frame && current_frame != buffer.top_frame) {
237 var target_obj = get_web_navigation_for_frame(current_frame);
238 apply_load_spec(target_obj, spec);
242 case FOLLOW_TOP_FRAME:
243 case OPEN_CURRENT_BUFFER:
246 case OPEN_NEW_WINDOW:
247 case OPEN_NEW_BUFFER:
248 case OPEN_NEW_BUFFER_BACKGROUND:
249 create_buffer(buffer.window,
250 buffer_creator(content_buffer,
252 $configuration = buffer.configuration),
258 * Follow a link-like element by generating fake mouse events.
260 function browser_follow_link_with_click(buffer, elem, x, y) {
261 var doc = elem.ownerDocument;
262 var view = doc.defaultView;
264 var evt = doc.createEvent("MouseEvents");
265 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
266 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
267 elem.dispatchEvent(evt);
269 evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
270 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
271 elem.dispatchEvent(evt);
274 function element_get_load_spec(elem) {
276 if (is_load_spec(elem))
281 if (elem instanceof Ci.nsIDOMWindow)
282 spec = load_spec({document: elem.document});
284 else if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
285 elem instanceof Ci.nsIDOMHTMLIFrameElement)
286 spec = load_spec({document: elem.contentDocument});
292 if (elem instanceof Ci.nsIDOMHTMLAnchorElement ||
293 elem instanceof Ci.nsIDOMHTMLAreaElement ||
294 elem instanceof Ci.nsIDOMHTMLLinkElement) {
295 if (!elem.hasAttribute("href"))
296 return null; // nothing can be done, as no nesting within these elements is allowed
298 title = elem.title || elem.textContent;
300 else if (elem instanceof Ci.nsIDOMHTMLImageElement) {
302 title = elem.title || elem.alt;
306 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
307 node = node.parentNode;
308 if (node && !node.hasAttribute("href"))
316 if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
317 url = linkNode.getAttributeNS(XLINK_NS, "href");
320 node = node.parentNode;
323 url = makeURLAbsolute(node.baseURI, url);
324 title = node.title || node.textContent;
327 if (url && url.length > 0) {
328 if (title && title.length == 0)
330 spec = load_spec({uri: url, source_frame: elem.ownerDocument.defaultView, title: title});
336 interactive("follow", function (I) {
337 var target = I.browse_target("follow");
338 var element = yield I.read_browser_object("follow", "Follow", "links", target);
339 browser_element_follow(I.buffer, target, element);
342 interactive("follow-top", function (I) {
343 var target = I.browse_target("follow-top");
344 var element = yield I.read_browser_object("follow_top", "Follow", "links", target);
345 browser_element_follow(I.buffer, target, element);
348 interactive("focus", function (I) {
349 var element = yield I.read_browser_object("focus", "Focus");
350 browser_element_focus(I.buffer, element);
353 function element_get_load_target_label(element) {
354 if (element instanceof Ci.nsIDOMWindow)
356 if (element instanceof Ci.nsIDOMHTMLFrameElement)
358 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
363 function element_get_operation_label(element, op_name, suffix) {
364 var target_label = element_get_load_target_label(element);
365 if (target_label != null)
366 target_label = " " + target_label;
371 suffix = " " + suffix;
375 return op_name + target_label + suffix + ":";
378 interactive("save", function (I) {
379 var element = yield I.read_browser_object("save", "Save", "links");
381 var spec = element_get_load_spec(element);
383 throw interactive_error("Element has no associated URI");
386 panel = create_info_panel(I.window, "download-panel",
388 element_get_operation_label(element, "Saving"),
389 load_spec_uri_string(spec)],
390 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
393 var file = yield I.minibuffer.read_file_check_overwrite(
394 $prompt = "Save as:",
395 $initial_value = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer),
407 function browser_element_copy(buffer, elem)
409 var spec = element_get_load_spec(elem);
412 text = load_spec_uri_string(spec);
414 if (!(elem instanceof Ci.nsIDOMNode))
415 throw interactive_error("Element has no associated text to copy.");
416 switch (elem.localName) {
422 if (elem.selectedIndex >= 0)
423 text = elem.item(elem.selectedIndex).text;
426 text = elem.textContent;
430 writeToClipboard (text);
431 buffer.window.minibuffer.message ("Copied: " + text);
435 interactive("copy", function (I) {
436 var element = yield I.read_browser_object("copy", "Copy", "links");
437 browser_element_copy(I.buffer, element);
440 var view_source_use_external_editor = false, view_source_function = null;
441 function browser_element_view_source(buffer, target, elem)
443 if (view_source_use_external_editor || view_source_function)
445 var spec = element_get_load_spec(elem);
447 throw interactive_error("Element has no associated URL");
451 let [file, temp] = yield download_as_temporary(spec,
453 $action = "View source");
454 if (view_source_use_external_editor)
455 yield open_file_with_external_editor(file, $temporary = temp);
457 yield view_source_function(file, $temporary = temp);
462 var window = buffer.window;
463 if (elem.localName) {
464 switch (elem.localName.toLowerCase()) {
465 case "frame": case "iframe":
466 win = elem.contentWindow;
469 view_mathml_source (window, charset, elem);
472 throw new Error("Invalid browser element");
478 var url_s = win.location.href;
479 if (url_s.substring (0,12) != "view-source:") {
481 open_in_browser(buffer, target, "view-source:" + url_s);
482 } catch(e) { dump_error(e); }
484 window.minibuffer.message ("Already viewing source");
488 interactive("view-source", function (I) {
489 var target = I.browse_target("follow");
490 var element = yield I.read_browser_object("view_source", "View source", "frames", target);
491 yield browser_element_view_source(I.buffer, target, element);
494 interactive("shell-command-on-url", function (I) {
496 var element = yield I.read_browser_object("shell_command_url", "URL shell command target", "links");
497 var spec = element_get_load_spec(element);
499 throw interactive_error("Unable to obtain URI from element");
501 var uri = load_spec_uri_string(spec);
504 panel = create_info_panel(I.window, "download-panel",
506 element_get_operation_label(element, "Running on", "URI"),
507 load_spec_uri_string(spec)],
508 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
511 var cmd = yield I.minibuffer.read_shell_command(
513 $initial_value = load_spec_default_shell_command(spec));
518 shell_command_with_argument_blind(cmd, uri, $cwd = cwd);
521 function browser_element_shell_command(buffer, elem, command) {
522 var spec = element_get_load_spec(elem);
524 throw interactive_error("Element has no associated URL");
527 yield download_as_temporary(spec,
529 $shell_command = command,
530 $shell_command_cwd = buffer.cwd);
533 interactive("shell-command-on-file", function (I) {
535 var element = yield I.read_browser_object("shell_command", "Shell command target", "links");
537 var spec = element_get_load_spec(element);
539 throw interactive_error("Unable to obtain URI from element");
541 var uri = load_spec_uri_string(spec);
544 panel = create_info_panel(I.window, "download-panel",
546 element_get_operation_label(element, "Running on"),
547 load_spec_uri_string(spec)],
548 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
552 var cmd = yield I.minibuffer.read_shell_command(
554 $initial_value = load_spec_default_shell_command(spec));
559 /* FIXME: specify cwd as well */
560 yield browser_element_shell_command(I.buffer, element, cmd);
563 interactive("bookmark", function (I) {
564 var element = yield I.read_browser_object("bookmark", "Bookmark", "frames");
565 var spec = element_get_load_spec(element);
567 throw interactive_error("Element has no associated URI");
568 var uri_string = load_spec_uri_string(spec);
570 panel = create_info_panel(I.window, "bookmark-panel",
572 element_get_operation_label(element, "Bookmarking"),
575 var title = yield I.minibuffer.read($prompt = "Bookmark with title:", $initial_value = load_spec_title(spec) || "");
579 add_bookmark(uri_string, title);
580 I.minibuffer.message("Added bookmark: " + uri_string + " - " + title);
583 interactive("save-page", function (I) {
584 check_buffer(I.buffer, content_buffer);
585 var element = yield I.read_browser_object("save_page", "Save page", "frames");
586 var spec = element_get_load_spec(element);
587 if (!spec || !load_spec_document(spec))
588 throw interactive_error("Element is not associated with a document.");
589 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
592 panel = create_info_panel(I.window, "download-panel",
594 element_get_operation_label(element, "Saving"),
595 load_spec_uri_string(spec)],
596 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
599 var file = yield I.minibuffer.read_file_check_overwrite(
600 $prompt = "Save page as:",
602 $initial_value = suggested_path);
607 save_uri(spec, file, $buffer = I.buffer);
610 interactive("save-page-as-text", function (I) {
611 check_buffer(I.buffer, content_buffer);
612 var element = yield I.read_browser_object("save_page_as_text", "Save page as text", "frames");
613 var spec = element_get_load_spec(element);
615 if (!spec || !(doc = load_spec_document(spec)))
616 throw interactive_error("Element is not associated with a document.");
617 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec, "txt"), I.buffer);
620 panel = create_info_panel(I.window, "download-panel",
622 element_get_operation_label(element, "Saving", "as text"),
623 load_spec_uri_string(spec)],
624 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
627 var file = yield I.minibuffer.read_file_check_overwrite(
628 $prompt = "Save page as text:",
630 $initial_value = suggested_path);
635 save_document_as_text(doc, file, $buffer = I.buffer);
638 interactive("save-page-complete", function (I) {
639 check_buffer(I.buffer, content_buffer);
640 var element = yield I.read_browser_object("save_page_complete", "Save page complete", "frames");
641 var spec = element_get_load_spec(element);
643 if (!spec || !(doc = load_spec_document(spec)))
644 throw interactive_error("Element is not associated with a document.");
645 var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
648 panel = create_info_panel(I.window, "download-panel",
650 element_get_operation_label(element, "Saving complete"),
651 load_spec_uri_string(spec)],
652 ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
655 var file = yield I.minibuffer.read_file_check_overwrite(
656 $prompt = "Save page complete:",
658 $initial_value = suggested_path);
659 // FIXME: use proper read function
660 var dir = yield I.minibuffer.read_file(
661 $prompt = "Data Directory:",
663 $initial_value = file.path + ".support");
668 save_document_complete(doc, file, dir, $buffer = I.buffer);