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);
290 define_variable("url_completion_use_webjumps", true, "Specifies whether URL completion should complete webjumps.");
291 define_variable("url_completion_use_bookmarks", true, "Specifies whether URL completion should complete bookmarks.");
292 define_variable("url_completion_use_history", false,
293 "Specifies whether URL completion should complete using browser history.");
295 minibuffer_auto_complete_preferences["url"] = true;
296 minibuffer.prototype.read_url = function () {
297 keywords(arguments, $prompt = "URL:", $history = "url", $initial_value = "",
298 $use_webjumps = url_completion_use_webjumps,
299 $use_history = url_completion_use_history,
300 $use_bookmarks = url_completion_use_bookmarks);
301 var completer = url_completer ($use_webjumps = arguments.$use_webjumps,
302 $use_bookmarks = arguments.$use_bookmarks,
303 $use_history = arguments.$use_history);
304 var result = yield this.read(
305 $prompt = arguments.$prompt,
306 $history = arguments.$history,
307 $completer = completer,
308 $initial_value = arguments.$initial_value,
309 $auto_complete = "url",
311 $match_required = false);
312 if (result == "") // well-formedness check. (could be better!)
313 throw ("invalid url or webjump (\""+ result +"\")");
314 yield co_return(result);
317 I.content_charset = interactive_method(
318 $sync = function (ctx) {
319 var buffer = ctx.buffer;
320 if (!(buffer instanceof content_buffer))
321 throw new Error("Current buffer is of invalid type");
322 // -- Charset of content area of focusedWindow
323 var focusedWindow = buffer.focused_frame;
325 return focusedWindow.document.characterSet;
331 I.content_selection = interactive_method(
332 $sync = function (ctx) {
333 // -- Selection of content area of focusedWindow
334 var focusedWindow = this.buffers.current.focused_frame;
335 return focusedWindow.getSelection ();
338 function overlink_update_status(buffer, text) {
340 buffer.window.minibuffer.show("Link: " + text);
342 buffer.window.minibuffer.show("");
345 define_global_mode("overlink_mode",
347 add_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
350 remove_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
356 function go_back (b, prefix)
359 go_forward(b, -prefix);
361 check_buffer(b, content_buffer);
363 if (b.web_navigation.canGoBack)
365 var hist = b.web_navigation.sessionHistory;
366 var idx = hist.index - prefix;
369 b.web_navigation.gotoIndex(idx);
371 throw interactive_error("Can't go back");
375 "Go back in the session history for the current buffer.",
376 function (I) {go_back(I.buffer, I.p);});
378 function go_forward (b, prefix)
383 check_buffer(b, content_buffer);
385 if (b.web_navigation.canGoForward)
387 var hist = b.web_navigation.sessionHistory;
388 var idx = hist.index + prefix;
389 if (idx >= hist.count) idx = hist.count-1;
390 b.web_navigation.gotoIndex(idx);
392 throw interactive_error("Can't go forward");
394 interactive("go-forward",
395 "Go back in the session hisory for the current buffer.",
396 function (I) {go_forward(I.buffer, I.p);});
398 function stop_loading (b)
400 check_buffer(b, content_buffer);
401 b.web_navigation.stop(Ci.nsIWebNavigation.STOP_NETWORK);
403 interactive("stop-loading",
404 "Stop loading the current document.",
405 function (I) {stop_loading(I.buffer);});
407 function reload (b, bypass_cache)
409 check_buffer(b, content_buffer);
410 var flags = bypass_cache != null ?
411 Ci.nsIWebNavigation.LOAD_FLAGS_NONE : Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
412 b.web_navigation.reload(flags);
414 interactive("reload",
415 "Reload the current document.\n" +
416 "If a prefix argument is specified, the cache is bypassed.",
417 function (I) {reload(I.buffer, I.P);});
420 * browserDOMWindow: intercept window opening
422 function initialize_browser_dom_window(window) {
423 window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
424 new browser_dom_window(window);
427 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'.");
429 function browser_dom_window(window) {
430 this.window = window;
431 this.next_target = null;
433 browser_dom_window.prototype = {
434 QueryInterface: generate_QI(Ci.nsIBrowserDOMWindow),
436 openURI : function(aURI, aOpener, aWhere, aContext) {
438 // Reference: http://www.xulplanet.com/references/xpcomref/ifaces/nsIBrowserDOMWindow.html
439 var target = this.next_target;
440 if (target == null || target == FOLLOW_DEFAULT)
441 target = browser_default_open_target;
442 this.next_target = null;
444 /* Determine the opener buffer */
445 var opener_buffer = get_buffer_from_frame(this.window, aOpener.top);
446 var config = opener_buffer ? opener_buffer.configuration : null;
448 switch (browser_default_open_target) {
449 case OPEN_CURRENT_BUFFER:
450 case FOLLOW_TOP_FRAME:
452 case FOLLOW_CURRENT_FRAME:
454 case OPEN_NEW_BUFFER:
455 var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
456 this.window.buffers.current = buffer;
457 return buffer.top_frame;
458 case OPEN_NEW_BUFFER_BACKGROUND:
459 var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
460 return buffer.top_frame;
461 case OPEN_NEW_WINDOW:
462 default: /* shouldn't be needed */
464 /* We don't call make_window here, because that will result
465 * in the URL being loaded as the top-level document,
466 * instead of within a browser buffer. Instead, we can
467 * rely on Mozilla using browser.chromeURL. */
468 window_set_extra_arguments(
469 {initial_buffer_creator: buffer_creator(content_buffer, $configuration = config)}
476 add_hook("window_initialize_early_hook", initialize_browser_dom_window);
478 define_keywords("$enable", "$disable", "$doc");
479 function define_page_mode(name, display_name) {
481 var enable = arguments.$enable;
482 var disable = arguments.$disable;
483 var doc = arguments.$doc;
484 define_buffer_mode(name, display_name,
485 $class = "page_mode",
487 $disable = function (buffer) {
490 buffer.local_variables = {};
491 buffer.default_browser_object_classes = {};
495 ignore_function_for_get_caller_source_code_reference("define_page_mode");
497 define_variable("auto_mode_list", [], "A list of mappings from URI regular expressions to page modes.");
498 function page_mode_auto_update(buffer) {
499 var uri = buffer.current_URI.spec;
500 var mode = predicate_alist_match(auto_mode_list, uri);
503 else if (buffer.page_mode)
504 conkeror[buffer.page_mode](buffer, false);
507 add_hook("content_buffer_location_change_hook", page_mode_auto_update);