2 * (C) Copyright 2004-2007 Shawn Betts
3 * (C) Copyright 2007-2009 John J. Foerch
4 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
6 * Use, modification, and distribution are subject to the terms specified in the
13 require("load-spec.js");
15 define_variable("homepage", "chrome://conkeror-help/content/help.html",
16 "The url loaded by default for new content buffers.");
18 define_buffer_local_hook("content_buffer_finished_loading_hook");
19 define_buffer_local_hook("content_buffer_started_loading_hook");
20 define_buffer_local_hook("content_buffer_progress_change_hook");
21 define_buffer_local_hook("content_buffer_location_change_hook");
22 define_buffer_local_hook("content_buffer_status_change_hook");
23 define_buffer_local_hook("content_buffer_focus_change_hook");
24 define_buffer_local_hook("content_buffer_overlink_change_hook");
25 define_buffer_local_hook("content_buffer_dom_link_added_hook");
26 define_buffer_local_hook("content_buffer_popup_blocked_hook");
28 define_current_buffer_hook("current_content_buffer_finished_loading_hook", "content_buffer_finished_loading_hook");
29 define_current_buffer_hook("current_content_buffer_progress_change_hook", "content_buffer_progress_change_hook");
30 define_current_buffer_hook("current_content_buffer_location_change_hook", "content_buffer_location_change_hook");
31 define_current_buffer_hook("current_content_buffer_status_change_hook", "content_buffer_status_change_hook");
32 define_current_buffer_hook("current_content_buffer_focus_change_hook", "content_buffer_focus_change_hook");
33 define_current_buffer_hook("current_content_buffer_overlink_change_hook", "content_buffer_overlink_change_hook");
36 define_input_mode("text", "content_buffer_text_keymap", $display_name = "input:TEXT");
37 define_input_mode("textarea", "content_buffer_textarea_keymap", $display_name = "input:TEXTAREA");
38 define_input_mode("richedit", "content_buffer_richedit_keymap", $display_name = "input:RICHEDIT");
39 define_input_mode("select", "content_buffer_select_keymap", $display_name = "input:SELECT");
40 define_input_mode("checkbox", "content_buffer_checkbox_keymap", $display_name = "input:CHECKBOX/RADIOBUTTON");
41 define_input_mode("button", "content_buffer_button_keymap", $display_name = "input:BUTTON");
43 function content_buffer_modality (buffer) {
44 var elem = buffer.focused_element;
45 buffer.keymaps.push(content_buffer_normal_keymap);
47 let p = elem.parentNode;
48 while (p && !(p instanceof Ci.nsIDOMHTMLFormElement))
51 buffer.keymaps.push(content_buffer_form_keymap);
53 if (elem instanceof Ci.nsIDOMHTMLInputElement) {
54 switch ((elem.getAttribute("type") || "").toLowerCase()) {
56 checkbox_input_mode(buffer, true);
59 checkbox_input_mode(buffer, true);
62 button_input_mode(buffer, true);
65 button_input_mode(buffer, true);
68 text_input_mode(buffer, true);
73 if (elem instanceof Ci.nsIDOMHTMLTextAreaElement) {
74 textarea_input_mode(buffer, true);
77 if (elem instanceof Ci.nsIDOMHTMLSelectElement) {
78 select_input_mode(buffer, true);
81 if (elem instanceof Ci.nsIDOMHTMLAnchorElement) {
82 buffer.keymaps.push(content_buffer_anchor_keymap);
85 if (elem instanceof Ci.nsIDOMHTMLButtonElement) {
86 button_input_mode(buffer, true);
89 var frame = buffer.focused_frame;
90 if (frame && frame.document.designMode &&
91 frame.document.designMode == "on")
93 richedit_input_mode(buffer, true);
97 switch (elem.contentEditable) {
99 richedit_input_mode(buffer, true);
103 default: // "inherit"
104 elem = elem.parentNode;
110 define_keywords("$load");
111 function content_buffer (window) {
113 this.constructor_begin();
115 conkeror.buffer.call(this, window, forward_keywords(arguments));
117 this.browser.addProgressListener(this);
119 this.browser.addEventListener("DOMTitleChanged", function (event) {
120 buffer_title_change_hook.run(buffer);
121 }, true /* capture */);
123 this.browser.addEventListener("scroll", function (event) {
124 buffer_scroll_hook.run(buffer);
125 }, true /* capture */);
127 this.browser.addEventListener("focus", function (event) {
128 content_buffer_focus_change_hook.run(buffer, event);
129 }, true /* capture */);
131 this.browser.addEventListener("mouseover", function (event) {
132 var node = event.target;
133 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
134 node = node.parentNode;
136 content_buffer_overlink_change_hook.run(buffer, node.href);
137 buffer.current_overlink = event.target;
139 }, true /* capture */);
141 this.browser.addEventListener("mouseout", function (event) {
142 if (buffer.current_overlink == event.target) {
143 buffer.current_overlink = null;
144 content_buffer_overlink_change_hook.run(buffer, "");
146 }, true /* capture */);
148 // initialize buffer.current_overlink in case mouseout happens
150 buffer.current_overlink = null;
152 this.browser.addEventListener("DOMLinkAdded", function (event) {
153 content_buffer_dom_link_added_hook.run(buffer, event);
154 }, true /* capture */);
156 this.browser.addEventListener("DOMPopupBlocked", function (event) {
157 dumpln("Blocked popup: " + event.popupWindowURI.spec);
158 content_buffer_popup_blocked_hook.run(buffer, event);
159 }, true /* capture */);
161 this.ignore_initial_blank = true;
163 var lspec = arguments.$load;
165 if (lspec.url == "about:blank")
166 this.ignore_initial_blank = false;
168 /* Ensure that an existing load of about:blank is stopped */
169 this.web_navigation.stop(Ci.nsIWebNavigation.STOP_ALL);
175 this.modalities.push(content_buffer_modality);
178 this.constructor_end();
181 content_buffer.prototype = {
182 constructor: content_buffer,
184 destroy: function () {
185 this.browser.removeProgressListener(this);
186 buffer.prototype.destroy.call(this);
189 get scrollX () { return this.top_frame.scrollX; },
190 get scrollY () { return this.top_frame.scrollY; },
191 get scrollMaxX () { return this.top_frame.scrollMaxX; },
192 get scrollMaxY () { return this.top_frame.scrollMaxY; },
194 /* Used to display the correct URI when the buffer opens initially
195 * even before loading has progressed far enough for currentURI to
196 * contain the correct URI. */
199 get display_uri_string () {
200 if (this._display_uri)
201 return this._display_uri;
202 if (this.current_uri)
203 return this.current_uri.spec;
207 get title () { return this.browser.contentTitle; },
208 get description () { return this.display_uri_string; },
210 load: function (load_spec) {
211 apply_load_spec(this, load_spec);
218 /* nsIWebProgressListener */
219 QueryInterface: generate_QI(Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference),
221 // This method is called to indicate state changes.
222 onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
224 const WPL = Components.interfaces.nsIWebProgressListener;
227 if (aStateFlags & WPL.STATE_START)
229 if (aStateFlags & WPL.STATE_STOP)
231 if (aStateFlags & WPL.STATE_IS_REQUEST)
232 flagstr += ",request";
233 if (aStateFlags & WPL.STATE_IS_DOCUMENT)
234 flagstr += ",document";
235 if (aStateFlags & WPL.STATE_IS_NETWORK)
236 flagstr += ",network";
237 if (aStateFlags & WPL.STATE_IS_WINDOW)
238 flagstr += ",window";
239 dumpln("onStateChange: " + flagstr + ", status: " + aStatus);
244 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
245 this._request_count++;
246 } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
247 const NS_ERROR_UNKNOWN_HOST = 2152398878;
248 if (--this._request_count > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
249 // to prevent bug 235825: wait for the request handled
250 // by the automatic keyword resolver
253 // since we (try to) only handle STATE_STOP of the last request,
254 // the count of open requests should now be 0
255 this._request_count = 0;
258 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
259 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
260 // It's okay to clear what the user typed when we start
261 // loading a document. If the user types, this counter gets
262 // set to zero, if the document load ends without an
263 // onLocationChange, this counter gets decremented
264 // (so we keep it while switching tabs after failed loads)
265 //dumpln("*** started loading");
267 content_buffer_started_loading_hook.run(this);
268 } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
269 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
270 if (this.loading == true) {
271 //dumpln("*** finished loading");
272 this.loading = false;
273 content_buffer_finished_loading_hook.run(this);
277 if (aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP |
278 Ci.nsIWebProgressListener.STATE_START)) {
280 this.set_default_message("Done");
284 /* This method is called to indicate progress changes for the currently
286 onProgressChange: function (webProgress, request, curSelf, maxSelf,
287 curTotal, maxTotal) {
288 content_buffer_progress_change_hook.run(this, request, curSelf, maxSelf, curTotal, maxTotal);
291 /* This method is called to indicate a change to the current location.
292 The url can be gotten as location.spec. */
293 onLocationChange: function (webProgress, request, location) {
294 /* Attempt to ignore onLocationChange calls due to the initial
295 * loading of about:blank by all xul:browser elements. */
296 if (location.spec == "about:blank" && this.ignore_initial_blank)
299 this.ignore_initial_blank = false;
301 //dumpln("spec: " + location.spec +" ;;; " + this.display_uri_string);
302 /* Use the real location URI now */
303 this._display_uri = null;
304 this.set_input_mode();
305 content_buffer_location_change_hook.run(this, request, location);
306 buffer_description_change_hook.run(this);
309 // This method is called to indicate a status changes for the currently
310 // loading page. The message is already formatted for display.
311 // Status messages could be displayed in the minibuffer output area.
312 onStatusChange: function (webProgress, request, status, msg) {
313 this.set_default_message(msg);
314 content_buffer_status_change_hook.run(this, request, status, msg);
317 // This method is called when the security state of the browser changes.
318 onSecurityChange: function (webProgress, request, state) {
319 /* FIXME: currently this isn't used */
322 const WPL = Components.interfaces.nsIWebProgressListener;
324 if (state & WPL.STATE_IS_INSECURE) {
325 // update visual indicator
327 var level = "unknown";
328 if (state & WPL.STATE_IS_SECURE) {
329 if (state & WPL.STATE_SECURE_HIGH)
331 else if (state & WPL.STATE_SECURE_MED)
333 else if (state & WPL.STATE_SECURE_LOW)
335 } else if (state & WPL_STATE_IS_BROKEN) {
338 // provide a visual indicator of the security state here.
343 /* Inherit from buffer */
344 __proto__: buffer.prototype
348 add_hook("current_content_buffer_finished_loading_hook",
350 buffer.window.minibuffer.show("Done");
353 add_hook("current_content_buffer_status_change_hook",
354 function (buffer, request, status, msg) {
355 buffer.set_default_message(msg);
360 define_variable("read_url_handler_list", [],
361 "A list of handler functions which transform a typed url into a valid " +
362 "url or webjump. If the typed input is not valid then each function " +
363 "on this list is tried in turn. The handler function is called with " +
364 "a single string argument and it should return either a string or " +
365 "null. The result of the first function on the list that returns a " +
366 "string is used in place of the input.");
369 * read_url_make_default_webjump_handler returns a function that
370 * transforms any input into the given webjump. It should be the last
371 * handler on read_url_handler_list (because any input is
374 function read_url_make_default_webjump_handler (default_webjump) {
375 return function (input) {
376 return default_webjump + " " + input;
381 * read_url_make_blank_url_handler returns a function that replaces a
382 * blank (empty) input with the given url (or webjump). The url may
383 * perform some function, eg. "javascript:location.reload()".
385 function read_url_make_blank_url_handler (url) {
386 return function (input) {
387 if (input.length == 0)
393 minibuffer.prototype.try_read_url_handlers = function (input) {
395 for (var i = 0; i < read_url_handler_list.length; ++i) {
396 if ((result = read_url_handler_list[i](input)))
402 define_variable("url_completion_use_webjumps", true,
403 "Specifies whether URL completion should complete webjumps.");
405 define_variable("url_completion_use_bookmarks", true,
406 "Specifies whether URL completion should complete bookmarks.");
408 define_variable("url_completion_use_history", false,
409 "Specifies whether URL completion should complete using browser "+
412 define_variable("minibuffer_read_url_select_initial", true,
413 "Specifies whether a URL presented in the minibuffer for editing "+
414 "should be selected. This affects find-alternate-url.");
417 minibuffer_auto_complete_preferences["url"] = true;
418 minibuffer.prototype.read_url = function () {
419 keywords(arguments, $prompt = "URL:", $history = "url", $initial_value = "",
420 $use_webjumps = url_completion_use_webjumps,
421 $use_history = url_completion_use_history,
422 $use_bookmarks = url_completion_use_bookmarks);
423 var completer = url_completer($use_webjumps = arguments.$use_webjumps,
424 $use_bookmarks = arguments.$use_bookmarks,
425 $use_history = arguments.$use_history);
426 var result = yield this.read(
427 $prompt = arguments.$prompt,
428 $history = arguments.$history,
429 $completer = completer,
430 $initial_value = arguments.$initial_value,
431 $auto_complete = "url",
432 $select = minibuffer_read_url_select_initial,
433 $match_required = false);
434 if (!possibly_valid_url(result) && !get_webjump(result))
435 result = this.try_read_url_handlers(result);
436 if (result == "") // well-formedness check. (could be better!)
437 throw ("invalid url or webjump (\""+ result +"\")");
438 yield co_return(load_spec(result));
441 I.content_charset = interactive_method(
442 $sync = function (ctx) {
443 var buffer = ctx.buffer;
444 if (!(buffer instanceof content_buffer))
445 throw new Error("Current buffer is of invalid type");
446 // -- Charset of content area of focusedWindow
447 var focusedWindow = buffer.focused_frame;
449 return focusedWindow.document.characterSet;
455 I.content_selection = interactive_method(
456 $sync = function (ctx) {
457 // -- Selection of content area of focusedWindow
458 var focusedWindow = this.buffers.current.focused_frame;
459 return focusedWindow.getSelection ();
462 function overlink_update_status (buffer, text) {
464 buffer.window.minibuffer.show("Link: " + text);
466 buffer.window.minibuffer.show("");
469 define_global_mode("overlink_mode", function () {
470 add_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
472 remove_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
478 function go_back (b, prefix) {
480 go_forward(b, -prefix);
482 check_buffer(b, content_buffer);
484 if (b.web_navigation.canGoBack) {
485 var hist = b.web_navigation.sessionHistory;
486 var idx = hist.index - prefix;
489 b.web_navigation.gotoIndex(idx);
491 throw interactive_error("Can't go back");
495 "Go back in the session history for the current buffer.",
496 function (I) {go_back(I.buffer, I.p);});
499 function go_forward (b, prefix) {
503 check_buffer(b, content_buffer);
505 if (b.web_navigation.canGoForward) {
506 var hist = b.web_navigation.sessionHistory;
507 var idx = hist.index + prefix;
508 if (idx >= hist.count) idx = hist.count-1;
509 b.web_navigation.gotoIndex(idx);
511 throw interactive_error("Can't go forward");
514 interactive("forward",
515 "Go forward in the session history for the current buffer.",
516 function (I) {go_forward(I.buffer, I.p);});
519 function stop_loading (b) {
520 check_buffer(b, content_buffer);
521 b.web_navigation.stop(Ci.nsIWebNavigation.STOP_NETWORK);
524 interactive("stop-loading",
525 "Stop loading the current document.",
526 function (I) {stop_loading(I.buffer);});
529 function reload (b, bypass_cache, element, forced_charset) {
530 check_buffer(b, content_buffer);
532 if (element instanceof Ci.nsIDOMHTMLImageElement) {
534 var cache = Cc['@mozilla.org/image/cache;1']
535 .getService(Ci.imgICache);
536 cache.removeEntry(make_uri(element.src));
539 element.parentNode.replaceChild(element.cloneNode(true), element);
541 var flags = bypass_cache == null ?
542 Ci.nsIWebNavigation.LOAD_FLAGS_NONE :
543 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
545 if (! forced_charset && forced_charset_list)
546 forced_charset = predicate_alist_match(forced_charset_list,
549 if (forced_charset) {
551 var atomservice = Cc['@mozilla.org/atom-service;1']
552 .getService(Ci.nsIAtomService);
553 b.web_navigation.documentCharsetInfo.forcedCharset =
554 atomservice.getAtom(forced_charset);
557 b.web_navigation.reload(flags);
561 interactive("reload",
562 "Reload the current document.\n" +
563 "If a prefix argument is specified, the cache is bypassed. If a "+
564 "DOM node is supplied via browser object, that node will be "+
567 check_buffer(I.buffer, content_buffer);
568 var element = yield read_browser_object(I);
569 reload(I.buffer, I.P, element, I.forced_charset);
573 * browserDOMWindow: intercept window opening
575 function initialize_browser_dom_window (window) {
576 window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
577 new browser_dom_window(window);
580 define_variable("browser_default_open_target", OPEN_NEW_BUFFER,
581 "Specifies how new window requests by content pages (e.g. by "+
582 "window.open from JavaScript or by using the target attribute of "+
583 "anchor and form elements) will be handled. This will generally "+
584 "be `OPEN_NEW_BUFFER', `OPEN_NEW_BUFFER_BACKGROUND', or "+
585 "`OPEN_NEW_WINDOW'.");
588 function browser_dom_window (window) {
589 this.window = window;
590 this.next_target = null;
592 browser_dom_window.prototype = {
593 constructor: browser_dom_window,
594 QueryInterface: generate_QI(Ci.nsIBrowserDOMWindow),
596 openURI: function (aURI, aOpener, aWhere, aContext) {
597 // Reference: http://www.xulplanet.com/references/xpcomref/ifaces/nsIBrowserDOMWindow.html
598 var target = this.next_target;
599 if (target == null || target == FOLLOW_DEFAULT)
600 target = browser_default_open_target;
601 this.next_target = null;
603 /* Determine the opener buffer */
604 var opener = get_buffer_from_frame(this.window, aOpener);
606 switch (browser_default_open_target) {
607 case OPEN_CURRENT_BUFFER:
609 case FOLLOW_CURRENT_FRAME:
611 case OPEN_NEW_BUFFER:
612 var buffer = new content_buffer(this.window, $opener = opener);
613 this.window.buffers.current = buffer;
614 return buffer.top_frame;
615 case OPEN_NEW_BUFFER_BACKGROUND:
616 var buffer = new content_buffer(this.window, $opener = opener);
617 return buffer.top_frame;
618 case OPEN_NEW_WINDOW:
619 default: /* shouldn't be needed */
621 /* We don't call make_window here, because that will result
622 * in the URL being loaded as the top-level document,
623 * instead of within a browser buffer. Instead, we can
624 * rely on Mozilla using browser.chromeURL. */
625 window_set_extra_arguments(
626 {initial_buffer_creator: buffer_creator(content_buffer, $opener = opener)}
633 add_hook("window_initialize_early_hook", initialize_browser_dom_window);
635 define_keywords("$display_name", "$enable", "$disable", "$doc");
636 function define_page_mode (name) {
638 var display_name = arguments.$display_name;
639 var enable = arguments.$enable;
640 var disable = arguments.$disable;
641 var doc = arguments.$doc;
642 define_buffer_mode(name,
643 $display_name = display_name,
644 $class = "page_mode",
645 $enable = function (buffer) {
647 local: { __proto__: buffer.local }
651 buffer.set_input_mode();
653 $disable = function (buffer) {
657 buffer.default_browser_object_classes = {};
658 buffer.set_input_mode();
662 ignore_function_for_get_caller_source_code_reference("define_page_mode");
665 define_variable("auto_mode_list", [],
666 "A list of mappings from URI regular expressions to page modes.");
668 function page_mode_auto_update (buffer) {
669 var uri = buffer.current_uri.spec;
670 var mode = predicate_alist_match(auto_mode_list, uri);
673 else if (buffer.page_mode)
674 conkeror[buffer.page_mode](buffer, false);
677 add_hook("content_buffer_location_change_hook", page_mode_auto_update);
679 provide("content-buffer");