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 * $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.
26 define_keywords("$hint");
27 function browser_object_class (name, doc, handler) {
30 this.handler = handler;
32 this.hint = arguments.$hint;
36 function define_browser_object_class (name, doc, handler) {
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));
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; },
51 function xpath_browser_object_handler (xpath_expression) {
52 return function (I, prompt) {
53 var result = yield I.buffer.window.minibuffer.read_hinted_element(
56 $hint_xpath_expression = xpath_expression);
57 yield co_return(result);
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)
73 let topwin = I.buffer.top_frame;
74 for each (let x in doc.getElementsByTagName("iframe"))
76 let style = topwin.getComputedStyle(x, "");
77 if (style.display == "none" || style.visibility == "hidden")
84 // only one frame (the top-level one), no need to use the hints system
85 yield co_return(I.buffer.top_frame);
87 var result = yield I.buffer.window.minibuffer.read_hinted_element(
90 $hint_xpath_expression = "//iframe | //frame | //xhtml:iframe | //xhtml:frame");
91 yield co_return(result);
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 " +
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);
117 $hint = "enter URL");
119 define_browser_object_class("pasteurl", null,
121 let url = read_from_x_primary_selection();
122 yield co_return(url);
125 define_browser_object_class("file", null,
126 function (I, prompt) {
127 var result = yield I.buffer.window.minibuffer.read_file(
129 $history = I.command.name+"/file",
130 $initial_value = I.local.cwd.path);
131 yield co_return(result);
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(
140 $hint_xpath_expression = "//img[@alt]");
141 yield co_return(result.alt);
143 $hint = "select image for alt-text");
145 define_browser_object_class("title",
147 function (I, prompt) {
148 var result = yield I.buffer.window.minibuffer.read_hinted_element(
151 $hint_xpath_expression = "//*[@title]");
152 yield co_return(result.title);
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(
161 $hint_xpath_expression = "//img[@alt] | //*[@title]");
162 yield co_return(result.title ? result.title : result.alt);
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(
175 $completer = completer,
176 $initial_value = null,
177 $auto_complete = "url",
179 $match_required = false);
180 yield co_return(result);
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);
190 define_browser_object_class("focused-element", null,
191 function (I, prompt) {
192 return I.buffer.focused_element;
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;
209 prompt = I.command.name.split(/-|_/).join(" ");
210 prompt = prompt[0].toUpperCase() + prompt.substring(1);
212 if (I.target != null)
213 prompt += TARGET_PROMPTS[I.target];
214 if (browser_object.hint)
215 prompt += " (" + browser_object.hint + ")";
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.
228 function browser_set_element_focus (buffer, elem, prevent_scroll) {
229 if (!element_dom_node_or_window_p(elem))
232 buffer.last_user_input_received = Date.now();
234 set_focus_no_scroll(buffer.window, elem);
239 function browser_element_focus (buffer, elem) {
240 if (!element_dom_node_or_window_p(elem))
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)
250 // If it is not a window, it must be an HTML element
253 if (elem instanceof Ci.nsIDOMHTMLFrameElement ||
254 elem instanceof Ci.nsIDOMHTMLIFrameElement)
256 elem.contentWindow.focus();
259 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
260 var coords = elem.getAttribute("coords").split(",");
261 x = Number(coords[0]);
262 y = Number(coords[1]);
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)
278 if (elem instanceof load_spec)
279 e = load_spec_element(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) {
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;
302 dom_node_click(e, x, y);
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
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))
317 target = OPEN_NEW_BUFFER;
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);
329 case OPEN_CURRENT_BUFFER:
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,
344 * Follow a link-like element by generating fake mouse events.
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) {
364 target = FOLLOW_DEFAULT;
366 if (target == OPEN_CURRENT_BUFFER)
367 check_buffer(I.buffer, content_buffer);
368 var element = yield read_browser_object(I);
370 element = load_spec(element);
371 if (I.forced_charset)
372 element.forced_charset = I.forced_charset;
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)
401 if (element instanceof Ci.nsIDOMHTMLFrameElement)
403 if (element instanceof Ci.nsIDOMHTMLIFrameElement)
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;
416 suffix = " " + suffix;
420 return op_name + target_label + suffix + ":";
424 function browser_element_copy (buffer, elem) {
427 spec = load_spec(elem);
431 text = load_spec_uri_string(spec);
433 if (!(elem instanceof Ci.nsIDOMNode))
434 throw interactive_error("Element has no associated text to copy.");
435 switch (elem.localName) {
441 if (elem.selectedIndex >= 0)
442 text = elem.item(elem.selectedIndex).text;
445 text = elem.textContent;
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,
473 $action = "View source");
474 if (view_source_use_external_editor)
475 yield open_file_with_external_editor(file, $temporary = temp);
477 yield view_source_function(file, $temporary = temp);
482 var window = buffer.window;
483 if (elem.localName) {
484 switch (elem.localName.toLowerCase()) {
485 case "frame": case "iframe":
486 win = elem.contentWindow;
489 view_mathml_source(window, charset, elem);
492 throw new Error("Invalid browser element");
498 var url_s = win.location.href;
499 if (url_s.substring (0,12) != "view-source:") {
501 browser_object_follow(buffer, target, "view-source:" + url_s);
502 } catch(e) { dump_error(e); }
504 window.minibuffer.message ("Already viewing source");
508 function view_source (I, target) {
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,
529 $shell_command = command,
530 $shell_command_cwd = cwd);