2 * (C) Copyright 2004-2007 Shawn Betts
3 * (C) Copyright 2007-2008 John J. Foerch
4 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
6 * Use, modification, and distribution are subject to the terms specified in the
10 require("load-spec.js");
12 require_later("content-buffer-input.js");
14 define_variable("homepage", "chrome://conkeror-help/content/help.html",
15 "The url loaded by default for new content buffers.");
17 define_buffer_local_hook("content_buffer_finished_loading_hook");
18 define_buffer_local_hook("content_buffer_started_loading_hook");
19 define_buffer_local_hook("content_buffer_progress_change_hook");
20 define_buffer_local_hook("content_buffer_location_change_hook");
21 define_buffer_local_hook("content_buffer_status_change_hook");
22 define_buffer_local_hook("content_buffer_focus_change_hook");
23 define_buffer_local_hook("content_buffer_overlink_change_hook");
24 define_buffer_local_hook("content_buffer_dom_link_added_hook");
26 define_current_buffer_hook("current_content_buffer_finished_loading_hook", "content_buffer_finished_loading_hook");
27 define_current_buffer_hook("current_content_buffer_progress_change_hook", "content_buffer_progress_change_hook");
28 define_current_buffer_hook("current_content_buffer_location_change_hook", "content_buffer_location_change_hook");
29 define_current_buffer_hook("current_content_buffer_status_change_hook", "content_buffer_status_change_hook");
30 define_current_buffer_hook("current_content_buffer_focus_change_hook", "content_buffer_focus_change_hook");
31 define_current_buffer_hook("current_content_buffer_overlink_change_hook", "content_buffer_overlink_change_hook");
33 /* If browser is null, create a new browser */
34 define_keywords("$load");
35 function content_buffer(window, element)
38 this.constructor_begin();
41 conkeror.buffer.call(this, window, element, forward_keywords(arguments));
43 this.browser.addProgressListener(this);
45 this.browser.addEventListener("DOMTitleChanged", function (event) {
46 buffer_title_change_hook.run(buffer);
47 }, true /* capture */, false /* ignore untrusted events */);
49 this.browser.addEventListener("scroll", function (event) {
50 buffer_scroll_hook.run(buffer);
51 }, true /* capture */, false /* ignore untrusted events */);
53 this.browser.addEventListener("focus", function (event) {
54 content_buffer_focus_change_hook.run(buffer, event);
55 }, true /* capture */, false /* ignore untrusted events */);
57 this.browser.addEventListener("mouseover", function (event) {
58 var node = event.target;
59 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
60 node = node.parentNode;
62 content_buffer_overlink_change_hook.run(buffer, node.href);
63 buffer.current_overlink = event.target;
67 this.browser.addEventListener("mouseout", function (event) {
68 if (buffer.current_overlink == event.target) {
69 buffer.current_overlink = null;
70 content_buffer_overlink_change_hook.run(buffer, "");
74 this.browser.addEventListener("mousedown", function (event) {
75 buffer.last_user_input_received = Date.now();
78 this.browser.addEventListener("keypress", function (event) {
79 buffer.last_user_input_received = Date.now();
82 this.browser.addEventListener("DOMLinkAdded", function (event) {
83 content_buffer_dom_link_added_hook.run(buffer, event);
86 buffer.last_user_input_received = null;
88 /* FIXME: Add a handler for blocked popups, and also PopupWindow event */
90 this.browser.addEventListener("DOMPopupBlocked", function (event) {
91 dumpln("PopupWindow: " + event);
95 normal_input_mode(this);
97 this.ignore_initial_blank = true;
99 var lspec = arguments.$load;
101 if (lspec.url == "about:blank")
102 this.ignore_initial_blank = false;
104 /* Ensure that an existing load of about:blank is stopped */
105 this.web_navigation.stop(Ci.nsIWebNavigation.STOP_ALL);
111 this.constructor_end();
114 content_buffer.prototype = {
115 constructor : content_buffer,
117 get scrollX () { return this.top_frame.scrollX; },
118 get scrollY () { return this.top_frame.scrollY; },
119 get scrollMaxX () { return this.top_frame.scrollMaxX; },
120 get scrollMaxY () { return this.top_frame.scrollMaxY; },
123 /* Used to display the correct URI when the buffer opens initially
124 * even before loading has progressed far enough for currentURI to
125 * contain the correct URI. */
128 get display_URI_string () {
129 if (this._display_URI)
130 return this._display_URI;
131 if (this.current_URI)
132 return this.current_URI.spec;
136 get title() { return this.browser.contentTitle; },
137 get description () { return this.display_URI_string; },
139 load : function (load_spec) {
140 apply_load_spec(this, load_spec);
147 /* nsIWebProgressListener */
148 QueryInterface: generate_QI(Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference),
150 // This method is called to indicate state changes.
151 onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
153 const WPL = Components.interfaces.nsIWebProgressListener;
156 if (aStateFlags & WPL.STATE_START)
158 if (aStateFlags & WPL.STATE_STOP)
160 if (aStateFlags & WPL.STATE_IS_REQUEST)
161 flagstr += ",request";
162 if (aStateFlags & WPL.STATE_IS_DOCUMENT)
163 flagstr += ",document";
164 if (aStateFlags & WPL.STATE_IS_NETWORK)
165 flagstr += ",network";
166 if (aStateFlags & WPL.STATE_IS_WINDOW)
167 flagstr += ",window";
168 dumpln("onStateChange: " + flagstr + ", status: " + aStatus);
174 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
175 this._request_count++;
177 else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
178 const NS_ERROR_UNKNOWN_HOST = 2152398878;
179 if (--this._request_count > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
180 // to prevent bug 235825: wait for the request handled
181 // by the automatic keyword resolver
184 // since we (try to) only handle STATE_STOP of the last request,
185 // the count of open requests should now be 0
186 this._request_count = 0;
189 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
190 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
191 // It's okay to clear what the user typed when we start
192 // loading a document. If the user types, this counter gets
193 // set to zero, if the document load ends without an
194 // onLocationChange, this counter gets decremented
195 // (so we keep it while switching tabs after failed loads)
196 //dumpln("*** started loading");
198 content_buffer_started_loading_hook.run(this);
199 this.last_user_input_received = null;
201 else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
202 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
203 if (this.loading == true) {
204 //dumpln("*** finished loading");
205 this.loading = false;
206 content_buffer_finished_loading_hook.run(this);
210 if (aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP |
211 Ci.nsIWebProgressListener.STATE_START)) {
213 this.set_default_message("Done");
217 /* This method is called to indicate progress changes for the currently
219 onProgressChange: function(webProgress, request, curSelf, maxSelf,
220 curTotal, maxTotal) {
221 content_buffer_progress_change_hook.run(this, request, curSelf, maxSelf, curTotal, maxTotal);
224 /* This method is called to indicate a change to the current location.
225 The url can be gotten as location.spec. */
226 onLocationChange : function(webProgress, request, location) {
227 /* Attempt to ignore onLocationChange calls due to the initial
228 * loading of about:blank by all xul:browser elements. */
229 if (location.spec == "about:blank" && this.ignore_initial_blank)
232 this.ignore_initial_blank = false;
234 //dumpln("spec: " + location.spec +" ;;; " + this.display_URI_string);
235 /* Use the real location URI now */
236 this._display_URI = null;
237 content_buffer_location_change_hook.run(this, request, location);
238 this.last_user_input_received = null;
239 buffer_description_change_hook.run(this);
242 // This method is called to indicate a status changes for the currently
243 // loading page. The message is already formatted for display.
244 // Status messages could be displayed in the minibuffer output area.
245 onStatusChange: function(webProgress, request, status, msg) {
246 this.set_default_message(msg);
247 content_buffer_status_change_hook.run(this, request, status, msg);
250 // This method is called when the security state of the browser changes.
251 onSecurityChange: function(webProgress, request, state) {
252 /* FIXME: currently this isn't used */
255 const WPL = Components.interfaces.nsIWebProgressListener;
257 if (state & WPL.STATE_IS_INSECURE) {
258 // update visual indicator
260 var level = "unknown";
261 if (state & WPL.STATE_IS_SECURE) {
262 if (state & WPL.STATE_SECURE_HIGH)
264 else if (state & WPL.STATE_SECURE_MED)
266 else if (state & WPL.STATE_SECURE_LOW)
268 } else if (state & WPL_STATE_IS_BROKEN) {
271 // provide a visual indicator of the security state here.
276 /* Inherit from buffer */
278 __proto__ : buffer.prototype
282 add_hook("current_content_buffer_finished_loading_hook",
284 buffer.window.minibuffer.show("Done");
287 add_hook("current_content_buffer_status_change_hook",
288 function (buffer, request, status, msg) {
289 buffer.set_default_message(msg);
294 define_variable("read_url_handler_list", [],
295 "A list of handler functions which transform a typed url into a valid " +
296 "url or webjump. If the typed input is not valid then each function " +
297 "on this list is tried in turn. The handler function is called with " +
298 "a single string argument and it should return either a string or " +
299 "null. The result of the first function on the list that returns a " +
300 "string is used in place of the input.");
302 /* read_url_make_default_webjump_handler returns a function that
303 * transforms any input into the given webjump. It should be the last
304 * handler on read_url_handler_list (because any input is
306 function read_url_make_default_webjump_handler(default_webjump) {
307 return function(input) {
308 return default_webjump + " " + input;
312 /* read_url_make_blank_url_handler returns a function that replaces a
313 * blank (empty) input with the given url (or webjump). The url may
314 * perform some function, eg. "javascript:location.reload()". */
315 function read_url_make_blank_url_handler(url) {
316 return function(input) {
317 if (input.length == 0)
323 minibuffer.prototype.try_read_url_handlers = function(input) {
325 for (var i = 0; i < read_url_handler_list.length; ++i)
326 if ((result = read_url_handler_list[i](input)))
331 define_variable("url_completion_use_webjumps", true, "Specifies whether URL completion should complete webjumps.");
332 define_variable("url_completion_use_bookmarks", true, "Specifies whether URL completion should complete bookmarks.");
333 define_variable("url_completion_use_history", false,
334 "Specifies whether URL completion should complete using browser history.");
336 define_variable("minibuffer_read_url_select_initial", true,
337 "Specifies whether a URL presented in the minibuffer for editing should be selected. This affects find-alternate-url.");
339 minibuffer_auto_complete_preferences["url"] = true;
340 minibuffer.prototype.read_url = function () {
341 keywords(arguments, $prompt = "URL:", $history = "url", $initial_value = "",
342 $use_webjumps = url_completion_use_webjumps,
343 $use_history = url_completion_use_history,
344 $use_bookmarks = url_completion_use_bookmarks);
345 var completer = url_completer ($use_webjumps = arguments.$use_webjumps,
346 $use_bookmarks = arguments.$use_bookmarks,
347 $use_history = arguments.$use_history);
348 var result = yield this.read(
349 $prompt = arguments.$prompt,
350 $history = arguments.$history,
351 $completer = completer,
352 $initial_value = arguments.$initial_value,
353 $auto_complete = "url",
354 $select = minibuffer_read_url_select_initial,
355 $match_required = false);
356 if (!possibly_valid_url(result) && !getWebJump(result))
357 result = this.try_read_url_handlers(result);
358 if (result == "") // well-formedness check. (could be better!)
359 throw ("invalid url or webjump (\""+ result +"\")");
360 yield co_return(result);
363 I.content_charset = interactive_method(
364 $sync = function (ctx) {
365 var buffer = ctx.buffer;
366 if (!(buffer instanceof content_buffer))
367 throw new Error("Current buffer is of invalid type");
368 // -- Charset of content area of focusedWindow
369 var focusedWindow = buffer.focused_frame;
371 return focusedWindow.document.characterSet;
377 I.content_selection = interactive_method(
378 $sync = function (ctx) {
379 // -- Selection of content area of focusedWindow
380 var focusedWindow = this.buffers.current.focused_frame;
381 return focusedWindow.getSelection ();
384 function overlink_update_status(buffer, text) {
386 buffer.window.minibuffer.show("Link: " + text);
388 buffer.window.minibuffer.show("");
391 define_global_mode("overlink_mode",
393 add_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
396 remove_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
402 function go_back (b, prefix)
405 go_forward(b, -prefix);
407 check_buffer(b, content_buffer);
409 if (b.web_navigation.canGoBack)
411 var hist = b.web_navigation.sessionHistory;
412 var idx = hist.index - prefix;
415 b.web_navigation.gotoIndex(idx);
417 throw interactive_error("Can't go back");
421 "Go back in the session history for the current buffer.",
422 function (I) {go_back(I.buffer, I.p);});
424 function go_forward (b, prefix)
429 check_buffer(b, content_buffer);
431 if (b.web_navigation.canGoForward)
433 var hist = b.web_navigation.sessionHistory;
434 var idx = hist.index + prefix;
435 if (idx >= hist.count) idx = hist.count-1;
436 b.web_navigation.gotoIndex(idx);
438 throw interactive_error("Can't go forward");
440 interactive("go-forward",
441 "Go back in the session history for the current buffer.",
442 function (I) {go_forward(I.buffer, I.p);});
444 function stop_loading (b)
446 check_buffer(b, content_buffer);
447 b.web_navigation.stop(Ci.nsIWebNavigation.STOP_NETWORK);
449 interactive("stop-loading",
450 "Stop loading the current document.",
451 function (I) {stop_loading(I.buffer);});
453 function reload (b, bypass_cache, element, forced_charset)
455 check_buffer(b, content_buffer);
457 if (element instanceof Ci.nsIDOMHTMLImageElement) {
459 var cache = Cc['@mozilla.org/image/cache;1']
460 .getService(Ci.imgICache);
461 cache.removeEntry(make_uri(element.src));
464 element.parentNode.replaceChild(element.cloneNode(true), element);
466 var flags = bypass_cache == null ?
467 Ci.nsIWebNavigation.LOAD_FLAGS_NONE :
468 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
470 if (! forced_charset && forced_charset_list)
471 forced_charset = predicate_alist_match(forced_charset_list,
474 if (forced_charset) {
476 var atomservice = Cc['@mozilla.org/atom-service;1']
477 .getService(Ci.nsIAtomService);
478 b.web_navigation.documentCharsetInfo.forcedCharset =
479 atomservice.getAtom(forced_charset);
482 b.web_navigation.reload(flags);
485 interactive("reload",
486 "Reload the current document.\n" +
487 "If a prefix argument is specified, the cache is bypassed. If a "+
488 "DOM node is supplied via browser object, that node will be "+
491 check_buffer(I.buffer, content_buffer);
492 var element = yield read_browser_object(I);
493 reload(I.buffer, I.P, element);
497 * browserDOMWindow: intercept window opening
499 function initialize_browser_dom_window(window) {
500 window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
501 new browser_dom_window(window);
504 define_variable("browser_default_open_target", OPEN_NEW_BUFFER,
505 "Specifies how new window requests by content pages "+
506 "(e.g. by window.open from JavaScript or by using the "+
507 "target attribute of anchor and form elements) will be "+
508 "handled. This will generally be `OPEN_NEW_BUFFER', "+
509 "`OPEN_NEW_BUFFER_BACKGROUND', or `OPEN_NEW_WINDOW'.");
511 function browser_dom_window(window) {
512 this.window = window;
513 this.next_target = null;
515 browser_dom_window.prototype = {
516 QueryInterface: generate_QI(Ci.nsIBrowserDOMWindow),
518 openURI : function(aURI, aOpener, aWhere, aContext) {
520 // Reference: http://www.xulplanet.com/references/xpcomref/ifaces/nsIBrowserDOMWindow.html
521 var target = this.next_target;
522 if (target == null || target == FOLLOW_DEFAULT)
523 target = browser_default_open_target;
524 this.next_target = null;
526 /* Determine the opener buffer */
527 var opener_buffer = get_buffer_from_frame(this.window, aOpener.top);
528 var config = opener_buffer ? opener_buffer.configuration : null;
530 switch (browser_default_open_target) {
531 case OPEN_CURRENT_BUFFER:
533 case FOLLOW_CURRENT_FRAME:
535 case OPEN_NEW_BUFFER:
536 var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
537 this.window.buffers.current = buffer;
538 return buffer.top_frame;
539 case OPEN_NEW_BUFFER_BACKGROUND:
540 var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
541 return buffer.top_frame;
542 case OPEN_NEW_WINDOW:
543 default: /* shouldn't be needed */
545 /* We don't call make_window here, because that will result
546 * in the URL being loaded as the top-level document,
547 * instead of within a browser buffer. Instead, we can
548 * rely on Mozilla using browser.chromeURL. */
549 window_set_extra_arguments(
550 {initial_buffer_creator: buffer_creator(content_buffer, $configuration = config)}
557 add_hook("window_initialize_early_hook", initialize_browser_dom_window);
559 define_keywords("$enable", "$disable", "$doc", "$keymaps");
560 function define_page_mode(name, display_name) {
562 var enable = arguments.$enable;
563 var disable = arguments.$disable;
564 var doc = arguments.$doc;
565 var keymaps = arguments.$keymaps;
566 function page_mode_update_keymap (buffer) {
567 if (keymaps[buffer.input_mode])
568 buffer.keymap = keymaps[buffer.input_mode];
570 define_buffer_mode(name, display_name,
571 $class = "page_mode",
572 $enable = function (buffer) {
576 add_hook.call(buffer, "input_mode_change_hook",
577 page_mode_update_keymap);
578 page_mode_update_keymap(buffer);
581 $disable = function (buffer) {
584 buffer.local_variables = {};
585 buffer.default_browser_object_classes = {};
587 remove_hook.call(buffer, "input_mode_change_hook",
588 page_mode_update_keymap);
589 content_buffer_update_keymap_for_input_mode(buffer);
594 ignore_function_for_get_caller_source_code_reference("define_page_mode");
597 define_variable("auto_mode_list", [],
598 "A list of mappings from URI regular expressions to page modes.");
600 function page_mode_auto_update(buffer) {
601 var uri = buffer.current_URI.spec;
602 var mode = predicate_alist_match(auto_mode_list, uri);
605 else if (buffer.page_mode)
606 conkeror[buffer.page_mode](buffer, false);
609 add_hook("content_buffer_location_change_hook", page_mode_auto_update);