define_browser_object_class: changed arg `hint` to keyword `$hint`
[conkeror/arlinius.git] / modules / element.js
blob4f7479bb943f751045c891cc1a71e94d16cfe860
1 /**
2  * (C) Copyright 2007-2008 John J. Foerch
3  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
4  *
5  * Portions of this file are derived from Vimperator,
6  * (C) Copyright 2006-2007 Martin Stubenschrott.
7  *
8  * Use, modification, and distribution are subject to the terms specified in the
9  * COPYING file.
10 **/
12 require("hints.js");
13 require("save.js");
14 require("mime-type-override.js");
15 require("minibuffer-read-mime-type.js");
17 var browser_object_classes = {};
19 /**
20  * handler is a coroutine called as: handler(buffer, prompt)
21  *
22  * $hint: short string (usually verb and noun) to describe the UI
23  *        of the browser object class to the user.  Only used by
24  *        browser object classes which make use of the minibuffer.
25  */
26 define_keywords("$hint");
27 function browser_object_class (name, doc, handler) {
28     keywords(arguments);
29     this.name = name;
30     this.handler = handler;
31     this.doc = doc;
32     this.hint = arguments.$hint;
35 // keywords: $hint
36 function define_browser_object_class (name, doc, handler) {
37     keywords(arguments);
38     var varname = 'browser_object_'+name.replace('-','_','g');
39     var ob = conkeror[varname] =
40         new browser_object_class(name, doc, handler,
41                                  forward_keywords(arguments));
42     interactive(
43         "browser-object-"+name,
44         "A prefix command to specify that the following command operate "+
45             "on objects of type: "+name+".",
46         function (I) { I.browser_object = ob; },
47         $prefix = true);
48     return ob;
51 function xpath_browser_object_handler (xpath_expression) {
52     return function (I, prompt) {
53         var result = yield I.buffer.window.minibuffer.read_hinted_element(
54             $buffer = I.buffer,
55             $prompt = prompt,
56             $hint_xpath_expression = xpath_expression);
57         yield co_return(result);
58     };
61 define_browser_object_class("images", null,
62     xpath_browser_object_handler("//img | //xhtml:img"),
63     $hint = "select image");
65 define_browser_object_class("frames", null,
66     function (I, prompt) {
67         var doc = I.buffer.document;
68         // Check for any frames or visible iframes
69         var skip_hints = true;
70         if (doc.getElementsByTagName("frame").length > 0)
71             skip_hints = false;
72         else {
73             let topwin = I.buffer.top_frame;
74             for each (let x in doc.getElementsByTagName("iframe"))
75             {
76                 let style = topwin.getComputedStyle(x, "");
77                 if (style.display == "none" || style.visibility == "hidden")
78                     continue;
79                 skip_hints = false;
80                 break;
81             }
82         }
83         if (skip_hints) {
84             // only one frame (the top-level one), no need to use the hints system
85             yield co_return(I.buffer.top_frame);
86         }
87         var result = yield I.buffer.window.minibuffer.read_hinted_element(
88             $buffer = I.buffer,
89             $prompt = prompt,
90             $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
91         yield co_return(result);
92     },
93     $hint = "select frame");
95 define_browser_object_class("links", null,
96     xpath_browser_object_handler (
97         "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
98         "@role='link'] | " +
99         "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " +
100         "//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand] | " +
101         "//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | " +
102         "//xhtml:button | //xhtml:select"),
103     $hint = "select link");
105 define_browser_object_class("mathml", null,
106     xpath_browser_object_handler("//m:math"),
107     $hint = "select MathML element");
109 define_browser_object_class("top", null,
110     function (I, prompt) { yield co_return(I.buffer.top_frame); });
112 define_browser_object_class("url", null,
113     function (I, prompt) {
114         var result = yield I.buffer.window.minibuffer.read_url($prompt = prompt);
115         yield co_return(result);
116     },
117     $hint = "enter URL");
119 define_browser_object_class("pasteurl", null,
120     function (I, url) {
121         let url = read_from_x_primary_selection();
122         yield co_return(url);
123     });
125 define_browser_object_class("file", null,
126     function (I, prompt) {
127         var result = yield I.buffer.window.minibuffer.read_file(
128             $prompt = prompt,
129             $history = I.command.name+"/file",
130             $initial_value = I.local.cwd.path);
131         yield co_return(result);
132     },
133     $hint = "enter file name");
135 define_browser_object_class("alt", null,
136     function (I, prompt) {
137         var result = yield I.buffer.window.minibuffer.read_hinted_element(
138             $buffer = I.buffer,
139             $prompt = prompt,
140             $hint_xpath_expression = "//img[@alt]");
141         yield co_return(result.alt);
142     },
143     $hint = "select image for alt-text");
145 define_browser_object_class("title",
146     null,
147     function (I, prompt) {
148         var result = yield I.buffer.window.minibuffer.read_hinted_element(
149             $buffer = I.buffer,
150             $prompt = prompt,
151             $hint_xpath_expression = "//*[@title]");
152         yield co_return(result.title);
153     },
154     $hint = "select element for title attribute");
156 define_browser_object_class("title-or-alt", null,
157     function (I, prompt) {
158         var result = yield I.buffer.window.minibuffer.read_hinted_element(
159             $buffer = I.buffer,
160             $prompt = prompt,
161             $hint_xpath_expression = "//img[@alt] | //*[@title]");
162         yield co_return(result.title ? result.title : result.alt);
163     },
164     $hint = "select element for title or alt-text");
166 define_browser_object_class("scrape-url",
167     "Scrapes urls from the source code of the top-level document of buffer.",
168     function (I, prompt) {
169         var completions = I.buffer.document.documentElement.innerHTML
170             .match(/http:[^\s>"]*/g)
171             .filter(remove_duplicates_filter());
172         var completer = prefix_completer($completions = completions);
173         var result = yield I.buffer.window.minibuffer.read(
174             $prompt = prompt,
175             $completer = completer,
176             $initial_value = null,
177             $auto_complete = "url",
178             $select,
179             $match_required = false);
180         yield co_return(result);
181     },
182     $hint = "choose scraped URL");
184 define_browser_object_class("up-url", null,
185     function (I, prompt) {
186         var up = compute_url_up_path(I.buffer.current_URI.spec);
187         return I.buffer.current_URI.resolve(up);
188     });
190 define_browser_object_class("focused-element", null,
191     function (I, prompt) {
192         return I.buffer.focused_element;
193     });
195 define_browser_object_class("dom-node", null,
196     xpath_browser_object_handler("//*"),
197     $hint = "select DOM node");
199 function read_browser_object (I) {
200     var browser_object = I.browser_object;
201     // literals cannot be overridden
202     if (browser_object instanceof Function)
203         yield co_return(browser_object());
204     if (! (browser_object instanceof browser_object_class))
205         yield co_return(browser_object);
207     var prompt = I.command.prompt;
208     if (! prompt) {
209         prompt = I.command.name.split(/-|_/).join(" ");
210         prompt = prompt[0].toUpperCase() + prompt.substring(1);
211     }
212     if (I.target != null)
213         prompt += TARGET_PROMPTS[I.target];
214     if (browser_object.hint)
215         prompt += " (" + browser_object.hint + ")";
216     prompt += ":";
218     var result = yield browser_object.handler.call(null, I, prompt);
219     yield co_return(result);
224  * This is a simple wrapper function that sets focus to elem, and
225  * bypasses the automatic focus prevention system, which might
226  * otherwise prevent this from happening.
227  */
228 function browser_set_element_focus (buffer, elem, prevent_scroll) {
229     if (!element_dom_node_or_window_p(elem))
230         return;
232     buffer.last_user_input_received = Date.now();
233     if (prevent_scroll)
234         set_focus_no_scroll(buffer.window, elem);
235     else
236         elem.focus();
239 function browser_element_focus (buffer, elem) {
240     if (!element_dom_node_or_window_p(elem))
241         return;
243     if (elem instanceof Ci.nsIDOMXULTextBoxElement)
244         elem = elem.wrappedJSObject.inputField; // focus the input field
246     browser_set_element_focus(buffer, elem);
247     if (elem instanceof Ci.nsIDOMWindow)
248         return;
250     // If it is not a window, it must be an HTML element
251     var x = 0;
252     var y = 0;
253     if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
254         elem instanceof Ci.nsIDOMHTMLIFrameElement)
255     {
256         elem.contentWindow.focus();
257         return;
258     }
259     if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
260         var coords = elem.getAttribute("coords").split(",");
261         x = Number(coords[0]);
262         y = Number(coords[1]);
263     }
265     var doc = elem.ownerDocument;
266     var evt = doc.createEvent("MouseEvents");
268     evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
269     elem.dispatchEvent(evt);
272 function browser_object_follow (buffer, target, elem) {
273     // XXX: would be better to let nsILocalFile objects be load_specs
274     if (elem instanceof Ci.nsILocalFile)
275         elem = elem.path;
277     var e;
278     if (elem instanceof load_spec)
279         e = load_spec_element(elem);
280     if (! e)
281         e = elem;
283     browser_set_element_focus(buffer, e, true /* no scroll */);
285     var no_click = ((e instanceof load_spec) ||
286                     (e instanceof Ci.nsIDOMWindow) ||
287                     (e instanceof Ci.nsIDOMHTMLFrameElement) ||
288                     (e instanceof Ci.nsIDOMHTMLIFrameElement) ||
289                     (e instanceof Ci.nsIDOMHTMLLinkElement) ||
290                     (e instanceof Ci.nsIDOMHTMLImageElement &&
291                      !e.hasAttribute("onmousedown") && !e.hasAttribute("onclick")));
293     if (target == FOLLOW_DEFAULT && !no_click) {
294         var x = 1, y = 1;
295         if (e instanceof Ci.nsIDOMHTMLAreaElement) {
296             var coords = e.getAttribute("coords").split(",");
297             if (coords.length >= 2) {
298                 x = Number(coords[0]) + 1;
299                 y = Number(coords[1]) + 1;
300             }
301         }
302         dom_node_click(e, x, y);
303         return;
304     }
306     var spec = load_spec(elem);
308     if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
309         // it is nonsensical to follow a javascript url in a different
310         // buffer or window
311         target = FOLLOW_DEFAULT;
312     } else if (!(buffer instanceof content_buffer) &&
313         (target == FOLLOW_CURRENT_FRAME ||
314          target == FOLLOW_DEFAULT ||
315          target == OPEN_CURRENT_BUFFER))
316     {
317         target = OPEN_NEW_BUFFER;
318     }
320     switch (target) {
321     case FOLLOW_CURRENT_FRAME:
322         var current_frame = load_spec_source_frame(spec);
323         if (current_frame && current_frame != buffer.top_frame) {
324             var target_obj = get_web_navigation_for_frame(current_frame);
325             apply_load_spec(target_obj, spec);
326             break;
327         }
328     case FOLLOW_DEFAULT:
329     case OPEN_CURRENT_BUFFER:
330         buffer.load(spec);
331         break;
332     case OPEN_NEW_WINDOW:
333     case OPEN_NEW_BUFFER:
334     case OPEN_NEW_BUFFER_BACKGROUND:
335         create_buffer(buffer.window,
336                       buffer_creator(content_buffer,
337                                      $opener = buffer,
338                                      $load = spec),
339                       target);
340     }
344  * Follow a link-like element by generating fake mouse events.
345  */
346 function dom_node_click (elem, x, y) {
347     var doc = elem.ownerDocument;
348     var view = doc.defaultView;
350     var evt = doc.createEvent("MouseEvents");
351     evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
352                        /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
353     elem.dispatchEvent(evt);
355     evt = doc.createEvent("MouseEvents");
356     evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
357                        /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
358     elem.dispatchEvent(evt);
362 function follow (I, target) {
363     if (target == null)
364         target = FOLLOW_DEFAULT;
365     I.target = target;
366     if (target == OPEN_CURRENT_BUFFER)
367         check_buffer(I.buffer, content_buffer);
368     var element = yield read_browser_object(I);
369     try {
370         element = load_spec(element);
371         if (I.forced_charset)
372             element.forced_charset = I.forced_charset;
373     } catch (e) {}
374     browser_object_follow(I.buffer, target, element);
377 function follow_new_buffer (I) {
378     yield follow(I, OPEN_NEW_BUFFER);
381 function follow_new_buffer_background (I) {
382     yield follow(I, OPEN_NEW_BUFFER_BACKGROUND);
385 function follow_new_window (I) {
386     yield follow(I, OPEN_NEW_WINDOW);
389 function follow_current_frame (I) {
390     yield follow(I, FOLLOW_CURRENT_FRAME);
393 function follow_current_buffer (I) {
394     yield follow(I, OPEN_CURRENT_BUFFER);
398 function element_get_load_target_label (element) {
399     if (element instanceof Ci.nsIDOMWindow)
400         return "page";
401     if (element instanceof Ci.nsIDOMHTMLFrameElement)
402         return "frame";
403     if (element instanceof Ci.nsIDOMHTMLIFrameElement)
404         return "iframe";
405     return null;
408 function element_get_operation_label (element, op_name, suffix) {
409     var target_label = element_get_load_target_label(element);
410     if (target_label != null)
411         target_label = " " + target_label;
412     else
413         target_label = "";
415     if (suffix != null)
416         suffix = " " + suffix;
417     else
418         suffix = "";
420     return op_name + target_label + suffix + ":";
424 function browser_element_copy (buffer, elem) {
425     var spec;
426     try {
427        spec = load_spec(elem);
428     } catch (e) {}
429     var text = null;
430     if (spec)
431         text = load_spec_uri_string(spec);
432     else  {
433         if (!(elem instanceof Ci.nsIDOMNode))
434             throw interactive_error("Element has no associated text to copy.");
435         switch (elem.localName) {
436         case "INPUT":
437         case "TEXTAREA":
438             text = elem.value;
439             break;
440         case "SELECT":
441             if (elem.selectedIndex >= 0)
442                 text = elem.item(elem.selectedIndex).text;
443             break;
444         default:
445             text = elem.textContent;
446             break;
447         }
448     }
449     browser_set_element_focus(buffer, elem);
450     writeToClipboard(text);
451     buffer.window.minibuffer.message("Copied: " + text);
455 define_variable("view_source_use_external_editor", false,
456     "When true, the `view-source' command will send its document to "+
457     "your external editor.");
459 define_variable("view_source_function", null,
460     "May be set to a user-defined function for viewing source code. "+
461     "The function should accept an nsILocalFile of the filename as "+
462     "its one positional argument, and it will also be called with "+
463     "the keyword `$temporary', whose value will be true if the file "+
464     "is considered temporary, and therefore the function must take "+
465     "responsibility for deleting it.");
467 function browser_object_view_source (buffer, target, elem) {
468     if (view_source_use_external_editor || view_source_function) {
469         var spec = load_spec(elem);
471         let [file, temp] = yield download_as_temporary(spec,
472                                                        $buffer = buffer,
473                                                        $action = "View source");
474         if (view_source_use_external_editor)
475             yield open_file_with_external_editor(file, $temporary = temp);
476         else
477             yield view_source_function(file, $temporary = temp);
478         return;
479     }
481     var win = null;
482     var window = buffer.window;
483     if (elem.localName) {
484         switch (elem.localName.toLowerCase()) {
485         case "frame": case "iframe":
486             win = elem.contentWindow;
487             break;
488         case "math":
489             view_mathml_source(window, charset, elem);
490             return;
491         default:
492             throw new Error("Invalid browser element");
493         }
494     } else
495         win = elem;
496     win.focus();
498     var url_s = win.location.href;
499     if (url_s.substring (0,12) != "view-source:") {
500         try {
501             browser_object_follow(buffer, target, "view-source:" + url_s);
502         } catch(e) { dump_error(e); }
503     } else {
504         window.minibuffer.message ("Already viewing source");
505     }
508 function view_source (I, target) {
509     I.target = target;
510     if (target == null)
511         target = OPEN_CURRENT_BUFFER;
512     var element = yield read_browser_object(I);
513     yield browser_object_view_source(I.buffer, target, element);
516 function view_source_new_buffer (I) {
517     yield view_source(I, OPEN_NEW_BUFFER);
520 function view_source_new_window (I) {
521     yield view_source(I, OPEN_NEW_WINDOW);
525 function browser_element_shell_command (buffer, elem, command, cwd) {
526     var spec = load_spec(elem);
527     yield download_as_temporary(spec,
528                                 $buffer = buffer,
529                                 $shell_command = command,
530                                 $shell_command_cwd = cwd);