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 for each (let x in doc.getElementsByTagName("iframe")) {
107 let style = topwin.getComputedStyle(x, "");
108 if (style.display == "none" || style.visibility == "hidden")
115 // only one frame (the top-level one), no need to use the hints system
116 yield co_return(I.buffer.top_frame);
118 var result = yield I.buffer.window.minibuffer.read_hinted_element(
121 $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
122 yield co_return(result);
124 $hint = "select frame");
126 define_browser_object_class("links",
127 "Browser object class for selecting a hyperlink, form field, "+
128 "or link-like element, via hinting.",
129 xpath_browser_object_handler(
130 "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
132 "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " +
133 "//*[@contenteditable = 'true']"),
134 $hint = "select link");
136 define_browser_object_class("mathml",
137 "Browser object class for selecting a MathML node via hinting.",
138 xpath_browser_object_handler("//m:math"),
139 $hint = "select MathML element");
141 define_browser_object_class("top",
142 "Browser object class which returns the top frame of the document.",
143 function (I, prompt) { return I.buffer.top_frame; });
145 define_browser_object_class("url",
146 "Browser object class which prompts the user for an url or webjump.",
147 function (I, prompt) {
148 var result = yield I.buffer.window.minibuffer.read_url($prompt = prompt);
149 yield co_return(result);
151 $hint = "enter URL/webjump");
153 define_browser_object_class("paste-url",
154 "Browser object which reads an url from the X Primary Selection, "+
155 "falling back on the clipboard for operating systems which lack one.",
156 function (I, prompt) {
157 var url = read_from_x_primary_selection();
159 url = url.replace(/^\s*|\s*$/,"");
160 // add http:// if needed
161 if (url.match(/^[^:]+\./)) {
162 url = "http://" + url;
165 return make_uri(url).spec;
167 throw new interactive_error("error: malformed url: "+url);
171 define_browser_object_class("file",
172 "Browser object which prompts for a file name.",
173 function (I, prompt) {
174 var result = yield I.buffer.window.minibuffer.read_file(
176 $history = I.command.name+"/file",
177 $initial_value = I.local.cwd.path);
178 yield co_return(result);
180 $hint = "enter file name");
182 define_browser_object_class("alt",
183 "Browser object class which returns the alt text of an html:img, "+
184 "selected via hinting",
185 function (I, prompt) {
186 var result = yield I.buffer.window.minibuffer.read_hinted_element(
189 $hint_xpath_expression = "//img[@alt]");
190 yield co_return(result.alt);
192 $hint = "select image for alt-text");
194 define_browser_object_class("title",
195 "Browser object class which returns the title attribute of an element, "+
196 "selected via hinting",
197 function (I, prompt) {
198 var result = yield I.buffer.window.minibuffer.read_hinted_element(
201 $hint_xpath_expression = "//*[@title]");
202 yield co_return(result.title);
204 $hint = "select element for title attribute");
206 define_browser_object_class("title-or-alt",
207 "Browser object which is the union of browser-object-alt and "+
208 "browser-object-title, with title having higher precedence in "+
209 "the case of an element that has both.",
210 function (I, prompt) {
211 var result = yield I.buffer.window.minibuffer.read_hinted_element(
214 $hint_xpath_expression = "//img[@alt] | //*[@title]");
215 yield co_return(result.title ? result.title : result.alt);
217 $hint = "select element for title or alt-text");
219 define_browser_object_class("scrape-url",
220 "Browser object which lets the user choose an url from a list of "+
221 "urls scraped from the source code of the document.",
222 function (I, prompt) {
223 var completions = I.buffer.document.documentElement.innerHTML
224 .match(/http:[^\s>"]*/g)
225 .filter(remove_duplicates_filter());
226 var completer = prefix_completer($completions = completions);
227 var result = yield I.buffer.window.minibuffer.read(
229 $completer = completer,
230 $initial_value = null,
231 $auto_complete = "url",
233 $match_required = false);
234 yield co_return(result);
236 $hint = "choose scraped URL");
238 define_browser_object_class("up-url",
239 "Browser object which returns the url one level above the current one.",
240 function (I, prompt) {
241 var up = compute_url_up_path(I.buffer.current_uri.spec);
242 return I.buffer.current_uri.resolve(up);
245 define_browser_object_class("focused-element",
246 "Browser object which returns the focused element.",
247 function (I, prompt) { return I.buffer.focused_element; });
249 define_browser_object_class("dom-node", null,
250 xpath_browser_object_handler("//*"),
251 $hint = "select DOM node");
253 function read_browser_object (I) {
254 var browser_object = I.browser_object;
255 // literals cannot be overridden
256 if (browser_object instanceof Function)
257 yield co_return(browser_object(I));
258 if (! (browser_object instanceof browser_object_class))
259 yield co_return(browser_object);
261 var prompt = I.command.prompt;
263 prompt = I.command.name.split(/-|_/).join(" ");
264 prompt = prompt[0].toUpperCase() + prompt.substring(1);
266 if (I.target != null)
267 prompt += TARGET_PROMPTS[I.target];
268 if (browser_object.hint)
269 prompt += " (" + browser_object.hint + ")";
272 var result = yield browser_object.handler.call(null, I, prompt);
273 yield co_return(result);
278 * This is a simple wrapper function that sets focus to elem, and
279 * bypasses the automatic focus prevention system, which might
280 * otherwise prevent this from happening.
282 function browser_set_element_focus (buffer, elem, prevent_scroll) {
283 if (!element_dom_node_or_window_p(elem))
286 set_focus_no_scroll(buffer.window, elem);
291 function browser_element_focus (buffer, elem) {
292 if (!element_dom_node_or_window_p(elem))
295 if (elem instanceof Ci.nsIDOMXULTextBoxElement)
296 elem = elem.wrappedJSObject.inputField; // focus the input field
298 browser_set_element_focus(buffer, elem);
299 if (elem instanceof Ci.nsIDOMWindow)
302 // If it is not a window, it must be an HTML element
305 if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
306 elem instanceof Ci.nsIDOMHTMLIFrameElement)
308 elem.contentWindow.focus();
311 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
312 var coords = elem.getAttribute("coords").split(",");
313 x = Number(coords[0]);
314 y = Number(coords[1]);
317 var doc = elem.ownerDocument;
318 var evt = doc.createEvent("MouseEvents");
320 evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
321 elem.dispatchEvent(evt);
324 function browser_object_follow (buffer, target, elem) {
325 // XXX: would be better to let nsILocalFile objects be load_specs
326 if (elem instanceof Ci.nsILocalFile)
330 if (elem instanceof load_spec)
331 e = load_spec_element(elem);
335 browser_set_element_focus(buffer, e, true /* no scroll */);
337 var no_click = (((elem instanceof load_spec) &&
338 load_spec_forced_charset(elem)) ||
339 (e instanceof load_spec) ||
340 (e instanceof Ci.nsIDOMWindow) ||
341 (e instanceof Ci.nsIDOMHTMLFrameElement) ||
342 (e instanceof Ci.nsIDOMHTMLIFrameElement) ||
343 (e instanceof Ci.nsIDOMHTMLLinkElement) ||
344 (e instanceof Ci.nsIDOMHTMLImageElement &&
345 !e.hasAttribute("onmousedown") && !e.hasAttribute("onclick")));
347 if (target == FOLLOW_DEFAULT && !no_click) {
349 if (e instanceof Ci.nsIDOMHTMLAreaElement) {
350 var coords = e.getAttribute("coords").split(",");
351 if (coords.length >= 2) {
352 x = Number(coords[0]) + 1;
353 y = Number(coords[1]) + 1;
356 dom_node_click(e, x, y);
360 var spec = load_spec(elem);
362 if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
363 // it is nonsensical to follow a javascript url in a different
365 target = FOLLOW_DEFAULT;
366 } else if (!(buffer instanceof content_buffer) &&
367 (target == FOLLOW_CURRENT_FRAME ||
368 target == FOLLOW_DEFAULT ||
369 target == OPEN_CURRENT_BUFFER))
371 target = OPEN_NEW_BUFFER;
375 case FOLLOW_CURRENT_FRAME:
376 var current_frame = load_spec_source_frame(spec);
377 if (current_frame && current_frame != buffer.top_frame) {
378 var target_obj = get_web_navigation_for_frame(current_frame);
379 apply_load_spec(target_obj, spec);
383 case OPEN_CURRENT_BUFFER:
386 case OPEN_NEW_WINDOW:
387 case OPEN_NEW_BUFFER:
388 case OPEN_NEW_BUFFER_BACKGROUND:
389 create_buffer(buffer.window,
390 buffer_creator(content_buffer,
398 * Follow a link-like element by generating fake mouse events.
400 function dom_node_click (elem, x, y) {
401 var doc = elem.ownerDocument;
402 var view = doc.defaultView;
404 var evt = doc.createEvent("MouseEvents");
405 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
406 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
407 elem.dispatchEvent(evt);
409 evt = doc.createEvent("MouseEvents");
410 evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
411 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
412 elem.dispatchEvent(evt);
416 function follow (I, target) {
418 target = FOLLOW_DEFAULT;
420 if (target == OPEN_CURRENT_BUFFER)
421 check_buffer(I.buffer, content_buffer);
422 var element = yield read_browser_object(I);
424 element = load_spec(element);
425 if (I.forced_charset)
426 element.forced_charset = I.forced_charset;
428 browser_object_follow(I.buffer, target, element);
431 function follow_new_buffer (I) {
432 yield follow(I, OPEN_NEW_BUFFER);
435 function follow_new_buffer_background (I) {
436 yield follow(I, OPEN_NEW_BUFFER_BACKGROUND);
439 function follow_new_window (I) {
440 yield follow(I, OPEN_NEW_WINDOW);
443 function follow_current_frame (I) {
444 yield follow(I, FOLLOW_CURRENT_FRAME);
447 function follow_current_buffer (I) {
448 yield follow(I, OPEN_CURRENT_BUFFER);
452 function element_get_load_target_label (element) {
453 if (element instanceof Ci.nsIDOMWindow)
455 if (element instanceof Ci.nsIDOMHTMLFrameElement)
457 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
462 function element_get_operation_label (element, op_name, suffix) {
463 var target_label = element_get_load_target_label(element);
464 if (target_label != null)
465 target_label = " " + target_label;
470 suffix = " " + suffix;
474 return op_name + target_label + suffix + ":";
478 function browser_element_copy (buffer, elem) {
481 spec = load_spec(elem);
485 text = load_spec_uri_string(spec);
487 if (!(elem instanceof Ci.nsIDOMNode))
488 throw interactive_error("Element has no associated text to copy.");
489 switch (elem.localName) {
495 if (elem.selectedIndex >= 0)
496 text = elem.item(elem.selectedIndex).text;
499 text = elem.textContent;
503 browser_set_element_focus(buffer, elem);
504 writeToClipboard(text);
505 buffer.window.minibuffer.message("Copied: " + text);
509 define_variable("view_source_use_external_editor", false,
510 "When true, the `view-source' command will send its document to "+
511 "your external editor.");
513 define_variable("view_source_function", null,
514 "May be set to a user-defined function for viewing source code. "+
515 "The function should accept an nsILocalFile of the filename as "+
516 "its one positional argument, and it will also be called with "+
517 "the keyword `$temporary', whose value will be true if the file "+
518 "is considered temporary, and therefore the function must take "+
519 "responsibility for deleting it.");
521 function browser_object_view_source (buffer, target, elem) {
522 if (view_source_use_external_editor || view_source_function) {
523 var spec = load_spec(elem);
525 let [file, temp] = yield download_as_temporary(spec,
527 $action = "View source");
528 if (view_source_use_external_editor)
529 yield open_file_with_external_editor(file, $temporary = temp);
531 yield view_source_function(file, $temporary = temp);
536 var window = buffer.window;
537 if (elem.localName) {
538 switch (elem.localName.toLowerCase()) {
539 case "frame": case "iframe":
540 win = elem.contentWindow;
543 view_mathml_source(window, charset, elem);
546 throw new Error("Invalid browser element");
552 var url_s = win.location.href;
553 if (url_s.substring (0,12) != "view-source:") {
555 browser_object_follow(buffer, target, "view-source:" + url_s);
556 } catch(e) { dump_error(e); }
558 window.minibuffer.message ("Already viewing source");
562 function view_source (I, target) {
565 target = OPEN_CURRENT_BUFFER;
566 var element = yield read_browser_object(I);
567 yield browser_object_view_source(I.buffer, target, element);
570 function view_source_new_buffer (I) {
571 yield view_source(I, OPEN_NEW_BUFFER);
574 function view_source_new_window (I) {
575 yield view_source(I, OPEN_NEW_WINDOW);
579 function browser_element_shell_command (buffer, elem, command, cwd) {
580 var spec = load_spec(elem);
581 yield download_as_temporary(spec,
583 $shell_command = command,
584 $shell_command_cwd = cwd);