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 (I) { I.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 // Check for any frames or visible iframes
61 var skip_hints = true;
62 if (doc.getElementsByTagName("frame").length > 0)
65 let topwin = I.buffer.top_frame;
66 for each (let x in doc.getElementsByTagName("iframe"))
68 let style = topwin.getComputedStyle(x, "");
69 if (style.display == "none" || style.visibility == "hidden")
76 // only one frame (the top-level one), no need to use the hints system
77 yield co_return(I.buffer.top_frame);
79 var result = yield I.buffer.window.minibuffer.read_hinted_element(
82 $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
83 yield co_return(result);
86 define_browser_object_class(
87 "links", "link", null,
88 xpath_browser_object_handler (
89 "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or " +
91 "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " +
92 "//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand] | " +
93 "//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | " +
94 "//xhtml:button | //xhtml:select"));
96 define_browser_object_class(
97 "mathml", "MathML element", null,
98 xpath_browser_object_handler("//m:math"));
100 define_browser_object_class(
102 function (I, prompt) { yield co_return(I.buffer.top_frame); });
104 define_browser_object_class(
106 function (I, prompt) {
107 var result = yield I.buffer.window.minibuffer.read_url($prompt = prompt);
108 yield co_return(result);
111 define_browser_object_class(
112 "pasteurl", null, null,
114 let url = read_from_x_primary_selection();
115 yield co_return(url);
118 define_browser_object_class(
120 function (I, prompt) {
121 var result = yield I.buffer.window.minibuffer.read_file(
123 $history = I.command.name+"/file",
124 $initial_value = I.local.cwd.path);
125 yield co_return(result);
128 define_browser_object_class(
129 "alt", "Image Alt-text", null,
130 function (I, prompt) {
131 var result = yield I.buffer.window.minibuffer.read_hinted_element(
134 $hint_xpath_expression = "//img[@alt]");
135 yield co_return(result.alt);
138 define_browser_object_class(
139 "title", "Element Title", null,
140 function (I, prompt) {
141 var result = yield I.buffer.window.minibuffer.read_hinted_element(
144 $hint_xpath_expression = "//*[@title]");
145 yield co_return(result.title);
148 define_browser_object_class(
149 "title-or-alt", "Element Title or Alt-text", null,
150 function (I, prompt) {
151 var result = yield I.buffer.window.minibuffer.read_hinted_element(
154 $hint_xpath_expression = "//img[@alt] | //*[@title]");
155 yield co_return(result.title ? result.title : result.alt);
158 define_browser_object_class(
160 "Scrapes urls from the source code of the top-level document of buffer.",
161 function (I, prompt) {
162 var completions = I.buffer.document.documentElement.innerHTML
163 .match(/http:[^\s>"]*/g)
164 .filter(remove_duplicates_filter());
165 var completer = prefix_completer($completions = completions);
166 var result = yield I.buffer.window.minibuffer.read(
168 $completer = completer,
169 $initial_value = null,
170 $auto_complete = "url",
172 $match_required = false);
173 yield co_return(result);
176 define_browser_object_class(
177 "up-url", "Up Url", null,
178 function (I, prompt) {
179 var up = compute_url_up_path(I.buffer.current_URI.spec);
180 return I.buffer.current_URI.resolve(up);
183 define_browser_object_class(
184 "focused-element", "Focused element", null,
185 function (I, prompt) {
186 return I.buffer.focused_element;
189 function read_browser_object (I) {
190 var browser_object = I.browser_object;
191 // literals cannot be overridden
192 if (browser_object instanceof Function)
193 yield co_return(browser_object());
194 if (! (browser_object instanceof browser_object_class))
195 yield co_return(browser_object);
197 var prompt = I.command.prompt;
199 prompt = I.command.name.split(/-|_/).join(" ");
200 prompt = prompt[0].toUpperCase() + prompt.substring(1);
202 if (I.target != null)
203 prompt += TARGET_PROMPTS[I.target];
204 if (browser_object.label)
205 prompt += " (select " + browser_object.label + ")";
208 var result = yield browser_object.handler.call(null, I, prompt);
209 yield co_return(result);
214 * This is a simple wrapper function that sets focus to elem, and
215 * bypasses the automatic focus prevention system, which might
216 * otherwise prevent this from happening.
218 function browser_set_element_focus (buffer, elem, prevent_scroll) {
219 if (!element_dom_node_or_window_p(elem))
222 buffer.last_user_input_received = Date.now();
224 set_focus_no_scroll(buffer.window, elem);
229 function browser_element_focus (buffer, elem) {
230 if (!element_dom_node_or_window_p(elem))
233 if (elem instanceof Ci.nsIDOMXULTextBoxElement)
234 elem = elem.wrappedJSObject.inputField; // focus the input field
236 browser_set_element_focus(buffer, elem);
237 if (elem instanceof Ci.nsIDOMWindow)
240 // If it is not a window, it must be an HTML element
243 if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
244 elem instanceof Ci.nsIDOMHTMLIFrameElement)
246 elem.contentWindow.focus();
249 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
250 var coords = elem.getAttribute("coords").split(",");
251 x = Number(coords[0]);
252 y = Number(coords[1]);
255 var doc = elem.ownerDocument;
256 var evt = doc.createEvent("MouseEvents");
258 evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null);
259 elem.dispatchEvent(evt);
262 function browser_object_follow (buffer, target, elem) {
263 // XXX: would be better to let nsILocalFile objects be load_specs
264 if (elem instanceof Ci.nsILocalFile)
268 if (elem instanceof load_spec)
269 e = load_spec_element(elem);
273 browser_set_element_focus(buffer, e, true /* no scroll */);
275 var no_click = ((e instanceof load_spec) ||
276 (e instanceof Ci.nsIDOMWindow) ||
277 (e instanceof Ci.nsIDOMHTMLFrameElement) ||
278 (e instanceof Ci.nsIDOMHTMLIFrameElement) ||
279 (e instanceof Ci.nsIDOMHTMLLinkElement) ||
280 (e instanceof Ci.nsIDOMHTMLImageElement &&
281 !e.hasAttribute("onmousedown") && !e.hasAttribute("onclick")));
283 if (target == FOLLOW_DEFAULT && !no_click) {
285 if (e instanceof Ci.nsIDOMHTMLAreaElement) {
286 var coords = e.getAttribute("coords").split(",");
287 if (coords.length >= 2) {
288 x = Number(coords[0]) + 1;
289 y = Number(coords[1]) + 1;
292 dom_node_click(e, x, y);
296 var spec = load_spec(elem);
298 if (load_spec_uri_string(spec).match(/^\s*javascript:/)) {
299 // it is nonsensical to follow a javascript url in a different
301 target = FOLLOW_DEFAULT;
302 } else if (!(buffer instanceof content_buffer) &&
303 (target == FOLLOW_CURRENT_FRAME ||
304 target == FOLLOW_DEFAULT ||
305 target == OPEN_CURRENT_BUFFER))
307 target = OPEN_NEW_BUFFER;
311 case FOLLOW_CURRENT_FRAME:
312 var current_frame = load_spec_source_frame(spec);
313 if (current_frame && current_frame != buffer.top_frame) {
314 var target_obj = get_web_navigation_for_frame(current_frame);
315 apply_load_spec(target_obj, spec);
319 case OPEN_CURRENT_BUFFER:
322 case OPEN_NEW_WINDOW:
323 case OPEN_NEW_BUFFER:
324 case OPEN_NEW_BUFFER_BACKGROUND:
325 create_buffer(buffer.window,
326 buffer_creator(content_buffer,
334 * Follow a link-like element by generating fake mouse events.
336 function dom_node_click (elem, x, y) {
337 var doc = elem.ownerDocument;
338 var view = doc.defaultView;
340 var evt = doc.createEvent("MouseEvents");
341 evt.initMouseEvent("mousedown", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
342 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
343 elem.dispatchEvent(evt);
345 evt = doc.createEvent("MouseEvents");
346 evt.initMouseEvent("click", true, true, view, 1, x, y, 0, 0, /*ctrl*/ 0, /*event.altKey*/0,
347 /*event.shiftKey*/ 0, /*event.metaKey*/ 0, 0, null);
348 elem.dispatchEvent(evt);
352 function follow (I, target) {
354 target = FOLLOW_DEFAULT;
356 if (target == OPEN_CURRENT_BUFFER)
357 check_buffer(I.buffer, content_buffer);
358 var element = yield read_browser_object(I);
360 element = load_spec(element);
361 if (I.forced_charset)
362 element.forced_charset = I.forced_charset;
364 browser_object_follow(I.buffer, target, element);
367 function follow_new_buffer (I) {
368 yield follow(I, OPEN_NEW_BUFFER);
371 function follow_new_buffer_background (I) {
372 yield follow(I, OPEN_NEW_BUFFER_BACKGROUND);
375 function follow_new_window (I) {
376 yield follow(I, OPEN_NEW_WINDOW);
379 function follow_current_frame (I) {
380 yield follow(I, FOLLOW_CURRENT_FRAME);
383 function follow_current_buffer (I) {
384 yield follow(I, OPEN_CURRENT_BUFFER);
388 function element_get_load_target_label (element) {
389 if (element instanceof Ci.nsIDOMWindow)
391 if (element instanceof Ci.nsIDOMHTMLFrameElement)
393 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
398 function element_get_operation_label (element, op_name, suffix) {
399 var target_label = element_get_load_target_label(element);
400 if (target_label != null)
401 target_label = " " + target_label;
406 suffix = " " + suffix;
410 return op_name + target_label + suffix + ":";
414 function browser_element_copy (buffer, elem) {
417 spec = load_spec(elem);
421 text = load_spec_uri_string(spec);
423 if (!(elem instanceof Ci.nsIDOMNode))
424 throw interactive_error("Element has no associated text to copy.");
425 switch (elem.localName) {
431 if (elem.selectedIndex >= 0)
432 text = elem.item(elem.selectedIndex).text;
435 text = elem.textContent;
439 browser_set_element_focus(buffer, elem);
440 writeToClipboard(text);
441 buffer.window.minibuffer.message("Copied: " + text);
445 define_variable("view_source_use_external_editor", false,
446 "When true, the `view-source' command will send its document to "+
447 "your external editor.");
449 define_variable("view_source_function", null,
450 "May be set to a user-defined function for viewing source code. "+
451 "The function should accept an nsILocalFile of the filename as "+
452 "its one positional argument, and it will also be called with "+
453 "the keyword `$temporary', whose value will be true if the file "+
454 "is considered temporary, and therefore the function must take "+
455 "responsibility for deleting it.");
457 function browser_object_view_source (buffer, target, elem) {
458 if (view_source_use_external_editor || view_source_function) {
459 var spec = load_spec(elem);
461 let [file, temp] = yield download_as_temporary(spec,
463 $action = "View source");
464 if (view_source_use_external_editor)
465 yield open_file_with_external_editor(file, $temporary = temp);
467 yield view_source_function(file, $temporary = temp);
472 var window = buffer.window;
473 if (elem.localName) {
474 switch (elem.localName.toLowerCase()) {
475 case "frame": case "iframe":
476 win = elem.contentWindow;
479 view_mathml_source(window, charset, elem);
482 throw new Error("Invalid browser element");
488 var url_s = win.location.href;
489 if (url_s.substring (0,12) != "view-source:") {
491 browser_object_follow(buffer, target, "view-source:" + url_s);
492 } catch(e) { dump_error(e); }
494 window.minibuffer.message ("Already viewing source");
498 function view_source (I, target) {
501 target = OPEN_CURRENT_BUFFER;
502 var element = yield read_browser_object(I);
503 yield browser_object_view_source(I.buffer, target, element);
506 function view_source_new_buffer (I) {
507 yield view_source(I, OPEN_NEW_BUFFER);
510 function view_source_new_window (I) {
511 yield view_source(I, OPEN_NEW_WINDOW);
515 function browser_element_shell_command (buffer, elem, command, cwd) {
516 var spec = load_spec(elem);
517 yield download_as_temporary(spec,
519 $shell_command = command,
520 $shell_command_cwd = cwd);