view-as-mime-type: fix bug in interactive declaration
[conkeror.git] / modules / element.js
blob2017e4b84b5d2f88f2942911ae4cf90016bfb418
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 function browser_object_class (name, label, doc, handler) {
23     this.name = name;
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);
33     interactive(
34         "browser-object-"+name,
35         "A prefix command to specify that the following command operate "+
36             "on objects of type: "+name+".",
37         function (I) { I.browser_object = ob; },
38         $prefix = true);
39     return 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(
45             $buffer = I.buffer,
46             $prompt = prompt,
47             $hint_xpath_expression = xpath_expression);
48         yield co_return(result);
49     };
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         var doc = I.buffer.document;
60         if (doc.getElementsByTagName("frame").length == 0 &&
61             doc.getElementsByTagName("iframe").length == 0)
62         {
63             // only one frame (the top-level one), no need to use the hints system
64             yield co_return(I.buffer.top_frame);
65         }
66         var result = yield I.buffer.window.minibuffer.read_hinted_element(
67             $buffer = I.buffer,
68             $prompt = prompt,
69             $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
70         yield co_return(result);
71     });
73 define_browser_object_class(
74     "links", "link", null,
75     xpath_browser_object_handler (
76         "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
77         "@role='link'] | " +
78         "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " +
79         "//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand] | " +
80         "//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | " +
81         "//xhtml:button | //xhtml:select"));
83 define_browser_object_class(
84     "mathml", "MathML element", null,
85     xpath_browser_object_handler("//m:math"));
87 define_browser_object_class(
88     "top", null, null,
89     function (I, prompt) { yield co_return(I.buffer.top_frame); });
91 define_browser_object_class(
92     "url", null, null,
93     function (I, prompt) {
94         var result = yield I.buffer.window.minibuffer.read_url($prompt = prompt);
95         yield co_return(result);
96     });
98 define_browser_object_class(
99     "pasteurl", null, null,
100     function (I, url) {
101         let url = read_from_x_primary_selection();
102         yield co_return(url);
103     });
105 define_browser_object_class(
106     "file", null, null,
107     function (I, prompt) {
108         var result = yield I.buffer.window.minibuffer.read_file(
109             $prompt = prompt,
110             $history = I.command.name+"/file",
111             $initial_value = I.local.cwd.path);
112         yield co_return(result);
113     });
115 define_browser_object_class(
116     "alt", "Image Alt-text", null,
117     function (I, prompt) {
118         var result = yield I.buffer.window.minibuffer.read_hinted_element(
119             $buffer = I.buffer,
120             $prompt = prompt,
121             $hint_xpath_expression = "//img[@alt]");
122         yield co_return(result.alt);
123     });
125 define_browser_object_class(
126     "title", "Element Title", null,
127     function (I, prompt) {
128         var result = yield I.buffer.window.minibuffer.read_hinted_element(
129             $buffer = I.buffer,
130             $prompt = prompt,
131             $hint_xpath_expression = "//*[@title]");
132         yield co_return(result.title);
133     });
135 define_browser_object_class(
136     "title-or-alt", "Element Title or Alt-text", null,
137     function (I, prompt) {
138         var result = yield I.buffer.window.minibuffer.read_hinted_element(
139             $buffer = I.buffer,
140             $prompt = prompt,
141             $hint_xpath_expression = "//img[@alt] | //*[@title]");
142         yield co_return(result.title ? result.title : result.alt);
143     });
145 define_browser_object_class(
146     "scrape-url", "url",
147     "Scrapes urls from the source code of the top-level document of buffer.",
148     function (I, prompt) {
149         var completions = I.buffer.document.documentElement.innerHTML
150             .match(/http:[^\s>"]*/g)
151             .filter(remove_duplicates_filter());
152         var completer = prefix_completer($completions = completions);
153         var result = yield I.buffer.window.minibuffer.read(
154             $prompt = prompt,
155             $completer = completer,
156             $initial_value = null,
157             $auto_complete = "url",
158             $select,
159             $match_required = false);
160         yield co_return(result);
161     });
163 define_browser_object_class(
164     "up-url", "Up Url", null,
165     function (I, prompt) {
166         var up = compute_url_up_path(I.buffer.current_URI.spec);
167         return I.buffer.current_URI.resolve(up);
168     });
170 define_browser_object_class(
171     "focused-element", "Focused element", null,
172     function (I, prompt) {
173         return I.buffer.focused_element;
174     });
176 function read_browser_object (I) {
177     var browser_object = I.browser_object;
178     // literals cannot be overridden
179     if (browser_object instanceof Function)
180         yield co_return(browser_object());
181     if (! (browser_object instanceof browser_object_class))
182         yield co_return(browser_object);
184     var prompt = I.command.prompt;
185     if (! prompt) {
186         prompt = I.command.name.split(/-|_/).join(" ");
187         prompt = prompt[0].toUpperCase() + prompt.substring(1);
188     }
189     if (I.target != null)
190         prompt += TARGET_PROMPTS[I.target];
191     if (browser_object.label)
192         prompt += " (select " + browser_object.label + ")";
193     prompt += ":";
195     var result = yield browser_object.handler.call(null, I, prompt);
196     yield co_return(result);
201  * This is a simple wrapper function that sets focus to elem, and
202  * bypasses the automatic focus prevention system, which might
203  * otherwise prevent this from happening.
204  */
205 function browser_set_element_focus (buffer, elem, prevent_scroll) {
206     if (!element_dom_node_or_window_p(elem))
207         return;
209     buffer.last_user_input_received = Date.now();
210     if (prevent_scroll)
211         set_focus_no_scroll(buffer.window, elem);
212     else
213         elem.focus();
216 function browser_element_focus (buffer, elem) {
217     if (!element_dom_node_or_window_p(elem))
218         return;
220     if (elem instanceof Ci.nsIDOMXULTextBoxElement)
221         elem = elem.wrappedJSObject.inputField; // focus the input field
223     browser_set_element_focus(buffer, elem);
224     if (elem instanceof Ci.nsIDOMWindow)
225         return;
227     // If it is not a window, it must be an HTML element
228     var x = 0;
229     var y = 0;
230     if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
231         elem instanceof Ci.nsIDOMHTMLIFrameElement)
232     {
233         elem.contentWindow.focus();
234         return;
235     }
236     if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
237         var coords = elem.getAttribute("coords").split(",");
238         x = Number(coords[0]);
239         y = Number(coords[1]);
240     }
242     var doc = elem.ownerDocument;
243     var evt = doc.createEvent("MouseEvents");
245     evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
246     elem.dispatchEvent(evt);
249 function browser_object_follow (buffer, target, elem) {
250     // XXX: would be better to let nsILocalFile objects be load_specs
251     if (elem instanceof Ci.nsILocalFile)
252         elem = elem.path;
254     var e;
255     if (elem instanceof load_spec)
256         e = load_spec_element(elem);
257     if (! e)
258         e = elem;
260     browser_set_element_focus(buffer, e, true /* no scroll */);
262     var no_click = ((e instanceof load_spec) ||
263                     (e instanceof Ci.nsIDOMWindow) ||
264                     (e instanceof Ci.nsIDOMHTMLFrameElement) ||
265                     (e instanceof Ci.nsIDOMHTMLIFrameElement) ||
266                     (e instanceof Ci.nsIDOMHTMLLinkElement) ||
267                     (e instanceof Ci.nsIDOMHTMLImageElement &&
268                      !e.hasAttribute("onmousedown") && !e.hasAttribute("onclick")));
270     if (target == FOLLOW_DEFAULT && !no_click) {
271         var x = 1, y = 1;
272         if (e instanceof Ci.nsIDOMHTMLAreaElement) {
273             var coords = e.getAttribute("coords").split(",");
274             if (coords.length >= 2) {
275                 x = Number(coords[0]) + 1;
276                 y = Number(coords[1]) + 1;
277             }
278         }
279         dom_node_click(e, x, y);
280         return;
281     }
283     var spec = load_spec(elem);
285     if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
286         // it is nonsensical to follow a javascript url in a different
287         // buffer or window
288         target = FOLLOW_DEFAULT;
289     } else if (!(buffer instanceof content_buffer) &&
290         (target == FOLLOW_CURRENT_FRAME ||
291          target == FOLLOW_DEFAULT ||
292          target == OPEN_CURRENT_BUFFER))
293     {
294         target = OPEN_NEW_BUFFER;
295     }
297     switch (target) {
298     case FOLLOW_CURRENT_FRAME:
299         var current_frame = load_spec_source_frame(spec);
300         if (current_frame && current_frame != buffer.top_frame) {
301             var target_obj = get_web_navigation_for_frame(current_frame);
302             apply_load_spec(target_obj, spec);
303             break;
304         }
305     case FOLLOW_DEFAULT:
306     case OPEN_CURRENT_BUFFER:
307         buffer.load(spec);
308         break;
309     case OPEN_NEW_WINDOW:
310     case OPEN_NEW_BUFFER:
311     case OPEN_NEW_BUFFER_BACKGROUND:
312         create_buffer(buffer.window,
313                       buffer_creator(content_buffer,
314                                      $opener = buffer,
315                                      $load = spec),
316                       target);
317     }
321  * Follow a link-like element by generating fake mouse events.
322  */
323 function dom_node_click (elem, x, y) {
324     var doc = elem.ownerDocument;
325     var view = doc.defaultView;
327     var evt = doc.createEvent("MouseEvents");
328     evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
329                        /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
330     elem.dispatchEvent(evt);
332     evt = doc.createEvent("MouseEvents");
333     evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
334                        /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
335     elem.dispatchEvent(evt);
339 function follow (I, target) {
340     if (target == null)
341         target = FOLLOW_DEFAULT;
342     I.target = target;
343     if (target == OPEN_CURRENT_BUFFER)
344         check_buffer(I.buffer, content_buffer);
345     var element = yield read_browser_object(I);
346     try {
347         element = load_spec(element);
348         if (I.forced_charset)
349             element.forced_charset = I.forced_charset;
350     } catch (e) {}
351     browser_object_follow(I.buffer, target, element);
354 function follow_new_buffer (I) {
355     yield follow(I, OPEN_NEW_BUFFER);
358 function follow_new_buffer_background (I) {
359     yield follow(I, OPEN_NEW_BUFFER_BACKGROUND);
362 function follow_new_window (I) {
363     yield follow(I, OPEN_NEW_WINDOW);
366 function follow_current_frame (I) {
367     yield follow(I, FOLLOW_CURRENT_FRAME);
370 function follow_current_buffer (I) {
371     yield follow(I, OPEN_CURRENT_BUFFER);
375 function element_get_load_target_label (element) {
376     if (element instanceof Ci.nsIDOMWindow)
377         return "page";
378     if (element instanceof Ci.nsIDOMHTMLFrameElement)
379         return "frame";
380     if (element instanceof Ci.nsIDOMHTMLIFrameElement)
381         return "iframe";
382     return null;
385 function element_get_operation_label (element, op_name, suffix) {
386     var target_label = element_get_load_target_label(element);
387     if (target_label != null)
388         target_label = " " + target_label;
389     else
390         target_label = "";
392     if (suffix != null)
393         suffix = " " + suffix;
394     else
395         suffix = "";
397     return op_name + target_label + suffix + ":";
401 function browser_element_copy (buffer, elem) {
402     var spec;
403     try {
404        spec = load_spec(elem);
405     } catch (e) {}
406     var text = null;
407     if (spec)
408         text = load_spec_uri_string(spec);
409     else  {
410         if (!(elem instanceof Ci.nsIDOMNode))
411             throw interactive_error("Element has no associated text to copy.");
412         switch (elem.localName) {
413         case "INPUT":
414         case "TEXTAREA":
415             text = elem.value;
416             break;
417         case "SELECT":
418             if (elem.selectedIndex >= 0)
419                 text = elem.item(elem.selectedIndex).text;
420             break;
421         default:
422             text = elem.textContent;
423             break;
424         }
425     }
426     browser_set_element_focus(buffer, elem);
427     writeToClipboard(text);
428     buffer.window.minibuffer.message("Copied: " + text);
432 define_variable("view_source_use_external_editor", false,
433     "When true, the `view-source' command will send its document to "+
434     "your external editor.");
436 define_variable("view_source_function", null,
437     "May be set to a user-defined function for viewing source code. "+
438     "The function should accept an nsILocalFile of the filename as "+
439     "its one positional argument, and it will also be called with "+
440     "the keyword `$temporary', whose value will be true if the file "+
441     "is considered temporary, and therefore the function must take "+
442     "responsibility for deleting it.");
444 function browser_object_view_source (buffer, target, elem) {
445     if (view_source_use_external_editor || view_source_function) {
446         var spec = load_spec(elem);
448         let [file, temp] = yield download_as_temporary(spec,
449                                                        $buffer = buffer,
450                                                        $action = "View source");
451         if (view_source_use_external_editor)
452             yield open_file_with_external_editor(file, $temporary = temp);
453         else
454             yield view_source_function(file, $temporary = temp);
455         return;
456     }
458     var win = null;
459     var window = buffer.window;
460     if (elem.localName) {
461         switch (elem.localName.toLowerCase()) {
462         case "frame": case "iframe":
463             win = elem.contentWindow;
464             break;
465         case "math":
466             view_mathml_source(window, charset, elem);
467             return;
468         default:
469             throw new Error("Invalid browser element");
470         }
471     } else
472         win = elem;
473     win.focus();
475     var url_s = win.location.href;
476     if (url_s.substring (0,12) != "view-source:") {
477         try {
478             browser_object_follow(buffer, target, "view-source:" + url_s);
479         } catch(e) { dump_error(e); }
480     } else {
481         window.minibuffer.message ("Already viewing source");
482     }
485 function view_source (I, target) {
486     I.target = target;
487     if (target == null)
488         target = OPEN_CURRENT_BUFFER;
489     var element = yield read_browser_object(I);
490     yield browser_object_view_source(I.buffer, target, element);
493 function view_source_new_buffer (I) {
494     yield view_source(I, OPEN_NEW_BUFFER);
497 function view_source_new_window (I) {
498     yield view_source(I, OPEN_NEW_WINDOW);
502 function browser_element_shell_command (buffer, elem, command, cwd) {
503     var spec = load_spec(elem);
504     yield download_as_temporary(spec,
505                                 $buffer = buffer,
506                                 $shell_command = command,
507                                 $shell_command_cwd = cwd);