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 = 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(
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 (I, prompt) {
59 var doc = I.buffer.document;
60 if (doc.getElementsByTagName("frame").length == 0 &&
61 doc.getElementsByTagName("iframe").length == 0)
63 // only one frame (the top-level one), no need to use the hints system
64 yield co_return(I.buffer.top_frame);
66 var result = yield I.buffer.window.minibuffer.read_hinted_element(
69 $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
70 yield co_return(result);
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 " +
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(
89 function (I, prompt) { yield co_return(I.buffer.top_frame); });
91 define_browser_object_class(
93 function (I, prompt) {
94 var result = yield I.buffer.window.minibuffer.read_url($prompt = prompt);
95 yield co_return(result);
98 define_browser_object_class(
99 "pasteurl", null, null,
101 let url = read_from_x_primary_selection();
102 yield co_return(url);
105 define_browser_object_class(
107 function (I, prompt) {
108 var result = yield I.buffer.window.minibuffer.read_file(
110 $history = I.command.name+"/file",
111 $initial_value = I.buffer.cwd.path);
112 yield co_return (result);
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(
121 $hint_xpath_expression = "//img[@alt]");
122 yield (co_return (result.alt));
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(
131 $hint_xpath_expression = "//*[@title]");
132 yield (co_return (result.title));
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(
141 $hint_xpath_expression = "//img[@alt] | //*[@title]");
142 yield (co_return (result.title ? result.title : result.alt));
145 define_browser_object_class(
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(
155 $completer = completer,
156 $initial_value = null,
157 $auto_complete = "url",
159 $match_required = false);
160 yield co_return(result);
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);
170 define_browser_object_class(
171 "focused-element", "Focused element", null,
172 function (I, prompt) {
173 return I.buffer.focused_element;
176 function read_browser_object (I)
178 var browser_object = I.browser_object;
179 // literals cannot be overridden
180 if (browser_object instanceof Function)
181 yield co_return(browser_object());
182 if (! (browser_object instanceof browser_object_class))
183 yield co_return(browser_object);
185 var prompt = I.command.prompt;
187 prompt = I.command.name.split(/-|_/).join(" ");
188 prompt = prompt[0].toUpperCase() + prompt.substring(1);
190 if (I.target != null)
191 prompt += TARGET_PROMPTS[I.target];
192 if (browser_object.label)
193 prompt += " (select " + browser_object.label + ")";
196 var result = yield browser_object.handler.call(null, I, prompt);
197 yield co_return(result);
201 function is_dom_node_or_window(elem) {
202 if (elem instanceof Ci.nsIDOMNode)
204 if (elem instanceof Ci.nsIDOMWindow)
210 * This is a simple wrapper function that sets focus to elem, and
211 * bypasses the automatic focus prevention system, which might
212 * otherwise prevent this from happening.
214 function browser_set_element_focus(buffer, elem, prevent_scroll) {
215 if (!is_dom_node_or_window(elem))
218 buffer.last_user_input_received = Date.now();
220 set_focus_no_scroll(buffer.window, elem);
225 function browser_element_focus(buffer, elem)
227 if (!is_dom_node_or_window(elem))
230 if (elem instanceof Ci.nsIDOMXULTextBoxElement) {
231 // Focus the input field instead
232 elem = elem.wrappedJSObject.inputField;
235 browser_set_element_focus(buffer, elem);
236 if (elem instanceof Ci.nsIDOMWindow) {
239 // If it is not a window, it must be an HTML element
242 if (elem instanceof Ci.nsIDOMHTMLFrameElement || elem instanceof Ci.nsIDOMHTMLIFrameElement) {
243 elem.contentWindow.focus();
246 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
247 var coords = elem.getAttribute("coords").split(",");
248 x = Number(coords[0]);
249 y = Number(coords[1]);
252 var doc = elem.ownerDocument;
253 var evt = doc.createEvent("MouseEvents");
255 evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
256 elem.dispatchEvent(evt);
259 function browser_object_follow(buffer, target, elem)
261 // XXX: would be better to let nsILocalFile objects be load_specs
262 if (elem instanceof Ci.nsILocalFile)
266 if (elem instanceof load_spec)
267 e = load_spec_element(elem);
271 browser_set_element_focus(buffer, e, true /* no scroll */);
273 var no_click = ((e instanceof load_spec) ||
274 (e instanceof Ci.nsIDOMWindow) ||
275 (e instanceof Ci.nsIDOMHTMLFrameElement) ||
276 (e instanceof Ci.nsIDOMHTMLIFrameElement) ||
277 (e instanceof Ci.nsIDOMHTMLLinkElement) ||
278 (e instanceof Ci.nsIDOMHTMLImageElement &&
279 !e.hasAttribute("onmousedown") && !e.hasAttribute("onclick")));
281 if (target == FOLLOW_DEFAULT && !no_click) {
283 if (e instanceof Ci.nsIDOMHTMLAreaElement) {
284 var coords = e.getAttribute("coords").split(",");
285 if (coords.length >= 2) {
286 x = Number(coords[0]) + 1;
287 y = Number(coords[1]) + 1;
290 dom_node_click(e, x, y);
294 var spec = load_spec(elem);
296 if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
297 // it is nonsensical to follow a javascript url in a different
299 target = FOLLOW_DEFAULT;
300 } else if (!(buffer instanceof content_buffer) &&
301 (target == FOLLOW_CURRENT_FRAME ||
302 target == FOLLOW_DEFAULT ||
303 target == OPEN_CURRENT_BUFFER))
305 target = OPEN_NEW_BUFFER;
309 case FOLLOW_CURRENT_FRAME:
310 var current_frame = load_spec_source_frame(spec);
311 if (current_frame && current_frame != buffer.top_frame) {
312 var target_obj = get_web_navigation_for_frame(current_frame);
313 apply_load_spec(target_obj, spec);
317 case OPEN_CURRENT_BUFFER:
320 case OPEN_NEW_WINDOW:
321 case OPEN_NEW_BUFFER:
322 case OPEN_NEW_BUFFER_BACKGROUND:
323 create_buffer(buffer.window,
324 buffer_creator(content_buffer,
326 $configuration = buffer.configuration),
332 * Follow a link-like element by generating fake mouse events.
334 function dom_node_click (elem, x, y) {
335 var doc = elem.ownerDocument;
336 var view = doc.defaultView;
338 var evt = doc.createEvent("MouseEvents");
339 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
340 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
341 elem.dispatchEvent(evt);
343 evt = doc.createEvent("MouseEvents");
344 evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
345 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
346 elem.dispatchEvent(evt);
350 function follow (I, target) {
352 target = FOLLOW_DEFAULT;
354 if (target == OPEN_CURRENT_BUFFER)
355 check_buffer (I.buffer, content_buffer);
356 var element = yield read_browser_object(I);
358 element = load_spec(element);
359 if (I.forced_charset)
360 element.forced_charset = I.forced_charset;
362 browser_object_follow(I.buffer, target, element);
365 function follow_new_buffer (I) {
366 yield follow(I, OPEN_NEW_BUFFER);
369 function follow_new_buffer_background (I) {
370 yield follow(I, OPEN_NEW_BUFFER_BACKGROUND);
373 function follow_new_window (I) {
374 yield follow(I, OPEN_NEW_WINDOW);
377 function follow_current_frame (I) {
378 yield follow(I, FOLLOW_CURRENT_FRAME);
381 function follow_current_buffer (I) {
382 yield follow(I, OPEN_CURRENT_BUFFER);
386 function element_get_load_target_label(element) {
387 if (element instanceof Ci.nsIDOMWindow)
389 if (element instanceof Ci.nsIDOMHTMLFrameElement)
391 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
396 function element_get_operation_label(element, op_name, suffix) {
397 var target_label = element_get_load_target_label(element);
398 if (target_label != null)
399 target_label = " " + target_label;
404 suffix = " " + suffix;
408 return op_name + target_label + suffix + ":";
412 function browser_element_copy(buffer, elem)
416 spec = load_spec(elem);
420 text = load_spec_uri_string(spec);
422 if (!(elem instanceof Ci.nsIDOMNode))
423 throw interactive_error("Element has no associated text to copy.");
424 switch (elem.localName) {
430 if (elem.selectedIndex >= 0)
431 text = elem.item(elem.selectedIndex).text;
434 text = elem.textContent;
438 browser_set_element_focus(buffer, elem);
439 writeToClipboard (text);
440 buffer.window.minibuffer.message ("Copied: " + text);
444 var view_source_use_external_editor = false, view_source_function = null;
445 function browser_object_view_source(buffer, target, elem)
447 if (view_source_use_external_editor || view_source_function)
449 var spec = load_spec(elem);
451 let [file, temp] = yield download_as_temporary(spec,
453 $action = "View source");
454 if (view_source_use_external_editor)
455 yield open_file_with_external_editor(file, $temporary = temp);
457 yield view_source_function(file, $temporary = temp);
462 var window = buffer.window;
463 if (elem.localName) {
464 switch (elem.localName.toLowerCase()) {
465 case "frame": case "iframe":
466 win = elem.contentWindow;
469 view_mathml_source (window, charset, elem);
472 throw new Error("Invalid browser element");
478 var url_s = win.location.href;
479 if (url_s.substring (0,12) != "view-source:") {
481 browser_object_follow(buffer, target, "view-source:" + url_s);
482 } catch(e) { dump_error(e); }
484 window.minibuffer.message ("Already viewing source");
488 function view_source (I, target) {
490 var element = yield read_browser_object(I);
491 yield browser_object_view_source(I.buffer, (target == null ? OPEN_CURRENT_BUFFER : target), element);
494 function view_source_new_buffer (I) {
495 yield view_source(I, OPEN_NEW_BUFFER);
498 function view_source_new_window (I) {
499 yield view_source(I, OPEN_NEW_WINDOW);
503 function browser_element_shell_command(buffer, elem, command) {
504 var spec = load_spec(elem);
505 yield download_as_temporary(spec,
507 $shell_command = command,
508 $shell_command_cwd = buffer.cwd);