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_class = ob; },
42 function xpath_browser_object_handler (xpath_expression) {
43 return function (buf, prompt) {
44 var result = yield buf.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 (buf, prompt) {
59 check_buffer(buf, content_buffer);
60 var doc = buf.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(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", "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 (buf, prompt) { yield co_return(buf.top_frame); });
92 define_browser_object_class(
94 function (buf, prompt) {
95 check_buffer (buf, content_buffer);
96 var result = yield buf.window.minibuffer.read_url ($prompt = prompt);
97 yield co_return (result);
100 define_browser_object_class(
101 "alt", "Image Alt-text", null,
102 function (buf, prompt) {
103 var result = yield buf.window.minibuffer.read_hinted_element(
106 $hint_xpath_expression = "//img[@alt]");
107 yield (co_return (result.alt));
110 define_browser_object_class(
111 "title", "Element Title", null,
112 function (buf, prompt) {
113 var result = yield buf.window.minibuffer.read_hinted_element(
116 $hint_xpath_expression = "//*[@title]");
117 yield (co_return (result.title));
120 define_browser_object_class(
121 "title-or-alt", "Element Title or Alt-text", null,
122 function (buf, prompt) {
123 var result = yield buf.window.minibuffer.read_hinted_element(
126 $hint_xpath_expression = "//img[@alt] | //*[@title]");
127 yield (co_return (result.title ? result.title : result.alt));
131 interactive_context.prototype.read_browser_object = function(action, target)
133 var browser_object = this.browser_object; //default
134 // literals cannot be overridden
135 if (browser_object instanceof Function)
136 yield co_return(browser_object());
137 if (! (browser_object instanceof browser_object_class))
138 yield co_return(browser_object);
140 var object_class = this._browser_object_class; //override
142 object_class = browser_object;
143 var prompt = action.split(/-|_/).join(" ");
144 prompt = prompt[0].toUpperCase() + prompt.substring(1);
146 prompt += TARGET_PROMPTS[target];
147 if (object_class.label)
148 prompt += " (select " + object_class.label + ")";
151 var result = yield object_class.handler.call(null, this.buffer, prompt);
152 yield co_return(result);
156 function is_dom_node_or_window(elem) {
157 if (elem instanceof Ci.nsIDOMNode)
159 if (elem instanceof Ci.nsIDOMWindow)
165 * This is a simple wrapper function that sets focus to elem, and
166 * bypasses the automatic focus prevention system, which might
167 * otherwise prevent this from happening.
169 function browser_set_element_focus(buffer, elem, prevent_scroll) {
170 if (!is_dom_node_or_window(elem))
173 buffer.last_user_input_received = Date.now();
175 set_focus_no_scroll(buffer.window, elem);
180 function browser_element_focus(buffer, elem)
182 if (!is_dom_node_or_window(elem))
185 if (elem instanceof Ci.nsIDOMXULTextBoxElement) {
186 // Focus the input field instead
187 elem = elem.wrappedJSObject.inputField;
190 browser_set_element_focus(buffer, elem);
191 if (elem instanceof Ci.nsIDOMWindow) {
194 // If it is not a window, it must be an HTML element
197 if (elem instanceof Ci.nsIDOMHTMLFrameElement || elem instanceof Ci.nsIDOMHTMLIFrameElement) {
198 elem.contentWindow.focus();
201 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
202 var coords = elem.getAttribute("coords").split(",");
203 x = Number(coords[0]);
204 y = Number(coords[1]);
207 var doc = elem.ownerDocument;
208 var evt = doc.createEvent("MouseEvents");
209 var doc = elem.ownerDocument;
211 evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
212 elem.dispatchEvent(evt);
215 function browser_object_follow(buffer, target, elem)
217 browser_set_element_focus(buffer, elem, true /* no scroll */);
219 var no_click = (is_load_spec(elem) ||
220 (elem instanceof Ci.nsIDOMWindow) ||
221 (elem instanceof Ci.nsIDOMHTMLFrameElement) ||
222 (elem instanceof Ci.nsIDOMHTMLIFrameElement) ||
223 (elem instanceof Ci.nsIDOMHTMLLinkElement) ||
224 (elem instanceof Ci.nsIDOMHTMLImageElement &&
225 !elem.hasAttribute("onmousedown") && !elem.hasAttribute("onclick")));
227 if (target == FOLLOW_DEFAULT && !no_click) {
229 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
230 var coords = elem.getAttribute("coords").split(",");
231 if (coords.length >= 2) {
232 x = Number(coords[0]) + 1;
233 y = Number(coords[1]) + 1;
236 browser_follow_link_with_click(buffer, elem, x, y);
240 var spec = element_get_load_spec(elem);
242 throw interactive_error("Element has no associated URL");
246 if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
247 // This URL won't work
248 throw interactive_error("Can't load javascript URL");
251 if (!(buffer instanceof content_buffer) &&
252 (target == FOLLOW_CURRENT_FRAME ||
253 target == FOLLOW_DEFAULT ||
254 target == FOLLOW_TOP_FRAME ||
255 target == OPEN_CURRENT_BUFFER))
256 target = OPEN_NEW_BUFFER;
259 case FOLLOW_CURRENT_FRAME:
260 var current_frame = load_spec_source_frame(spec);
261 if (current_frame && current_frame != buffer.top_frame) {
262 var target_obj = get_web_navigation_for_frame(current_frame);
263 apply_load_spec(target_obj, spec);
267 case FOLLOW_TOP_FRAME:
268 case OPEN_CURRENT_BUFFER:
271 case OPEN_NEW_WINDOW:
272 case OPEN_NEW_BUFFER:
273 case OPEN_NEW_BUFFER_BACKGROUND:
274 create_buffer(buffer.window,
275 buffer_creator(content_buffer,
277 $configuration = buffer.configuration),
283 * Follow a link-like element by generating fake mouse events.
285 function browser_follow_link_with_click(buffer, elem, x, y) {
286 var doc = elem.ownerDocument;
287 var view = doc.defaultView;
289 var evt = doc.createEvent("MouseEvents");
290 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
291 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
292 elem.dispatchEvent(evt);
294 evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
295 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
296 elem.dispatchEvent(evt);
299 function element_get_load_spec(elem) {
301 if (is_load_spec(elem))
306 if (elem instanceof Ci.nsIDOMWindow)
307 spec = load_spec({document: elem.document});
309 else if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
310 elem instanceof Ci.nsIDOMHTMLIFrameElement)
311 spec = load_spec({document: elem.contentDocument});
317 if (elem instanceof Ci.nsIDOMHTMLAnchorElement ||
318 elem instanceof Ci.nsIDOMHTMLAreaElement ||
319 elem instanceof Ci.nsIDOMHTMLLinkElement) {
320 if (!elem.hasAttribute("href"))
321 return null; // nothing can be done, as no nesting within these elements is allowed
323 title = elem.title || elem.textContent;
325 else if (elem instanceof Ci.nsIDOMHTMLImageElement) {
327 title = elem.title || elem.alt;
331 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
332 node = node.parentNode;
333 if (node && !node.hasAttribute("href"))
341 if (node.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
342 url = linkNode.getAttributeNS(XLINK_NS, "href");
345 node = node.parentNode;
348 url = makeURLAbsolute(node.baseURI, url);
349 title = node.title || node.textContent;
352 if (url && url.length > 0) {
353 if (title && title.length == 0)
355 spec = load_spec({uri: url, source_frame: elem.ownerDocument.defaultView, title: title});
362 function follow (I, target) {
364 target = FOLLOW_DEFAULT;
365 var element = yield I.read_browser_object(I.command, target);
366 // XXX: to follow in the current buffer requires that the current
367 // buffer be a content_buffer. this is perhaps not the best place
368 // for this check, because FOLLOW_DEFAULT could signify new buffer
370 check_buffer (I.buffer, content_buffer);
371 browser_object_follow(I.buffer, target, element);
374 function follow_new_buffer (I) {
375 yield follow(I, OPEN_NEW_BUFFER);
378 function follow_new_buffer_background (I) {
379 yield follow(I, OPEN_NEW_BUFFER_BACKGROUND);
382 function follow_new_window (I) {
383 yield follow(I, OPEN_NEW_WINDOW);
386 function follow_top (I) {
387 yield follow(I, FOLLOW_TOP_FRAME);
390 function follow_current_frame (I) {
391 yield follow(I, FOLLOW_CURRENT_FRAME);
394 function follow_current_buffer (I) {
395 yield follow(I, OPEN_CURRENT_BUFFER);
399 function element_get_load_target_label(element) {
400 if (element instanceof Ci.nsIDOMWindow)
402 if (element instanceof Ci.nsIDOMHTMLFrameElement)
404 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
409 function element_get_operation_label(element, op_name, suffix) {
410 var target_label = element_get_load_target_label(element);
411 if (target_label != null)
412 target_label = " " + target_label;
417 suffix = " " + suffix;
421 return op_name + target_label + suffix + ":";
425 function browser_element_copy(buffer, elem)
427 var spec = element_get_load_spec(elem);
430 text = load_spec_uri_string(spec);
432 if (!(elem instanceof Ci.nsIDOMNode))
433 throw interactive_error("Element has no associated text to copy.");
434 switch (elem.localName) {
440 if (elem.selectedIndex >= 0)
441 text = elem.item(elem.selectedIndex).text;
444 text = elem.textContent;
448 browser_set_element_focus(buffer, elem);
449 writeToClipboard (text);
450 buffer.window.minibuffer.message ("Copied: " + text);
454 var view_source_use_external_editor = false, view_source_function = null;
455 function browser_object_view_source(buffer, target, elem)
457 if (view_source_use_external_editor || view_source_function)
459 var spec = element_get_load_spec(elem);
461 throw interactive_error("Element has no associated URL");
465 let [file, temp] = yield download_as_temporary(spec,
467 $action = "View source");
468 if (view_source_use_external_editor)
469 yield open_file_with_external_editor(file, $temporary = temp);
471 yield view_source_function(file, $temporary = temp);
476 var window = buffer.window;
477 if (elem.localName) {
478 switch (elem.localName.toLowerCase()) {
479 case "frame": case "iframe":
480 win = elem.contentWindow;
483 view_mathml_source (window, charset, elem);
486 throw new Error("Invalid browser element");
492 var url_s = win.location.href;
493 if (url_s.substring (0,12) != "view-source:") {
495 browser_object_follow(buffer, target, "view-source:" + url_s);
496 } catch(e) { dump_error(e); }
498 window.minibuffer.message ("Already viewing source");
502 function view_source (I, target) {
503 var element = yield I.read_browser_object(I.command, target);
504 yield browser_object_view_source(I.buffer, target || OPEN_CURRENT_BUFFER, element);
507 function view_source_new_buffer (I) {
508 yield view_source(I, OPEN_NEW_BUFFER);
511 function view_source_new_window (I) {
512 yield view_source(I, OPEN_NEW_WINDOW);
516 function browser_element_shell_command(buffer, elem, command) {
517 var spec = element_get_load_spec(elem);
519 throw interactive_error("Element has no associated URL");
522 yield download_as_temporary(spec,
524 $shell_command = command,
525 $shell_command_cwd = buffer.cwd);