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 function browser_object_class (name, label, doc, handler) {
24 this.handler = handler;
25 if (doc) this.doc = doc;
26 if (label) this.label = label;
29 function define_browser_object_class (name, label, doc, handler) {
30 var varname = 'browser_object_'+name.replace('-','_','g');
31 var ob = conkeror[varname] =
32 new browser_object_class (name, label, doc, handler);
34 "browser-object-"+name,
35 "A prefix command to specify that the following command operate "+
36 "on objects of type: "+name+".",
37 function (ctx) { ctx.browser_object = ob; },
42 function xpath_browser_object_handler (xpath_expression) {
43 return function (I, prompt) {
44 var result = yield I.buffer.window.minibuffer.read_hinted_element(
47 $hint_xpath_expression = xpath_expression);
48 yield co_return(result);
52 define_browser_object_class(
53 "images", "image", null,
54 xpath_browser_object_handler ("//img | //xhtml:img"));
56 define_browser_object_class(
57 "frames","frame", null,
58 function (I, prompt) {
59 check_buffer(I.buffer, content_buffer);
60 var doc = I.buffer.document;
61 if (doc.getElementsByTagName("frame").length == 0 &&
62 doc.getElementsByTagName("iframe").length == 0)
64 // only one frame (the top-level one), no need to use the hints system
65 yield co_return(I.buffer.top_frame);
67 var result = yield I.buffer.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", "link", null,
76 xpath_browser_object_handler (
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(
85 "mathml", "MathML element", null,
86 xpath_browser_object_handler ("//m:math"));
88 define_browser_object_class(
90 function (I, prompt) { yield co_return(I.buffer.top_frame); });
92 define_browser_object_class(
94 function (I, prompt) {
95 check_buffer (I.buffer, content_buffer);
96 var result = yield I.buffer.window.minibuffer.read_url ($prompt = prompt);
97 yield co_return (result);
100 define_browser_object_class(
101 "pasteurl", null, null,
103 check_buffer (I.buffer, content_buffer);
104 let url = read_from_x_primary_selection();
105 yield co_return (url);
108 define_browser_object_class(
110 function (I, prompt) {
111 var result = yield I.buffer.window.minibuffer.read_file(
113 $history = I.command.name+"/file",
114 $initial_value = I.buffer.cwd.path);
115 yield co_return (result);
118 define_browser_object_class(
119 "alt", "Image Alt-text", null,
120 function (I, prompt) {
121 var result = yield I.buffer.window.minibuffer.read_hinted_element(
124 $hint_xpath_expression = "//img[@alt]");
125 yield (co_return (result.alt));
128 define_browser_object_class(
129 "title", "Element Title", null,
130 function (I, prompt) {
131 var result = yield I.buffer.window.minibuffer.read_hinted_element(
134 $hint_xpath_expression = "//*[@title]");
135 yield (co_return (result.title));
138 define_browser_object_class(
139 "title-or-alt", "Element Title or Alt-text", null,
140 function (I, prompt) {
141 var result = yield I.buffer.window.minibuffer.read_hinted_element(
144 $hint_xpath_expression = "//img[@alt] | //*[@title]");
145 yield (co_return (result.title ? result.title : result.alt));
148 define_browser_object_class(
150 "Scrapes urls from the source code of the top-level document of buffer.",
151 function (I, prompt) {
152 check_buffer (I.buffer, content_buffer);
153 var completions = I.buffer.document.documentElement.innerHTML
154 .match(/http:[^\s>"]*/g)
155 .filter(remove_duplicates_filter());
156 var completer = prefix_completer($completions = completions);
157 var result = yield I.buffer.window.minibuffer.read(
159 $completer = completer,
160 $initial_value = null,
161 $auto_complete = "url",
163 $match_required = false);
164 yield co_return (result);
167 define_browser_object_class(
168 "up-url", "Up Url", null,
169 function (I, prompt) {
170 check_buffer(I.buffer, content_buffer);
171 var up = compute_url_up_path(I.buffer.current_URI.spec);
172 return I.buffer.current_URI.resolve(up);
176 function read_browser_object (I)
178 var browser_object = I.browser_object;
179 // literals cannot be overridden
180 if (browser_object instanceof Function)
181 yield co_return(browser_object());
182 if (! (browser_object instanceof browser_object_class))
183 yield co_return(browser_object);
185 var prompt = I.command.prompt;
187 prompt = I.command.name.split(/-|_/).join(" ");
188 prompt = prompt[0].toUpperCase() + prompt.substring(1);
190 if (I.target != null)
191 prompt += TARGET_PROMPTS[I.target];
192 if (browser_object.label)
193 prompt += " (select " + browser_object.label + ")";
196 var result = yield browser_object.handler.call(null, I, prompt);
197 yield co_return(result);
201 function is_dom_node_or_window(elem) {
202 if (elem instanceof Ci.nsIDOMNode)
204 if (elem instanceof Ci.nsIDOMWindow)
210 * This is a simple wrapper function that sets focus to elem, and
211 * bypasses the automatic focus prevention system, which might
212 * otherwise prevent this from happening.
214 function browser_set_element_focus(buffer, elem, prevent_scroll) {
215 if (!is_dom_node_or_window(elem))
218 buffer.last_user_input_received = Date.now();
220 set_focus_no_scroll(buffer.window, elem);
225 function browser_element_focus(buffer, elem)
227 if (!is_dom_node_or_window(elem))
230 if (elem instanceof Ci.nsIDOMXULTextBoxElement) {
231 // Focus the input field instead
232 elem = elem.wrappedJSObject.inputField;
235 browser_set_element_focus(buffer, elem);
236 if (elem instanceof Ci.nsIDOMWindow) {
239 // If it is not a window, it must be an HTML element
242 if (elem instanceof Ci.nsIDOMHTMLFrameElement || elem instanceof Ci.nsIDOMHTMLIFrameElement) {
243 elem.contentWindow.focus();
246 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
247 var coords = elem.getAttribute("coords").split(",");
248 x = Number(coords[0]);
249 y = Number(coords[1]);
252 var doc = elem.ownerDocument;
253 var evt = doc.createEvent("MouseEvents");
255 evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
256 elem.dispatchEvent(evt);
259 function browser_object_follow(buffer, target, elem)
261 browser_set_element_focus(buffer, elem, true /* no scroll */);
263 // XXX: would be better to let nsILocalFile objects be load_specs
264 if (elem instanceof Ci.nsILocalFile)
267 var no_click = (is_load_spec(elem) ||
268 (elem instanceof Ci.nsIDOMWindow) ||
269 (elem instanceof Ci.nsIDOMHTMLFrameElement) ||
270 (elem instanceof Ci.nsIDOMHTMLIFrameElement) ||
271 (elem instanceof Ci.nsIDOMHTMLLinkElement) ||
272 (elem instanceof Ci.nsIDOMHTMLImageElement &&
273 !elem.hasAttribute("onmousedown") && !elem.hasAttribute("onclick")));
275 if (target == FOLLOW_DEFAULT && !no_click) {
277 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
278 var coords = elem.getAttribute("coords").split(",");
279 if (coords.length >= 2) {
280 x = Number(coords[0]) + 1;
281 y = Number(coords[1]) + 1;
284 browser_follow_link_with_click(buffer, elem, x, y);
288 var spec = element_get_load_spec(elem);
290 throw interactive_error("Element has no associated URL");
294 if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
295 // it is nonsensical to follow a javascript url in a different
297 target = FOLLOW_DEFAULT;
298 } else if (!(buffer instanceof content_buffer) &&
299 (target == FOLLOW_CURRENT_FRAME ||
300 target == FOLLOW_DEFAULT ||
301 target == FOLLOW_TOP_FRAME ||
302 target == OPEN_CURRENT_BUFFER))
304 target = OPEN_NEW_BUFFER;
308 case FOLLOW_CURRENT_FRAME:
309 var current_frame = load_spec_source_frame(spec);
310 if (current_frame && current_frame != buffer.top_frame) {
311 var target_obj = get_web_navigation_for_frame(current_frame);
312 apply_load_spec(target_obj, spec);
316 case FOLLOW_TOP_FRAME:
317 case OPEN_CURRENT_BUFFER:
320 case OPEN_NEW_WINDOW:
321 case OPEN_NEW_BUFFER:
322 case OPEN_NEW_BUFFER_BACKGROUND:
323 create_buffer(buffer.window,
324 buffer_creator(content_buffer,
326 $configuration = buffer.configuration),
332 * Follow a link-like element by generating fake mouse events.
334 function browser_follow_link_with_click(buffer, elem, x, y) {
335 var doc = elem.ownerDocument;
336 var view = doc.defaultView;
338 var evt = doc.createEvent("MouseEvents");
339 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
340 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
341 elem.dispatchEvent(evt);
343 evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
344 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
345 elem.dispatchEvent(evt);
348 function element_get_load_spec(elem) {
350 if (is_load_spec(elem))
355 if (elem instanceof Ci.nsIDOMWindow)
356 spec = load_spec({document: elem.document});
358 else if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
359 elem instanceof Ci.nsIDOMHTMLIFrameElement)
360 spec = load_spec({document: elem.contentDocument});
366 if (elem instanceof Ci.nsIDOMHTMLAnchorElement ||
367 elem instanceof Ci.nsIDOMHTMLAreaElement ||
368 elem instanceof Ci.nsIDOMHTMLLinkElement) {
369 if (!elem.hasAttribute("href"))
370 return null; // nothing can be done, as no nesting within these elements is allowed
372 title = elem.title || elem.textContent;
374 else if (elem instanceof Ci.nsIDOMHTMLImageElement) {
376 title = elem.title || elem.alt;
380 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
381 node = node.parentNode;
383 if (node.hasAttribute("href"))
392 if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
393 url = node.getAttributeNS(XLINK_NS, "href");
396 node = node.parentNode;
399 url = makeURLAbsolute(node.baseURI, url);
400 title = node.title || node.textContent;
403 if (url && url.length > 0) {
404 if (title && title.length == 0)
406 spec = load_spec({uri: url, source_frame: elem.ownerDocument.defaultView, title: title});
413 function follow (I, target) {
415 target = FOLLOW_DEFAULT;
417 var element = yield read_browser_object(I);
418 // XXX: to follow in the current buffer requires that the current
419 // buffer be a content_buffer. this is perhaps not the best place
420 // for this check, because FOLLOW_DEFAULT could signify new buffer
422 check_buffer (I.buffer, content_buffer);
423 browser_object_follow(I.buffer, target, element);
426 function follow_new_buffer (I) {
427 yield follow(I, OPEN_NEW_BUFFER);
430 function follow_new_buffer_background (I) {
431 yield follow(I, OPEN_NEW_BUFFER_BACKGROUND);
434 function follow_new_window (I) {
435 yield follow(I, OPEN_NEW_WINDOW);
438 function follow_top (I) {
439 yield follow(I, FOLLOW_TOP_FRAME);
442 function follow_current_frame (I) {
443 yield follow(I, FOLLOW_CURRENT_FRAME);
446 function follow_current_buffer (I) {
447 yield follow(I, OPEN_CURRENT_BUFFER);
451 function element_get_load_target_label(element) {
452 if (element instanceof Ci.nsIDOMWindow)
454 if (element instanceof Ci.nsIDOMHTMLFrameElement)
456 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
461 function element_get_operation_label(element, op_name, suffix) {
462 var target_label = element_get_load_target_label(element);
463 if (target_label != null)
464 target_label = " " + target_label;
469 suffix = " " + suffix;
473 return op_name + target_label + suffix + ":";
477 function browser_element_copy(buffer, elem)
479 var spec = element_get_load_spec(elem);
482 text = load_spec_uri_string(spec);
484 if (!(elem instanceof Ci.nsIDOMNode))
485 throw interactive_error("Element has no associated text to copy.");
486 switch (elem.localName) {
492 if (elem.selectedIndex >= 0)
493 text = elem.item(elem.selectedIndex).text;
496 text = elem.textContent;
500 browser_set_element_focus(buffer, elem);
501 writeToClipboard (text);
502 buffer.window.minibuffer.message ("Copied: " + text);
506 var view_source_use_external_editor = false, view_source_function = null;
507 function browser_object_view_source(buffer, target, elem)
509 if (view_source_use_external_editor || view_source_function)
511 var spec = element_get_load_spec(elem);
513 throw interactive_error("Element has no associated URL");
517 let [file, temp] = yield download_as_temporary(spec,
519 $action = "View source");
520 if (view_source_use_external_editor)
521 yield open_file_with_external_editor(file, $temporary = temp);
523 yield view_source_function(file, $temporary = temp);
528 var window = buffer.window;
529 if (elem.localName) {
530 switch (elem.localName.toLowerCase()) {
531 case "frame": case "iframe":
532 win = elem.contentWindow;
535 view_mathml_source (window, charset, elem);
538 throw new Error("Invalid browser element");
544 var url_s = win.location.href;
545 if (url_s.substring (0,12) != "view-source:") {
547 browser_object_follow(buffer, target, "view-source:" + url_s);
548 } catch(e) { dump_error(e); }
550 window.minibuffer.message ("Already viewing source");
554 function view_source (I, target) {
556 var element = yield read_browser_object(I);
557 yield browser_object_view_source(I.buffer, (target == null ? OPEN_CURRENT_BUFFER : target), element);
560 function view_source_new_buffer (I) {
561 yield view_source(I, OPEN_NEW_BUFFER);
564 function view_source_new_window (I) {
565 yield view_source(I, OPEN_NEW_WINDOW);
569 function browser_element_shell_command(buffer, elem, command) {
570 var spec = element_get_load_spec(elem);
572 throw interactive_error("Element has no associated URL");
575 yield download_as_temporary(spec,
577 $shell_command = command,
578 $shell_command_cwd = buffer.cwd);