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();
37 conkeror
.buffer
.call(this, window
, element
, forward_keywords(arguments
));
39 this.browser
.addProgressListener(this);
41 this.browser
.addEventListener("DOMTitleChanged", function (event
) {
42 buffer_title_change_hook
.run(buffer
);
43 }, true /* capture */, false /* ignore untrusted events */);
45 this.browser
.addEventListener("scroll", function (event
) {
46 buffer_scroll_hook
.run(buffer
);
47 }, true /* capture */, false /* ignore untrusted events */);
49 this.browser
.addEventListener("focus", function (event
) {
50 content_buffer_focus_change_hook
.run(buffer
, event
);
51 }, true /* capture */, false /* ignore untrusted events */);
53 this.browser
.addEventListener("mouseover", function (event
) {
54 if (event
.target
instanceof Ci
.nsIDOMHTMLAnchorElement
) {
55 content_buffer_overlink_change_hook
.run(buffer
, event
.target
.href
);
56 buffer
.current_overlink
= event
.target
;
60 this.browser
.addEventListener("mouseout", function (event
) {
61 if (buffer
.current_overlink
== event
.target
) {
62 buffer
.current_overlink
= null;
63 content_buffer_overlink_change_hook
.run(buffer
, "");
67 this.browser
.addEventListener("mousedown", function (event
) {
68 buffer
.last_user_input_received
= Date
.now();
71 this.browser
.addEventListener("keypress", function (event
) {
72 buffer
.last_user_input_received
= Date
.now();
75 this.browser
.addEventListener("DOMLinkAdded", function (event
) {
76 content_buffer_dom_link_added_hook
.run(buffer
, event
);
79 buffer
.last_user_input_received
= null;
81 /* FIXME: Add a handler for blocked popups, and also PopupWindow event */
83 this.browser.addEventListener("DOMPopupBlocked", function (event) {
84 dumpln("PopupWindow: " + event);
88 normal_input_mode(this);
90 this.ignore_initial_blank
= true;
92 var lspec
= arguments
.$load
;
94 if (lspec
.url
== "about:blank")
95 this.ignore_initial_blank
= false;
97 /* Ensure that an existing load of about:blank is stopped */
98 this.web_navigation
.stop(Ci
.nsIWebNavigation
.STOP_ALL
);
104 this.constructor_end();
106 content_buffer
.prototype = {
107 constructor : content_buffer
,
109 get scrollX () { return this.top_frame
.scrollX
; },
110 get scrollY () { return this.top_frame
.scrollY
; },
111 get scrollMaxX () { return this.top_frame
.scrollMaxX
; },
112 get scrollMaxY () { return this.top_frame
.scrollMaxY
; },
115 /* Used to display the correct URI when the buffer opens initially
116 * even before loading has progressed far enough for currentURI to
117 * contain the correct URI. */
120 get display_URI_string () {
121 if (this._display_URI
)
122 return this._display_URI
;
123 if (this.current_URI
)
124 return this.current_URI
.spec
;
128 get title() { return this.browser
.contentTitle
; },
129 get description () { return this.display_URI_string
; },
131 load : function (load_spec
) {
132 apply_load_spec(this, load_spec
);
139 /* nsIWebProgressListener */
140 QueryInterface
: generate_QI(Ci
.nsIWebProgressListener
, Ci
.nsISupportsWeakReference
),
142 // This method is called to indicate state changes.
143 onStateChange: function(aWebProgress
, aRequest
, aStateFlags
, aStatus
) {
145 const WPL = Components.interfaces.nsIWebProgressListener;
148 if (aStateFlags & WPL.STATE_START)
150 if (aStateFlags & WPL.STATE_STOP)
152 if (aStateFlags & WPL.STATE_IS_REQUEST)
153 flagstr += ",request";
154 if (aStateFlags & WPL.STATE_IS_DOCUMENT)
155 flagstr += ",document";
156 if (aStateFlags & WPL.STATE_IS_NETWORK)
157 flagstr += ",network";
158 if (aStateFlags & WPL.STATE_IS_WINDOW)
159 flagstr += ",window";
160 dumpln("onStateChange: " + flagstr + ", status: " + aStatus);
166 if (aStateFlags
& Ci
.nsIWebProgressListener
.STATE_START
) {
167 this._request_count
++;
169 else if (aStateFlags
& Ci
.nsIWebProgressListener
.STATE_STOP
) {
170 const NS_ERROR_UNKNOWN_HOST
= 2152398878;
171 if (--this._request_count
> 0 && aStatus
== NS_ERROR_UNKNOWN_HOST
) {
172 // to prevent bug 235825: wait for the request handled
173 // by the automatic keyword resolver
176 // since we (try to) only handle STATE_STOP of the last request,
177 // the count of open requests should now be 0
178 this._request_count
= 0;
181 if (aStateFlags
& Ci
.nsIWebProgressListener
.STATE_START
&&
182 aStateFlags
& Ci
.nsIWebProgressListener
.STATE_IS_NETWORK
) {
183 // It's okay to clear what the user typed when we start
184 // loading a document. If the user types, this counter gets
185 // set to zero, if the document load ends without an
186 // onLocationChange, this counter gets decremented
187 // (so we keep it while switching tabs after failed loads)
188 //dumpln("*** started loading");
190 content_buffer_started_loading_hook
.run(this);
191 this.last_user_input_received
= null;
193 else if (aStateFlags
& Ci
.nsIWebProgressListener
.STATE_STOP
&&
194 aStateFlags
& Ci
.nsIWebProgressListener
.STATE_IS_NETWORK
) {
195 if (this.loading
== true) {
196 //dumpln("*** finished loading");
197 content_buffer_finished_loading_hook
.run(this);
198 this.loading
= false;
202 if (aStateFlags
& (Ci
.nsIWebProgressListener
.STATE_STOP
|
203 Ci
.nsIWebProgressListener
.STATE_START
)) {
205 this.set_default_message("Done");
209 /* This method is called to indicate progress changes for the currently
211 onProgressChange: function(webProgress
, request
, curSelf
, maxSelf
,
212 curTotal
, maxTotal
) {
213 content_buffer_progress_change_hook
.run(this, request
, curSelf
, maxSelf
, curTotal
, maxTotal
);
216 /* This method is called to indicate a change to the current location.
217 The url can be gotten as location.spec. */
218 onLocationChange : function(webProgress
, request
, location
) {
219 /* Attempt to ignore onLocationChange calls due to the initial
220 * loading of about:blank by all xul:browser elements. */
221 if (location
.spec
== "about:blank" && this.ignore_initial_blank
)
224 this.ignore_initial_blank
= false;
226 //dumpln("spec: " + location.spec +" ;;; " + this.display_URI_string);
227 /* Use the real location URI now */
228 this._display_URI
= null;
229 content_buffer_location_change_hook
.run(this, request
, location
);
230 this.last_user_input_received
= null;
231 buffer_description_change_hook
.run(this);
234 // This method is called to indicate a status changes for the currently
235 // loading page. The message is already formatted for display.
236 // Status messages could be displayed in the minibuffer output area.
237 onStatusChange: function(webProgress
, request
, status
, msg
) {
238 this.set_default_message(msg
);
239 content_buffer_status_change_hook
.run(this, request
, status
, msg
);
242 // This method is called when the security state of the browser changes.
243 onSecurityChange: function(webProgress
, request
, state
) {
244 /* FIXME: currently this isn't used */
247 const WPL = Components.interfaces.nsIWebProgressListener;
249 if (state & WPL.STATE_IS_INSECURE) {
250 // update visual indicator
252 var level = "unknown";
253 if (state & WPL.STATE_IS_SECURE) {
254 if (state & WPL.STATE_SECURE_HIGH)
256 else if (state & WPL.STATE_SECURE_MED)
258 else if (state & WPL.STATE_SECURE_LOW)
260 } else if (state & WPL_STATE_IS_BROKEN) {
263 // provide a visual indicator of the security state here.
268 /* Inherit from buffer */
270 __proto__
: buffer
.prototype
274 add_hook("current_content_buffer_finished_loading_hook",
276 buffer.window.minibuffer.show("Done");
279 add_hook("current_content_buffer_status_change_hook",
280 function (buffer, request, status, msg) {
281 buffer.set_default_message(msg);
285 define_variable("url_completion_use_webjumps", true, "Specifies whether URL completion should complete webjumps.");
286 define_variable("url_completion_use_bookmarks", true, "Specifies whether URL completion should complete bookmarks.");
287 define_variable("url_completion_use_history", false,
288 "Specifies whether URL completion should complete using browser history.");
290 minibuffer_auto_complete_preferences
["url"] = true;
291 minibuffer
.prototype.read_url = function () {
292 keywords(arguments
, $prompt
= "URL:", $history
= "url", $initial_value
= "",
293 $use_webjumps
= url_completion_use_webjumps
,
294 $use_history
= url_completion_use_history
,
295 $use_bookmarks
= url_completion_use_bookmarks
);
296 var completer
= url_completer ($use_webjumps
= arguments
.$use_webjumps
,
297 $use_bookmarks
= arguments
.$use_bookmarks
,
298 $use_history
= arguments
.$use_history
);
299 var result
= yield this.read(
300 $prompt
= arguments
.$prompt
,
301 $history
= arguments
.$history
,
302 $completer
= completer
,
303 $initial_value
= arguments
.$initial_value
,
304 $auto_complete
= "url",
306 $match_required
= false);
307 if (result
== "") // well-formedness check. (could be better!)
308 throw ("invalid url or webjump (\""+ result
+"\")");
309 yield co_return(result
);
312 I.content_charset = interactive_method(
313 $sync = function (ctx) {
314 var buffer = ctx.buffer;
315 if (!(buffer instanceof content_buffer))
316 throw new Error("Current buffer is of invalid type");
317 // -- Charset of content area of focusedWindow
318 var focusedWindow = buffer.focused_frame;
320 return focusedWindow.document.characterSet;
326 I.content_selection = interactive_method(
327 $sync = function (ctx) {
328 // -- Selection of content area of focusedWindow
329 var focusedWindow = this.buffers.current.focused_frame;
330 return focusedWindow.getSelection ();
333 function overlink_update_status(buffer
, text
) {
335 buffer
.window
.minibuffer
.show("Link: " + text
);
337 buffer
.window
.minibuffer
.show("");
340 define_global_mode("overlink_mode",
342 add_hook("current_content_buffer_overlink_change_hook", overlink_update_status
);
345 remove_hook("current_content_buffer_overlink_change_hook", overlink_update_status
);
350 function open_in_browser(buffer
, target
, lspec
)
353 case OPEN_CURRENT_BUFFER
:
355 case FOLLOW_CURRENT_FRAME
:
356 case FOLLOW_TOP_FRAME
:
357 if (buffer
instanceof content_buffer
) {
358 apply_load_spec(buffer
, lspec
);
361 target
= OPEN_NEW_BUFFER
;
362 // If the current buffer is not a content_buffer, use a new buffer.
364 create_buffer(buffer
.window
,
365 buffer_creator(content_buffer
,
367 $configuration
= buffer
.configuration
),
373 interactive("find-alternate-url",
374 "Edit the current URL in the minibuffer",
376 var target
= I
.browse_target("find-url");
377 check_buffer(I
.buffer
, content_buffer
);
378 open_in_browser(I
.buffer
, target
,
379 (yield I
.minibuffer
.read_url($prompt
= browse_target_prompt(target
),
380 $initial_value
= I
.buffer
.display_URI_string
)));
383 interactive("find-url",
384 "Open a URL in the current buffer",
386 var target
= I
.browse_target("find-url");
387 open_in_browser(I
.buffer
, target
,
388 (yield I
.minibuffer
.read_url($prompt
= browse_target_prompt(target
))));
390 default_browse_targets
["find-url"] = [OPEN_CURRENT_BUFFER
, OPEN_NEW_BUFFER
, OPEN_NEW_WINDOW
];
393 interactive("find-url-new-buffer",
394 "Open a URL in a new buffer",
396 var target
= I
.browse_target("find-url-new-buffer");
397 open_in_browser(I
.buffer
, target
,
398 (yield I
.minibuffer
.read_url($prompt
= browse_target_prompt(target
))));
400 default_browse_targets
["find-url-new-buffer"] = [OPEN_NEW_BUFFER
, OPEN_NEW_WINDOW
];
402 interactive("find-url-new-window",
403 "Open a URL in a new window",
405 var target
= I
.browse_target("find-url-new-window");
406 open_in_browser(I
.buffer
, target
,
407 (yield I
.minibuffer
.read_url($prompt
= browse_target_prompt(target
))));
409 default_browse_targets
["find-url-new-window"] = [OPEN_NEW_WINDOW
];
411 function go_up (b
, target
)
413 var url
= Cc
["@mozilla.org/network/standard-url;1"]
414 .createInstance (Ci
.nsIURL
);
415 url
.spec
= b
.current_URI
.spec
;
417 if (url
.param
!= "" || url
.query
!= "")
419 else if (url
.fileName
!= "")
423 open_in_browser(b
, target
, b
.current_URI
.resolve (up
));
426 "Go to the parent directory of the current URL",
427 function (I
) { go_up(check_buffer(I
.buffer
, content_buffer
), I
.browse_target("go-up")); });
428 default_browse_targets
["go-up"] = "find-url";
431 function go_back (b
, prefix
)
434 go_forward(b
, -prefix
);
436 check_buffer(b
, content_buffer
);
438 if (b
.web_navigation
.canGoBack
)
440 var hist
= b
.web_navigation
.sessionHistory
;
441 var idx
= hist
.index
- prefix
;
444 b
.web_navigation
.gotoIndex(idx
);
446 throw interactive_error("Can't go back");
450 "Go back in the session history for the current buffer.",
451 function (I
) {go_back(I
.buffer
, I
.p
);});
453 function go_forward (b
, prefix
)
458 check_buffer(b
, content_buffer
);
460 if (b
.web_navigation
.canGoForward
)
462 var hist
= b
.web_navigation
.sessionHistory
;
463 var idx
= hist
.index
+ prefix
;
464 if (idx
>= hist
.count
) idx
= hist
.count
-1;
465 b
.web_navigation
.gotoIndex(idx
);
467 throw interactive_error("Can't go forward");
469 interactive("go-forward",
470 "Go back in the session hisory for the current buffer.",
471 function (I
) {go_forward(I
.buffer
, I
.p
);});
473 function stop_loading (b
)
475 check_buffer(b
, content_buffer
);
476 b
.web_navigation
.stop(Ci
.nsIWebNavigation
.STOP_NETWORK
);
478 interactive("stop-loading",
479 "Stop loading the current document.",
480 function (I
) {stop_loading(I
.buffer
);});
482 function reload (b
, bypass_cache
)
484 check_buffer(b
, content_buffer
);
485 var flags
= bypass_cache
!= null ?
486 Ci
.nsIWebNavigation
.LOAD_FLAGS_NONE
: Ci
.nsIWebNavigation
.LOAD_FLAGS_BYPASS_CACHE
;
487 b
.web_navigation
.reload(flags
);
489 interactive("reload",
490 "Reload the current document.\n" +
491 "If a prefix argument is specified, the cache is bypassed.",
492 function (I
) {reload(I
.buffer
, I
.P
);});
495 * browserDOMWindow: intercept window opening
497 function initialize_browser_dom_window(window
) {
498 window
.QueryInterface(Ci
.nsIDOMChromeWindow
).browserDOMWindow
=
499 new browser_dom_window(window
);
502 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'.");
504 function browser_dom_window(window
) {
505 this.window
= window
;
506 this.next_target
= null;
508 browser_dom_window
.prototype = {
509 QueryInterface
: generate_QI(Ci
.nsIBrowserDOMWindow
),
511 openURI : function(aURI
, aOpener
, aWhere
, aContext
) {
513 // Reference: http://www.xulplanet.com/references/xpcomref/ifaces/nsIBrowserDOMWindow.html
514 var target
= this.next_target
;
515 if (target
== null || target
== FOLLOW_DEFAULT
)
516 target
= browser_default_open_target
;
517 this.next_target
= null;
519 /* Determine the opener buffer */
520 var opener_buffer
= get_buffer_from_frame(this.window
, aOpener
.top
);
521 var config
= opener_buffer
? opener_buffer
.configuration
: null;
523 switch (browser_default_open_target
) {
524 case OPEN_CURRENT_BUFFER
:
525 case FOLLOW_TOP_FRAME
:
527 case FOLLOW_CURRENT_FRAME
:
529 case OPEN_NEW_BUFFER
:
530 var buffer
= new content_buffer(this.window
, null /* element */, $configuration
= config
);
531 this.window
.buffers
.current
= buffer
;
532 return buffer
.top_frame
;
533 case OPEN_NEW_BUFFER_BACKGROUND
:
534 var buffer
= new content_buffer(this.window
, null /* element */, $configuration
= config
);
535 return buffer
.top_frame
;
536 case OPEN_NEW_WINDOW
:
537 default: /* shouldn't be needed */
539 /* We don't call make_window here, because that will result
540 * in the URL being loaded as the top-level document,
541 * instead of within a browser buffer. Instead, we can
542 * rely on Mozilla using browser.chromeURL. */
543 window_set_extra_arguments({initial_buffer_configuration
: config
});
549 add_hook("window_initialize_early_hook", initialize_browser_dom_window
);
551 define_keywords("$enable", "$disable", "$doc");
552 function define_page_mode(name
, display_name
) {
554 var enable
= arguments
.$enable
;
555 var disable
= arguments
.$disable
;
556 var doc
= arguments
.$doc
;
557 define_buffer_mode(name
, display_name
,
558 $class = "page_mode",
560 $disable = function (buffer
) {
563 buffer
.local_variables
= {};
567 ignore_function_for_get_caller_source_code_reference("define_page_mode");
569 define_variable("auto_mode_list", [], "A list of mappings from URI regular expressions to page modes.");
570 function page_mode_auto_update(buffer
) {
571 var uri
= buffer
.current_URI
.spec
;
572 var mode
= predicate_alist_match(auto_mode_list
, uri
);
575 else if (buffer
.page_mode
)
576 conkeror
[buffer
.page_mode
](buffer
, false);
579 add_hook("content_buffer_location_change_hook", page_mode_auto_update
);