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);
168 function read_browser_object (I)
170 var browser_object = I.browser_object;
171 // literals cannot be overridden
172 if (browser_object instanceof Function)
173 yield co_return(browser_object());
174 if (! (browser_object instanceof browser_object_class))
175 yield co_return(browser_object);
177 var prompt = I.command.prompt;
179 prompt = I.command.name.split(/-|_/).join(" ");
180 prompt = prompt[0].toUpperCase() + prompt.substring(1);
182 if (I.target != null)
183 prompt += TARGET_PROMPTS[I.target];
184 if (browser_object.label)
185 prompt += " (select " + browser_object.label + ")";
188 var result = yield browser_object.handler.call(null, I, prompt);
189 yield co_return(result);
193 function is_dom_node_or_window(elem) {
194 if (elem instanceof Ci.nsIDOMNode)
196 if (elem instanceof Ci.nsIDOMWindow)
202 * This is a simple wrapper function that sets focus to elem, and
203 * bypasses the automatic focus prevention system, which might
204 * otherwise prevent this from happening.
206 function browser_set_element_focus(buffer, elem, prevent_scroll) {
207 if (!is_dom_node_or_window(elem))
210 buffer.last_user_input_received = Date.now();
212 set_focus_no_scroll(buffer.window, elem);
217 function browser_element_focus(buffer, elem)
219 if (!is_dom_node_or_window(elem))
222 if (elem instanceof Ci.nsIDOMXULTextBoxElement) {
223 // Focus the input field instead
224 elem = elem.wrappedJSObject.inputField;
227 browser_set_element_focus(buffer, elem);
228 if (elem instanceof Ci.nsIDOMWindow) {
231 // If it is not a window, it must be an HTML element
234 if (elem instanceof Ci.nsIDOMHTMLFrameElement || elem instanceof Ci.nsIDOMHTMLIFrameElement) {
235 elem.contentWindow.focus();
238 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
239 var coords = elem.getAttribute("coords").split(",");
240 x = Number(coords[0]);
241 y = Number(coords[1]);
244 var doc = elem.ownerDocument;
245 var evt = doc.createEvent("MouseEvents");
247 evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
248 elem.dispatchEvent(evt);
251 function browser_object_follow(buffer, target, elem)
253 browser_set_element_focus(buffer, elem, true /* no scroll */);
255 // XXX: would be better to let nsILocalFile objects be load_specs
256 if (elem instanceof Ci.nsILocalFile)
259 var no_click = (is_load_spec(elem) ||
260 (elem instanceof Ci.nsIDOMWindow) ||
261 (elem instanceof Ci.nsIDOMHTMLFrameElement) ||
262 (elem instanceof Ci.nsIDOMHTMLIFrameElement) ||
263 (elem instanceof Ci.nsIDOMHTMLLinkElement) ||
264 (elem instanceof Ci.nsIDOMHTMLImageElement &&
265 !elem.hasAttribute("onmousedown") && !elem.hasAttribute("onclick")));
267 if (target == FOLLOW_DEFAULT && !no_click) {
269 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
270 var coords = elem.getAttribute("coords").split(",");
271 if (coords.length >= 2) {
272 x = Number(coords[0]) + 1;
273 y = Number(coords[1]) + 1;
276 browser_follow_link_with_click(buffer, elem, x, y);
280 var spec = element_get_load_spec(elem);
282 throw interactive_error("Element has no associated URL");
286 if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
287 // it is nonsensical to follow a javascript url in a different
289 target = FOLLOW_DEFAULT;
290 } else if (!(buffer instanceof content_buffer) &&
291 (target == FOLLOW_CURRENT_FRAME ||
292 target == FOLLOW_DEFAULT ||
293 target == FOLLOW_TOP_FRAME ||
294 target == OPEN_CURRENT_BUFFER))
296 target = OPEN_NEW_BUFFER;
300 case FOLLOW_CURRENT_FRAME:
301 var current_frame = load_spec_source_frame(spec);
302 if (current_frame && current_frame != buffer.top_frame) {
303 var target_obj = get_web_navigation_for_frame(current_frame);
304 apply_load_spec(target_obj, spec);
308 case FOLLOW_TOP_FRAME:
309 case OPEN_CURRENT_BUFFER:
312 case OPEN_NEW_WINDOW:
313 case OPEN_NEW_BUFFER:
314 case OPEN_NEW_BUFFER_BACKGROUND:
315 create_buffer(buffer.window,
316 buffer_creator(content_buffer,
318 $configuration = buffer.configuration),
324 * Follow a link-like element by generating fake mouse events.
326 function browser_follow_link_with_click(buffer, elem, x, y) {
327 var doc = elem.ownerDocument;
328 var view = doc.defaultView;
330 var evt = doc.createEvent("MouseEvents");
331 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
332 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
333 elem.dispatchEvent(evt);
335 evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
336 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
337 elem.dispatchEvent(evt);
340 function element_get_load_spec(elem) {
342 if (is_load_spec(elem))
347 if (elem instanceof Ci.nsIDOMWindow)
348 spec = load_spec({document: elem.document});
350 else if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
351 elem instanceof Ci.nsIDOMHTMLIFrameElement)
352 spec = load_spec({document: elem.contentDocument});
358 if (elem instanceof Ci.nsIDOMHTMLAnchorElement ||
359 elem instanceof Ci.nsIDOMHTMLAreaElement ||
360 elem instanceof Ci.nsIDOMHTMLLinkElement) {
361 if (!elem.hasAttribute("href"))
362 return null; // nothing can be done, as no nesting within these elements is allowed
364 title = elem.title || elem.textContent;
366 else if (elem instanceof Ci.nsIDOMHTMLImageElement) {
368 title = elem.title || elem.alt;
372 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
373 node = node.parentNode;
375 if (node.hasAttribute("href"))
384 if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
385 url = node.getAttributeNS(XLINK_NS, "href");
388 node = node.parentNode;
391 url = makeURLAbsolute(node.baseURI, url);
392 title = node.title || node.textContent;
395 if (url && url.length > 0) {
396 if (title && title.length == 0)
398 spec = load_spec({uri: url, source_frame: elem.ownerDocument.defaultView, title: title});
405 function follow (I, target) {
407 target = FOLLOW_DEFAULT;
409 var element = yield read_browser_object(I);
410 // XXX: to follow in the current buffer requires that the current
411 // buffer be a content_buffer. this is perhaps not the best place
412 // for this check, because FOLLOW_DEFAULT could signify new buffer
414 check_buffer (I.buffer, content_buffer);
415 browser_object_follow(I.buffer, target, element);
418 function follow_new_buffer (I) {
419 yield follow(I, OPEN_NEW_BUFFER);
422 function follow_new_buffer_background (I) {
423 yield follow(I, OPEN_NEW_BUFFER_BACKGROUND);
426 function follow_new_window (I) {
427 yield follow(I, OPEN_NEW_WINDOW);
430 function follow_top (I) {
431 yield follow(I, FOLLOW_TOP_FRAME);
434 function follow_current_frame (I) {
435 yield follow(I, FOLLOW_CURRENT_FRAME);
438 function follow_current_buffer (I) {
439 yield follow(I, OPEN_CURRENT_BUFFER);
443 function element_get_load_target_label(element) {
444 if (element instanceof Ci.nsIDOMWindow)
446 if (element instanceof Ci.nsIDOMHTMLFrameElement)
448 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
453 function element_get_operation_label(element, op_name, suffix) {
454 var target_label = element_get_load_target_label(element);
455 if (target_label != null)
456 target_label = " " + target_label;
461 suffix = " " + suffix;
465 return op_name + target_label + suffix + ":";
469 function browser_element_copy(buffer, elem)
471 var spec = element_get_load_spec(elem);
474 text = load_spec_uri_string(spec);
476 if (!(elem instanceof Ci.nsIDOMNode))
477 throw interactive_error("Element has no associated text to copy.");
478 switch (elem.localName) {
484 if (elem.selectedIndex >= 0)
485 text = elem.item(elem.selectedIndex).text;
488 text = elem.textContent;
492 browser_set_element_focus(buffer, elem);
493 writeToClipboard (text);
494 buffer.window.minibuffer.message ("Copied: " + text);
498 var view_source_use_external_editor = false, view_source_function = null;
499 function browser_object_view_source(buffer, target, elem)
501 if (view_source_use_external_editor || view_source_function)
503 var spec = element_get_load_spec(elem);
505 throw interactive_error("Element has no associated URL");
509 let [file, temp] = yield download_as_temporary(spec,
511 $action = "View source");
512 if (view_source_use_external_editor)
513 yield open_file_with_external_editor(file, $temporary = temp);
515 yield view_source_function(file, $temporary = temp);
520 var window = buffer.window;
521 if (elem.localName) {
522 switch (elem.localName.toLowerCase()) {
523 case "frame": case "iframe":
524 win = elem.contentWindow;
527 view_mathml_source (window, charset, elem);
530 throw new Error("Invalid browser element");
536 var url_s = win.location.href;
537 if (url_s.substring (0,12) != "view-source:") {
539 browser_object_follow(buffer, target, "view-source:" + url_s);
540 } catch(e) { dump_error(e); }
542 window.minibuffer.message ("Already viewing source");
546 function view_source (I, target) {
548 var element = yield read_browser_object(I);
549 yield browser_object_view_source(I.buffer, (target == null ? OPEN_CURRENT_BUFFER : target), element);
552 function view_source_new_buffer (I) {
553 yield view_source(I, OPEN_NEW_BUFFER);
556 function view_source_new_window (I) {
557 yield view_source(I, OPEN_NEW_WINDOW);
561 function browser_element_shell_command(buffer, elem, command) {
562 var spec = element_get_load_spec(elem);
564 throw interactive_error("Element has no associated URL");
567 yield download_as_temporary(spec,
569 $shell_command = command,
570 $shell_command_cwd = buffer.cwd);