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_buffer_local_hook("content_buffer_finished_loading_hook");
15 define_buffer_local_hook("content_buffer_started_loading_hook");
16 define_buffer_local_hook("content_buffer_progress_change_hook");
17 define_buffer_local_hook("content_buffer_location_change_hook");
18 define_buffer_local_hook("content_buffer_status_change_hook");
19 define_buffer_local_hook("content_buffer_focus_change_hook");
20 define_buffer_local_hook("content_buffer_overlink_change_hook");
21 define_buffer_local_hook("content_buffer_dom_link_added_hook");
23 define_current_buffer_hook("current_content_buffer_finished_loading_hook", "content_buffer_finished_loading_hook");
24 define_current_buffer_hook("current_content_buffer_progress_change_hook", "content_buffer_progress_change_hook");
25 define_current_buffer_hook("current_content_buffer_location_change_hook", "content_buffer_location_change_hook");
26 define_current_buffer_hook("current_content_buffer_status_change_hook", "content_buffer_status_change_hook");
27 define_current_buffer_hook("current_content_buffer_focus_change_hook", "content_buffer_focus_change_hook");
28 define_current_buffer_hook("current_content_buffer_overlink_change_hook", "content_buffer_overlink_change_hook");
30 /* If browser is null, create a new browser */
31 define_keywords("$load");
32 function content_buffer(window, element)
35 this.constructor_begin();
38 conkeror.buffer.call(this, window, element, forward_keywords(arguments));
40 this.browser.addProgressListener(this);
42 this.browser.addEventListener("DOMTitleChanged", function (event) {
43 buffer_title_change_hook.run(buffer);
44 }, true /* capture */, false /* ignore untrusted events */);
46 this.browser.addEventListener("scroll", function (event) {
47 buffer_scroll_hook.run(buffer);
48 }, true /* capture */, false /* ignore untrusted events */);
50 this.browser.addEventListener("focus", function (event) {
51 content_buffer_focus_change_hook.run(buffer, event);
52 }, true /* capture */, false /* ignore untrusted events */);
54 this.browser.addEventListener("mouseover", function (event) {
55 var node = event.target;
56 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
57 node = node.parentNode;
59 content_buffer_overlink_change_hook.run(buffer, node.href);
60 buffer.current_overlink = event.target;
64 this.browser.addEventListener("mouseout", function (event) {
65 if (buffer.current_overlink == event.target) {
66 buffer.current_overlink = null;
67 content_buffer_overlink_change_hook.run(buffer, "");
71 this.browser.addEventListener("mousedown", function (event) {
72 buffer.last_user_input_received = Date.now();
75 this.browser.addEventListener("keypress", function (event) {
76 buffer.last_user_input_received = Date.now();
79 this.browser.addEventListener("DOMLinkAdded", function (event) {
80 content_buffer_dom_link_added_hook.run(buffer, event);
83 buffer.last_user_input_received = null;
85 /* FIXME: Add a handler for blocked popups, and also PopupWindow event */
87 this.browser.addEventListener("DOMPopupBlocked", function (event) {
88 dumpln("PopupWindow: " + event);
92 normal_input_mode(this);
94 this.ignore_initial_blank = true;
96 var lspec = arguments.$load;
98 if (lspec.url == "about:blank")
99 this.ignore_initial_blank = false;
101 /* Ensure that an existing load of about:blank is stopped */
102 this.web_navigation.stop(Ci.nsIWebNavigation.STOP_ALL);
108 this.constructor_end();
111 content_buffer.prototype = {
112 constructor : content_buffer,
114 get scrollX () { return this.top_frame.scrollX; },
115 get scrollY () { return this.top_frame.scrollY; },
116 get scrollMaxX () { return this.top_frame.scrollMaxX; },
117 get scrollMaxY () { return this.top_frame.scrollMaxY; },
120 /* Used to display the correct URI when the buffer opens initially
121 * even before loading has progressed far enough for currentURI to
122 * contain the correct URI. */
125 get display_URI_string () {
126 if (this._display_URI)
127 return this._display_URI;
128 if (this.current_URI)
129 return this.current_URI.spec;
133 get title() { return this.browser.contentTitle; },
134 get description () { return this.display_URI_string; },
136 load : function (load_spec) {
137 apply_load_spec(this, load_spec);
144 /* nsIWebProgressListener */
145 QueryInterface: generate_QI(Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference),
147 // This method is called to indicate state changes.
148 onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
150 const WPL = Components.interfaces.nsIWebProgressListener;
153 if (aStateFlags & WPL.STATE_START)
155 if (aStateFlags & WPL.STATE_STOP)
157 if (aStateFlags & WPL.STATE_IS_REQUEST)
158 flagstr += ",request";
159 if (aStateFlags & WPL.STATE_IS_DOCUMENT)
160 flagstr += ",document";
161 if (aStateFlags & WPL.STATE_IS_NETWORK)
162 flagstr += ",network";
163 if (aStateFlags & WPL.STATE_IS_WINDOW)
164 flagstr += ",window";
165 dumpln("onStateChange: " + flagstr + ", status: " + aStatus);
171 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
172 this._request_count++;
174 else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
175 const NS_ERROR_UNKNOWN_HOST = 2152398878;
176 if (--this._request_count > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
177 // to prevent bug 235825: wait for the request handled
178 // by the automatic keyword resolver
181 // since we (try to) only handle STATE_STOP of the last request,
182 // the count of open requests should now be 0
183 this._request_count = 0;
186 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
187 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
188 // It's okay to clear what the user typed when we start
189 // loading a document. If the user types, this counter gets
190 // set to zero, if the document load ends without an
191 // onLocationChange, this counter gets decremented
192 // (so we keep it while switching tabs after failed loads)
193 //dumpln("*** started loading");
195 content_buffer_started_loading_hook.run(this);
196 this.last_user_input_received = null;
198 else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
199 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
200 if (this.loading == true) {
201 //dumpln("*** finished loading");
202 content_buffer_finished_loading_hook.run(this);
203 this.loading = false;
207 if (aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP |
208 Ci.nsIWebProgressListener.STATE_START)) {
210 this.set_default_message("Done");
214 /* This method is called to indicate progress changes for the currently
216 onProgressChange: function(webProgress, request, curSelf, maxSelf,
217 curTotal, maxTotal) {
218 content_buffer_progress_change_hook.run(this, request, curSelf, maxSelf, curTotal, maxTotal);
221 /* This method is called to indicate a change to the current location.
222 The url can be gotten as location.spec. */
223 onLocationChange : function(webProgress, request, location) {
224 /* Attempt to ignore onLocationChange calls due to the initial
225 * loading of about:blank by all xul:browser elements. */
226 if (location.spec == "about:blank" && this.ignore_initial_blank)
229 this.ignore_initial_blank = false;
231 //dumpln("spec: " + location.spec +" ;;; " + this.display_URI_string);
232 /* Use the real location URI now */
233 this._display_URI = null;
234 content_buffer_location_change_hook.run(this, request, location);
235 this.last_user_input_received = null;
236 buffer_description_change_hook.run(this);
239 // This method is called to indicate a status changes for the currently
240 // loading page. The message is already formatted for display.
241 // Status messages could be displayed in the minibuffer output area.
242 onStatusChange: function(webProgress, request, status, msg) {
243 this.set_default_message(msg);
244 content_buffer_status_change_hook.run(this, request, status, msg);
247 // This method is called when the security state of the browser changes.
248 onSecurityChange: function(webProgress, request, state) {
249 /* FIXME: currently this isn't used */
252 const WPL = Components.interfaces.nsIWebProgressListener;
254 if (state & WPL.STATE_IS_INSECURE) {
255 // update visual indicator
257 var level = "unknown";
258 if (state & WPL.STATE_IS_SECURE) {
259 if (state & WPL.STATE_SECURE_HIGH)
261 else if (state & WPL.STATE_SECURE_MED)
263 else if (state & WPL.STATE_SECURE_LOW)
265 } else if (state & WPL_STATE_IS_BROKEN) {
268 // provide a visual indicator of the security state here.
273 /* Inherit from buffer */
275 __proto__ : buffer.prototype
279 add_hook("current_content_buffer_finished_loading_hook",
281 buffer.window.minibuffer.show("Done");
284 add_hook("current_content_buffer_status_change_hook",
285 function (buffer, request, status, msg) {
286 buffer.set_default_message(msg);
291 define_variable("read_url_handler_list", [],
292 "A list of handler functions which transform a typed url into a valid " +
293 "url or webjump. If the typed input is not valid then each function " +
294 "on this list is tried in turn. The handler function is called with " +
295 "a single string argument and it should return either a string or " +
296 "null. The result of the first function on the list that returns a " +
297 "string is used in place of the input.");
299 /* read_url_make_default_webjump_handler returns a function that
300 * transforms any input into the given webjump. It should be the last
301 * handler on read_url_handler_list (because any input is
303 function read_url_make_default_webjump_handler(default_webjump) {
304 return function(input) {
305 return default_webjump + " " + input;
309 /* read_url_make_blank_url_handler returns a function that replaces a
310 * blank (empty) input with the given url (or webjump). The url may
311 * perform some function, eg. "javascript:location.reload()". */
312 function read_url_make_blank_url_handler(url) {
313 return function(input) {
314 if (input.length == 0)
320 minibuffer.prototype.try_read_url_handlers = function(input) {
322 for (var i = 0; i < read_url_handler_list.length; ++i)
323 if (result = read_url_handler_list[i](input))
328 define_variable("url_completion_use_webjumps", true, "Specifies whether URL completion should complete webjumps.");
329 define_variable("url_completion_use_bookmarks", true, "Specifies whether URL completion should complete bookmarks.");
330 define_variable("url_completion_use_history", false,
331 "Specifies whether URL completion should complete using browser history.");
333 define_variable("minibuffer_read_url_select_initial", true,
334 "Specifies whether a URL presented in the minibuffer for editing should be selected. This affects find-alternate-url.");
336 minibuffer_auto_complete_preferences["url"] = true;
337 minibuffer.prototype.read_url = function () {
338 keywords(arguments, $prompt = "URL:", $history = "url", $initial_value = "",
339 $use_webjumps = url_completion_use_webjumps,
340 $use_history = url_completion_use_history,
341 $use_bookmarks = url_completion_use_bookmarks);
342 var completer = url_completer ($use_webjumps = arguments.$use_webjumps,
343 $use_bookmarks = arguments.$use_bookmarks,
344 $use_history = arguments.$use_history);
345 var result = yield this.read(
346 $prompt = arguments.$prompt,
347 $history = arguments.$history,
348 $completer = completer,
349 $initial_value = arguments.$initial_value,
350 $auto_complete = "url",
351 $select = minibuffer_read_url_select_initial,
352 $match_required = false);
353 if (!possibly_valid_url(result) && !getWebJump(result))
354 result = this.try_read_url_handlers(result);
355 if (result == "") // well-formedness check. (could be better!)
356 throw ("invalid url or webjump (\""+ result +"\")");
357 yield co_return(result);
360 I.content_charset = interactive_method(
361 $sync = function (ctx) {
362 var buffer = ctx.buffer;
363 if (!(buffer instanceof content_buffer))
364 throw new Error("Current buffer is of invalid type");
365 // -- Charset of content area of focusedWindow
366 var focusedWindow = buffer.focused_frame;
368 return focusedWindow.document.characterSet;
374 I.content_selection = interactive_method(
375 $sync = function (ctx) {
376 // -- Selection of content area of focusedWindow
377 var focusedWindow = this.buffers.current.focused_frame;
378 return focusedWindow.getSelection ();
381 function overlink_update_status(buffer, text) {
383 buffer.window.minibuffer.show("Link: " + text);
385 buffer.window.minibuffer.show("");
388 define_global_mode("overlink_mode",
390 add_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
393 remove_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
399 function go_back (b, prefix)
402 go_forward(b, -prefix);
404 check_buffer(b, content_buffer);
406 if (b.web_navigation.canGoBack)
408 var hist = b.web_navigation.sessionHistory;
409 var idx = hist.index - prefix;
412 b.web_navigation.gotoIndex(idx);
414 throw interactive_error("Can't go back");
418 "Go back in the session history for the current buffer.",
419 function (I) {go_back(I.buffer, I.p);});
421 function go_forward (b, prefix)
426 check_buffer(b, content_buffer);
428 if (b.web_navigation.canGoForward)
430 var hist = b.web_navigation.sessionHistory;
431 var idx = hist.index + prefix;
432 if (idx >= hist.count) idx = hist.count-1;
433 b.web_navigation.gotoIndex(idx);
435 throw interactive_error("Can't go forward");
437 interactive("go-forward",
438 "Go back in the session hisory for the current buffer.",
439 function (I) {go_forward(I.buffer, I.p);});
441 function stop_loading (b)
443 check_buffer(b, content_buffer);
444 b.web_navigation.stop(Ci.nsIWebNavigation.STOP_NETWORK);
446 interactive("stop-loading",
447 "Stop loading the current document.",
448 function (I) {stop_loading(I.buffer);});
450 function reload (b, bypass_cache)
452 check_buffer(b, content_buffer);
453 var flags = bypass_cache != null ?
454 Ci.nsIWebNavigation.LOAD_FLAGS_NONE : Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
455 b.web_navigation.reload(flags);
457 interactive("reload",
458 "Reload the current document.\n" +
459 "If a prefix argument is specified, the cache is bypassed.",
460 function (I) {reload(I.buffer, I.P);});
463 * browserDOMWindow: intercept window opening
465 function initialize_browser_dom_window(window) {
466 window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
467 new browser_dom_window(window);
470 define_variable("browser_default_open_target", OPEN_NEW_BUFFER, "Specifies how new window requests by content pages (e.g. by window.open from JavaScript or by using the target attribute of anchor and form elements) will be handled. This will generally be `OPEN_NEW_BUFFER', `OPEN_NEW_BUFFER_BACKGROUND', or `OPEN_NEW_WINDOW'.");
472 function browser_dom_window(window) {
473 this.window = window;
474 this.next_target = null;
476 browser_dom_window.prototype = {
477 QueryInterface: generate_QI(Ci.nsIBrowserDOMWindow),
479 openURI : function(aURI, aOpener, aWhere, aContext) {
481 // Reference: http://www.xulplanet.com/references/xpcomref/ifaces/nsIBrowserDOMWindow.html
482 var target = this.next_target;
483 if (target == null || target == FOLLOW_DEFAULT)
484 target = browser_default_open_target;
485 this.next_target = null;
487 /* Determine the opener buffer */
488 var opener_buffer = get_buffer_from_frame(this.window, aOpener.top);
489 var config = opener_buffer ? opener_buffer.configuration : null;
491 switch (browser_default_open_target) {
492 case OPEN_CURRENT_BUFFER:
493 case FOLLOW_TOP_FRAME:
495 case FOLLOW_CURRENT_FRAME:
497 case OPEN_NEW_BUFFER:
498 var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
499 this.window.buffers.current = buffer;
500 return buffer.top_frame;
501 case OPEN_NEW_BUFFER_BACKGROUND:
502 var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
503 return buffer.top_frame;
504 case OPEN_NEW_WINDOW:
505 default: /* shouldn't be needed */
507 /* We don't call make_window here, because that will result
508 * in the URL being loaded as the top-level document,
509 * instead of within a browser buffer. Instead, we can
510 * rely on Mozilla using browser.chromeURL. */
511 window_set_extra_arguments(
512 {initial_buffer_creator: buffer_creator(content_buffer, $configuration = config)}
519 add_hook("window_initialize_early_hook", initialize_browser_dom_window);
521 define_keywords("$enable", "$disable", "$doc");
522 function define_page_mode(name, display_name) {
524 var enable = arguments.$enable;
525 var disable = arguments.$disable;
526 var doc = arguments.$doc;
527 define_buffer_mode(name, display_name,
528 $class = "page_mode",
530 $disable = function (buffer) {
533 buffer.local_variables = {};
534 buffer.default_browser_object_classes = {};
538 ignore_function_for_get_caller_source_code_reference("define_page_mode");
540 define_variable("auto_mode_list", [], "A list of mappings from URI regular expressions to page modes.");
541 function page_mode_auto_update(buffer) {
542 var uri = buffer.current_URI.spec;
543 var mode = predicate_alist_match(auto_mode_list, uri);
546 else if (buffer.page_mode)
547 conkeror[buffer.page_mode](buffer, false);
550 add_hook("content_buffer_location_change_hook", page_mode_auto_update);