2 * (C) Copyright 2007-2009 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 * browser_object_class
22 * In normal cases, make a new browser_object_class with the function,
23 * `define_browser_object_class'.
25 * name: See note on `define_browser_object_class'.
29 * handler: a coroutine called as: handler(I, prompt). `I' is a normal
30 * interactive context. `prompt' is there to pass along as the
31 * $prompt of various minibuffer read procedures, if needed.
33 * $hint: short string (usually verb and noun) to describe the UI
34 * of the browser object class to the user. Only used by
35 * browser object classes which make use of the minibuffer.
37 define_keywords("$hint");
38 function browser_object_class (name, doc, handler) {
41 this.handler = handler;
43 this.hint = arguments.$hint;
47 * define_browser_object_class
49 * In normal cases, make a new browser_object_class with the function,
50 * `define_browser_object_class'.
52 * name: the name of the browser object class. multiword names should be
53 * hyphenated. From this name, a variable browser_object_NAME and
54 * an interactive command browser-object-NAME will be generated.
56 * Other arguments are as for `browser_object_class'.
59 function define_browser_object_class (name, doc, handler) {
61 var varname = 'browser_object_'+name.replace('-','_','g');
62 var ob = conkeror[varname] =
63 new browser_object_class(name, doc, handler,
64 forward_keywords(arguments));
65 interactive("browser-object-"+name,
66 "A prefix command to specify that the following command operate "+
67 "on objects of type: "+name+".",
68 function (I) { I.browser_object = ob; },
74 * xpath_browser_object_handler
76 * This generates a function of the type needed for a handler of a
77 * browser object class. The handler uses `read_hinted_element' of
78 * hints.js to let the user pick a DOM node from those matched by
81 function xpath_browser_object_handler (xpath_expression) {
82 return function (I, prompt) {
83 var result = yield I.buffer.window.minibuffer.read_hinted_element(
86 $hint_xpath_expression = xpath_expression);
87 yield co_return(result);
91 define_browser_object_class("images",
92 "Browser object class for selecting an html:img via hinting.",
93 xpath_browser_object_handler("//img | //xhtml:img"),
94 $hint = "select image");
96 define_browser_object_class("frames",
97 "Browser object class for selecting a frame or iframe via hinting.",
98 function (I, prompt) {
99 var doc = I.buffer.document;
100 // Check for any frames or visible iframes
101 var skip_hints = true;
102 if (doc.getElementsByTagName("frame").length > 0)
105 let topwin = I.buffer.top_frame;
106 let iframes = doc.getElementsByTagName("iframe");
107 for (var i = 0, nframes = iframes.length; i < nframes; i++) {
108 let style = topwin.getComputedStyle(iframes[i], "");
109 if (style.display == "none" || style.visibility == "hidden")
116 // only one frame (the top-level one), no need to use the hints system
117 yield co_return(I.buffer.top_frame);
119 var result = yield I.buffer.window.minibuffer.read_hinted_element(
122 $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
123 yield co_return(result);
125 $hint = "select frame");
127 define_browser_object_class("links",
128 "Browser object class for selecting a hyperlink, form field, "+
129 "or link-like element, via hinting.",
130 xpath_browser_object_handler(
131 "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
133 "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " +
134 "//*[@contenteditable = 'true']"),
135 $hint = "select link");
137 define_browser_object_class("mathml",
138 "Browser object class for selecting a MathML node via hinting.",
139 xpath_browser_object_handler("//m:math"),
140 $hint = "select MathML element");
142 define_browser_object_class("top",
143 "Browser object class which returns the top frame of the document.",
144 function (I, prompt) { return I.buffer.top_frame; });
146 define_browser_object_class("url",
147 "Browser object class which prompts the user for an url or webjump.",
148 function (I, prompt) {
149 var result = yield I.buffer.window.minibuffer.read_url($prompt = prompt);
150 yield co_return(result);
152 $hint = "enter URL/webjump");
154 define_browser_object_class("paste-url",
155 "Browser object which reads an url from the X Primary Selection, "+
156 "falling back on the clipboard for operating systems which lack one.",
157 function (I, prompt) {
158 var url = read_from_x_primary_selection();
160 url = url.replace(/^\s*|\s*$/,"");
161 // add http:// if needed
162 if (url.match(/^[^:]+\./)) {
163 url = "http://" + url;
166 return make_uri(url).spec;
168 throw new interactive_error("error: malformed url: "+url);
172 define_browser_object_class("file",
173 "Browser object which prompts for a file name.",
174 function (I, prompt) {
175 var result = yield I.buffer.window.minibuffer.read_file(
177 $history = I.command.name+"/file",
178 $initial_value = I.local.cwd.path);
179 yield co_return(result);
181 $hint = "enter file name");
183 define_browser_object_class("alt",
184 "Browser object class which returns the alt text of an html:img, "+
185 "selected via hinting",
186 function (I, prompt) {
187 var result = yield I.buffer.window.minibuffer.read_hinted_element(
190 $hint_xpath_expression = "//img[@alt]");
191 yield co_return(result.alt);
193 $hint = "select image for alt-text");
195 define_browser_object_class("title",
196 "Browser object class which returns the title attribute of an element, "+
197 "selected via hinting",
198 function (I, prompt) {
199 var result = yield I.buffer.window.minibuffer.read_hinted_element(
202 $hint_xpath_expression = "//*[@title]");
203 yield co_return(result.title);
205 $hint = "select element for title attribute");
207 define_browser_object_class("title-or-alt",
208 "Browser object which is the union of browser-object-alt and "+
209 "browser-object-title, with title having higher precedence in "+
210 "the case of an element that has both.",
211 function (I, prompt) {
212 var result = yield I.buffer.window.minibuffer.read_hinted_element(
215 $hint_xpath_expression = "//img[@alt] | //*[@title]");
216 yield co_return(result.title ? result.title : result.alt);
218 $hint = "select element for title or alt-text");
220 define_browser_object_class("scrape-url",
221 "Browser object which lets the user choose an url from a list of "+
222 "urls scraped from the source code of the document.",
223 function (I, prompt) {
224 var completions = I.buffer.document.documentElement.innerHTML
225 .match(/http:[^\s>"]*/g)
226 .filter(remove_duplicates_filter());
227 var completer = prefix_completer($completions = completions);
228 var result = yield I.buffer.window.minibuffer.read(
230 $completer = completer,
231 $initial_value = null,
232 $auto_complete = "url",
234 $match_required = false);
235 yield co_return(result);
237 $hint = "choose scraped URL");
239 define_browser_object_class("up-url",
240 "Browser object which returns the url one level above the current one.",
241 function (I, prompt) {
242 var up = compute_url_up_path(I.buffer.current_uri.spec);
243 return I.buffer.current_uri.resolve(up);
246 define_browser_object_class("focused-element",
247 "Browser object which returns the focused element.",
248 function (I, prompt) { return I.buffer.focused_element; });
250 define_browser_object_class("dom-node", null,
251 xpath_browser_object_handler("//*"),
252 $hint = "select DOM node");
254 function read_browser_object (I) {
255 var browser_object = I.browser_object;
256 // literals cannot be overridden
257 if (browser_object instanceof Function)
258 yield co_return(browser_object(I));
259 if (! (browser_object instanceof browser_object_class))
260 yield co_return(browser_object);
262 var prompt = I.command.prompt;
264 prompt = I.command.name.split(/-|_/).join(" ");
265 prompt = prompt[0].toUpperCase() + prompt.substring(1);
267 if (I.target != null)
268 prompt += TARGET_PROMPTS[I.target];
269 if (browser_object.hint)
270 prompt += " (" + browser_object.hint + ")";
273 var result = yield browser_object.handler.call(null, I, prompt);
274 yield co_return(result);
279 * This is a simple wrapper function that sets focus to elem, and
280 * bypasses the automatic focus prevention system, which might
281 * otherwise prevent this from happening.
283 function browser_set_element_focus (buffer, elem, prevent_scroll) {
284 if (!element_dom_node_or_window_p(elem))
287 set_focus_no_scroll(buffer.window, elem);
292 function browser_element_focus (buffer, elem) {
293 if (!element_dom_node_or_window_p(elem))
296 if (elem instanceof Ci.nsIDOMXULTextBoxElement)
297 elem = elem.wrappedJSObject.inputField; // focus the input field
299 browser_set_element_focus(buffer, elem);
300 if (elem instanceof Ci.nsIDOMWindow)
303 // If it is not a window, it must be an HTML element
306 if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
307 elem instanceof Ci.nsIDOMHTMLIFrameElement)
309 elem.contentWindow.focus();
312 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
313 var coords = elem.getAttribute("coords").split(",");
314 x = Number(coords[0]);
315 y = Number(coords[1]);
318 var doc = elem.ownerDocument;
319 var evt = doc.createEvent("MouseEvents");
321 evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
322 elem.dispatchEvent(evt);
325 function browser_object_follow (buffer, target, elem) {
326 // XXX: would be better to let nsILocalFile objects be load_specs
327 if (elem instanceof Ci.nsILocalFile)
331 if (elem instanceof load_spec)
332 e = load_spec_element(elem);
336 browser_set_element_focus(buffer, e, true /* no scroll */);
338 var no_click = (((elem instanceof load_spec) &&
339 load_spec_forced_charset(elem)) ||
340 (e instanceof load_spec) ||
341 (e instanceof Ci.nsIDOMWindow) ||
342 (e instanceof Ci.nsIDOMHTMLFrameElement) ||
343 (e instanceof Ci.nsIDOMHTMLIFrameElement) ||
344 (e instanceof Ci.nsIDOMHTMLLinkElement) ||
345 (e instanceof Ci.nsIDOMHTMLImageElement &&
346 !e.hasAttribute("onmousedown") && !e.hasAttribute("onclick")));
348 if (target == FOLLOW_DEFAULT && !no_click) {
350 if (e instanceof Ci.nsIDOMHTMLAreaElement) {
351 var coords = e.getAttribute("coords").split(",");
352 if (coords.length >= 2) {
353 x = Number(coords[0]) + 1;
354 y = Number(coords[1]) + 1;
357 dom_node_click(e, x, y);
361 var spec = load_spec(elem);
363 if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
364 // it is nonsensical to follow a javascript url in a different
366 target = FOLLOW_DEFAULT;
367 } else if (!(buffer instanceof content_buffer) &&
368 (target == FOLLOW_CURRENT_FRAME ||
369 target == FOLLOW_DEFAULT ||
370 target == OPEN_CURRENT_BUFFER))
372 target = OPEN_NEW_BUFFER;
376 case FOLLOW_CURRENT_FRAME:
377 var current_frame = load_spec_source_frame(spec);
378 if (current_frame && current_frame != buffer.top_frame) {
379 var target_obj = get_web_navigation_for_frame(current_frame);
380 apply_load_spec(target_obj, spec);
384 case OPEN_CURRENT_BUFFER:
387 case OPEN_NEW_WINDOW:
388 case OPEN_NEW_BUFFER:
389 case OPEN_NEW_BUFFER_BACKGROUND:
390 create_buffer(buffer.window,
391 buffer_creator(content_buffer,
399 * Follow a link-like element by generating fake mouse events.
401 function dom_node_click (elem, x, y) {
402 var doc = elem.ownerDocument;
403 var view = doc.defaultView;
405 var evt = doc.createEvent("MouseEvents");
406 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
407 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
408 elem.dispatchEvent(evt);
410 evt = doc.createEvent("MouseEvents");
411 evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
412 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
413 elem.dispatchEvent(evt);
417 function follow (I, target) {
419 target = FOLLOW_DEFAULT;
421 if (target == OPEN_CURRENT_BUFFER)
422 check_buffer(I.buffer, content_buffer);
423 var element = yield read_browser_object(I);
425 element = load_spec(element);
426 if (I.forced_charset)
427 element.forced_charset = I.forced_charset;
429 browser_object_follow(I.buffer, target, element);
432 function follow_new_buffer (I) {
433 yield follow(I, OPEN_NEW_BUFFER);
436 function follow_new_buffer_background (I) {
437 yield follow(I, OPEN_NEW_BUFFER_BACKGROUND);
440 function follow_new_window (I) {
441 yield follow(I, OPEN_NEW_WINDOW);
444 function follow_current_frame (I) {
445 yield follow(I, FOLLOW_CURRENT_FRAME);
448 function follow_current_buffer (I) {
449 yield follow(I, OPEN_CURRENT_BUFFER);
453 function element_get_load_target_label (element) {
454 if (element instanceof Ci.nsIDOMWindow)
456 if (element instanceof Ci.nsIDOMHTMLFrameElement)
458 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
463 function element_get_operation_label (element, op_name, suffix) {
464 var target_label = element_get_load_target_label(element);
465 if (target_label != null)
466 target_label = " " + target_label;
471 suffix = " " + suffix;
475 return op_name + target_label + suffix + ":";
479 function browser_element_copy (buffer, elem) {
482 spec = load_spec(elem);
486 text = load_spec_uri_string(spec);
488 if (!(elem instanceof Ci.nsIDOMNode))
489 throw interactive_error("Element has no associated text to copy.");
490 switch (elem.localName) {
496 if (elem.selectedIndex >= 0)
497 text = elem.item(elem.selectedIndex).text;
500 text = elem.textContent;
504 browser_set_element_focus(buffer, elem);
505 writeToClipboard(text);
506 buffer.window.minibuffer.message("Copied: " + text);
510 define_variable("view_source_use_external_editor", false,
511 "When true, the `view-source' command will send its document to "+
512 "your external editor.");
514 define_variable("view_source_function", null,
515 "May be set to a user-defined function for viewing source code. "+
516 "The function should accept an nsILocalFile of the filename as "+
517 "its one positional argument, and it will also be called with "+
518 "the keyword `$temporary', whose value will be true if the file "+
519 "is considered temporary, and therefore the function must take "+
520 "responsibility for deleting it.");
522 function browser_object_view_source (buffer, target, elem) {
523 if (view_source_use_external_editor || view_source_function) {
524 var spec = load_spec(elem);
526 let [file, temp] = yield download_as_temporary(spec,
528 $action = "View source");
529 if (view_source_use_external_editor)
530 yield open_file_with_external_editor(file, $temporary = temp);
532 yield view_source_function(file, $temporary = temp);
537 var window = buffer.window;
538 if (elem.localName) {
539 switch (elem.localName.toLowerCase()) {
540 case "frame": case "iframe":
541 win = elem.contentWindow;
544 view_mathml_source(window, charset, elem);
547 throw new Error("Invalid browser element");
553 var url_s = win.location.href;
554 if (url_s.substring (0,12) != "view-source:") {
556 browser_object_follow(buffer, target, "view-source:" + url_s);
557 } catch(e) { dump_error(e); }
559 window.minibuffer.message ("Already viewing source");
563 function view_source (I, target) {
566 target = OPEN_CURRENT_BUFFER;
567 var element = yield read_browser_object(I);
568 yield browser_object_view_source(I.buffer, target, element);
571 function view_source_new_buffer (I) {
572 yield view_source(I, OPEN_NEW_BUFFER);
575 function view_source_new_window (I) {
576 yield view_source(I, OPEN_NEW_WINDOW);
580 function browser_element_shell_command (buffer, elem, command, cwd) {
581 var spec = load_spec(elem);
582 yield download_as_temporary(spec,
584 $shell_command = command,
585 $shell_command_cwd = cwd);