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_dom_link_added_hook");
25 define_buffer_local_hook("content_buffer_popup_blocked_hook");
27 define_current_buffer_hook("current_content_buffer_finished_loading_hook", "content_buffer_finished_loading_hook");
28 define_current_buffer_hook("current_content_buffer_progress_change_hook", "content_buffer_progress_change_hook");
29 define_current_buffer_hook("current_content_buffer_location_change_hook", "content_buffer_location_change_hook");
30 define_current_buffer_hook("current_content_buffer_status_change_hook", "content_buffer_status_change_hook");
31 define_current_buffer_hook("current_content_buffer_focus_change_hook", "content_buffer_focus_change_hook");
34 function content_buffer_modality (buffer) {
35 var elem = buffer.focused_element;
36 function push_keymaps (tag) {
37 buffer.content_modalities.map(
40 buffer.keymaps.push(m[tag]);
44 push_keymaps('normal');
46 let p = elem.parentNode;
47 while (p && !(p instanceof Ci.nsIDOMHTMLFormElement))
52 if (elem instanceof Ci.nsIDOMHTMLInputElement) {
53 var type = (elem.getAttribute("type") || "").toLowerCase();
54 if ({checkbox:1, radio:1, submit:1, reset:1}[type])
55 return push_keymaps(type);
57 return push_keymaps('text');
59 if (elem instanceof Ci.nsIDOMHTMLTextAreaElement)
60 return push_keymaps('textarea');
61 if (elem instanceof Ci.nsIDOMHTMLSelectElement)
62 return push_keymaps('select');
63 if (elem instanceof Ci.nsIDOMHTMLAnchorElement)
64 return push_keymaps('anchor');
65 if (elem instanceof Ci.nsIDOMHTMLButtonElement)
66 return push_keymaps('button');
67 if (elem instanceof Ci.nsIDOMHTMLEmbedElement ||
68 elem instanceof Ci.nsIDOMHTMLObjectElement)
70 return push_keymaps('embed');
72 var frame = buffer.focused_frame;
73 if (frame && frame.document.designMode &&
74 frame.document.designMode == "on")
76 return push_keymaps('richedit');
79 switch (elem.contentEditable) {
81 return push_keymaps('richedit');
85 elem = elem.parentNode;
92 define_keywords("$load");
93 function content_buffer (window) {
95 this.constructor_begin();
97 conkeror.buffer.call(this, window, forward_keywords(arguments));
99 this.browser.addProgressListener(this);
101 this.browser.addEventListener("DOMTitleChanged", function (event) {
102 buffer_title_change_hook.run(buffer);
103 }, true /* capture */);
105 this.browser.addEventListener("scroll", function (event) {
106 buffer_scroll_hook.run(buffer);
107 }, true /* capture */);
109 this.browser.addEventListener("focus", function (event) {
110 content_buffer_focus_change_hook.run(buffer, event);
111 }, true /* capture */);
113 this.browser.addEventListener("DOMLinkAdded", function (event) {
114 content_buffer_dom_link_added_hook.run(buffer, event);
115 }, true /* capture */);
117 this.browser.addEventListener("DOMPopupBlocked", function (event) {
118 dumpln("Blocked popup: " + event.popupWindowURI.spec);
119 content_buffer_popup_blocked_hook.run(buffer, event);
120 }, true /* capture */);
122 this.ignore_initial_blank = true;
124 var lspec = arguments.$load;
126 if (lspec.url == "about:blank")
127 this.ignore_initial_blank = false;
129 /* Ensure that an existing load of about:blank is stopped */
130 this.web_navigation.stop(Ci.nsIWebNavigation.STOP_ALL);
136 this.modalities.push(content_buffer_modality);
137 this.content_modalities = [{
138 normal: content_buffer_normal_keymap,
139 form: content_buffer_form_keymap,
140 checkbox: content_buffer_checkbox_keymap,
141 radio: content_buffer_checkbox_keymap,
142 submit: content_buffer_button_keymap,
143 reset: content_buffer_button_keymap,
144 text: content_buffer_text_keymap,
145 textarea: content_buffer_textarea_keymap,
146 select: content_buffer_select_keymap,
147 anchor: content_buffer_anchor_keymap,
148 button: content_buffer_button_keymap,
149 embed: content_buffer_embed_keymap,
150 richedit: content_buffer_richedit_keymap
153 this.constructor_end();
156 content_buffer.prototype = {
157 constructor: content_buffer,
159 destroy: function () {
160 this.browser.removeProgressListener(this);
161 buffer.prototype.destroy.call(this);
164 content_modalities: null,
166 get scrollX () { return this.top_frame.scrollX; },
167 get scrollY () { return this.top_frame.scrollY; },
168 get scrollMaxX () { return this.top_frame.scrollMaxX; },
169 get scrollMaxY () { return this.top_frame.scrollMaxY; },
171 /* Used to display the correct URI when the buffer opens initially
172 * even before loading has progressed far enough for currentURI to
173 * contain the correct URI. */
176 get display_uri_string () {
177 if (this._display_uri)
178 return this._display_uri;
179 if (this.current_uri)
180 return this.current_uri.spec;
184 get title () { return this.browser.contentTitle; },
185 get description () { return this.display_uri_string; },
187 load: function (load_spec) {
188 apply_load_spec(this, load_spec);
195 /* nsIWebProgressListener */
196 QueryInterface: generate_QI(Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference),
198 // This method is called to indicate state changes.
199 onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
201 const WPL = Components.interfaces.nsIWebProgressListener;
204 if (aStateFlags & WPL.STATE_START)
206 if (aStateFlags & WPL.STATE_STOP)
208 if (aStateFlags & WPL.STATE_IS_REQUEST)
209 flagstr += ",request";
210 if (aStateFlags & WPL.STATE_IS_DOCUMENT)
211 flagstr += ",document";
212 if (aStateFlags & WPL.STATE_IS_NETWORK)
213 flagstr += ",network";
214 if (aStateFlags & WPL.STATE_IS_WINDOW)
215 flagstr += ",window";
216 dumpln("onStateChange: " + flagstr + ", status: " + aStatus);
221 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
222 this._request_count++;
223 } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
224 const NS_ERROR_UNKNOWN_HOST = 2152398878;
225 if (--this._request_count > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
226 // to prevent bug 235825: wait for the request handled
227 // by the automatic keyword resolver
230 // since we (try to) only handle STATE_STOP of the last request,
231 // the count of open requests should now be 0
232 this._request_count = 0;
235 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
236 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
237 // It's okay to clear what the user typed when we start
238 // loading a document. If the user types, this counter gets
239 // set to zero, if the document load ends without an
240 // onLocationChange, this counter gets decremented
241 // (so we keep it while switching tabs after failed loads)
242 //dumpln("*** started loading");
244 content_buffer_started_loading_hook.run(this);
245 } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
246 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
247 if (this.loading == true) {
248 //dumpln("*** finished loading");
249 this.loading = false;
250 content_buffer_finished_loading_hook.run(this);
254 if (aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP |
255 Ci.nsIWebProgressListener.STATE_START)) {
257 this.set_default_message("Done");
261 /* This method is called to indicate progress changes for the currently
263 onProgressChange: function (webProgress, request, curSelf, maxSelf,
264 curTotal, maxTotal) {
265 content_buffer_progress_change_hook.run(this, request, curSelf, maxSelf, curTotal, maxTotal);
268 /* This method is called to indicate a change to the current location.
269 The url can be gotten as location.spec. */
270 onLocationChange: function (webProgress, request, location) {
271 /* Attempt to ignore onLocationChange calls due to the initial
272 * loading of about:blank by all xul:browser elements. */
273 if (location.spec == "about:blank" && this.ignore_initial_blank)
276 this.ignore_initial_blank = false;
278 //dumpln("spec: " + location.spec +" ;;; " + this.display_uri_string);
279 /* Use the real location URI now */
280 this._display_uri = null;
281 this.set_input_mode();
282 content_buffer_location_change_hook.run(this, request, location);
283 buffer_description_change_hook.run(this);
286 // This method is called to indicate a status changes for the currently
287 // loading page. The message is already formatted for display.
288 // Status messages could be displayed in the minibuffer output area.
289 onStatusChange: function (webProgress, request, status, msg) {
290 this.set_default_message(msg);
291 content_buffer_status_change_hook.run(this, request, status, msg);
294 // This method is called when the security state of the browser changes.
295 onSecurityChange: function (webProgress, request, state) {
296 /* FIXME: currently this isn't used */
299 const WPL = Components.interfaces.nsIWebProgressListener;
301 if (state & WPL.STATE_IS_INSECURE) {
302 // update visual indicator
304 var level = "unknown";
305 if (state & WPL.STATE_IS_SECURE) {
306 if (state & WPL.STATE_SECURE_HIGH)
308 else if (state & WPL.STATE_SECURE_MED)
310 else if (state & WPL.STATE_SECURE_LOW)
312 } else if (state & WPL_STATE_IS_BROKEN) {
315 // provide a visual indicator of the security state here.
320 /* Inherit from buffer */
321 __proto__: buffer.prototype
325 add_hook("current_content_buffer_finished_loading_hook",
327 buffer.window.minibuffer.show("Done");
330 add_hook("current_content_buffer_status_change_hook",
331 function (buffer, request, status, msg) {
332 buffer.set_default_message(msg);
337 define_variable("read_url_handler_list", [],
338 "A list of handler functions which transform a typed url into a valid " +
339 "url or webjump. If the typed input is not valid then each function " +
340 "on this list is tried in turn. The handler function is called with " +
341 "a single string argument and it should return either a string or " +
342 "null. The result of the first function on the list that returns a " +
343 "string is used in place of the input.");
346 * read_url_make_default_webjump_handler returns a function that
347 * transforms any input into the given webjump. It should be the last
348 * handler on read_url_handler_list (because any input is
351 function read_url_make_default_webjump_handler (default_webjump) {
352 return function (input) {
353 return default_webjump + " " + input;
358 * read_url_make_blank_url_handler returns a function that replaces a
359 * blank (empty) input with the given url (or webjump). The url may
360 * perform some function, eg. "javascript:location.reload()".
362 function read_url_make_blank_url_handler (url) {
363 return function (input) {
364 if (input.length == 0)
370 minibuffer.prototype.try_read_url_handlers = function (input) {
372 for (var i = 0; i < read_url_handler_list.length; ++i) {
373 if ((result = read_url_handler_list[i](input)))
379 define_variable("url_completion_use_webjumps", true,
380 "Specifies whether URL completion should complete webjumps.");
382 define_variable("url_completion_use_bookmarks", true,
383 "Specifies whether URL completion should complete bookmarks.");
385 define_variable("url_completion_use_history", false,
386 "Specifies whether URL completion should complete using browser "+
389 define_variable("minibuffer_read_url_select_initial", true,
390 "Specifies whether a URL presented in the minibuffer for editing "+
391 "should be selected. This affects find-alternate-url.");
394 minibuffer_auto_complete_preferences["url"] = true;
395 minibuffer.prototype.read_url = function () {
396 keywords(arguments, $prompt = "URL:", $history = "url", $initial_value = "",
397 $use_webjumps = url_completion_use_webjumps,
398 $use_history = url_completion_use_history,
399 $use_bookmarks = url_completion_use_bookmarks);
400 var completer = url_completer($use_webjumps = arguments.$use_webjumps,
401 $use_bookmarks = arguments.$use_bookmarks,
402 $use_history = arguments.$use_history);
403 var result = yield this.read(
404 $prompt = arguments.$prompt,
405 $history = arguments.$history,
406 $completer = completer,
407 $initial_value = arguments.$initial_value,
408 $auto_complete = "url",
409 $select = minibuffer_read_url_select_initial,
410 $match_required = false);
411 if (!possibly_valid_url(result) && !get_webjump(result))
412 result = this.try_read_url_handlers(result);
413 if (result == "") // well-formedness check. (could be better!)
414 throw ("invalid url or webjump (\""+ result +"\")");
415 yield co_return(load_spec(result));
422 function overlink_update_status (buffer, node) {
423 if (node && node.href.length > 0)
424 buffer.window.minibuffer.show("Link: " + node.href);
426 buffer.window.minibuffer.show("");
429 function overlink_predicate (node) {
430 while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
431 node = node.parentNode;
435 function overlink_initialize (buffer) {
436 buffer.current_overlink = null;
437 buffer.overlink_mouseover = function (event) {
438 if (buffer != buffer.window.buffers.current)
440 var node = overlink_predicate(event.target);
442 buffer.current_overlink = event.target;
443 overlink_update_status(buffer, node);
446 buffer.overlink_mouseout = function (event) {
447 if (buffer != buffer.window.buffers.current)
449 if (buffer.current_overlink == event.target) {
450 buffer.current_overlink = null;
451 overlink_update_status(buffer, null);
454 buffer.browser.addEventListener("mouseover", buffer.overlink_mouseover, true);
455 buffer.browser.addEventListener("mouseout", buffer.overlink_mouseout, true);
458 define_global_mode("overlink_mode",
460 add_hook("create_buffer_hook", overlink_initialize);
461 for_each_buffer(overlink_initialize);
463 function disable () {
464 remove_hook("create_buffer_hook", overlink_initialize);
465 for_each_buffer(function (b) {
466 b.browser.removeEventListener("mouseover", b.overlink_mouseover, true);
467 b.browser.removeEventListener("mouseout", b.overlink_mouseout, true);
468 delete b.current_overlink;
469 delete b.overlink_mouseover;
470 delete b.overlink_mouseout;
478 * Navigation Commands
480 function go_back (b, prefix) {
482 go_forward(b, -prefix);
484 check_buffer(b, content_buffer);
486 if (b.web_navigation.canGoBack) {
487 var hist = b.web_navigation.sessionHistory;
488 var idx = hist.index - prefix;
491 b.web_navigation.gotoIndex(idx);
493 throw interactive_error("Can't go back");
497 "Go back in the session history for the current buffer.",
498 function (I) {go_back(I.buffer, I.p);});
501 function go_forward (b, prefix) {
505 check_buffer(b, content_buffer);
507 if (b.web_navigation.canGoForward) {
508 var hist = b.web_navigation.sessionHistory;
509 var idx = hist.index + prefix;
510 if (idx >= hist.count) idx = hist.count-1;
511 b.web_navigation.gotoIndex(idx);
513 throw interactive_error("Can't go forward");
516 interactive("forward",
517 "Go forward in the session history for the current buffer.",
518 function (I) {go_forward(I.buffer, I.p);});
521 function stop_loading (b) {
522 check_buffer(b, content_buffer);
523 b.web_navigation.stop(Ci.nsIWebNavigation.STOP_NETWORK);
526 interactive("stop-loading",
527 "Stop loading the current document.",
528 function (I) {stop_loading(I.buffer);});
531 function reload (b, bypass_cache, element, forced_charset) {
532 check_buffer(b, content_buffer);
534 if (element instanceof Ci.nsIDOMHTMLImageElement) {
536 var cache = Cc['@mozilla.org/image/cache;1']
537 .getService(Ci.imgICache);
538 cache.removeEntry(make_uri(element.src));
541 element.parentNode.replaceChild(element.cloneNode(true), element);
542 } else if (b.current_uri.spec != b.display_uri_string) {
543 apply_load_spec(b, load_spec({ uri: b.display_uri_string,
544 forced_charset: forced_charset }));
546 var flags = bypass_cache == null ?
547 Ci.nsIWebNavigation.LOAD_FLAGS_NONE :
548 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
550 if (! forced_charset && forced_charset_list)
551 forced_charset = predicate_alist_match(forced_charset_list,
554 if (forced_charset) {
556 var atomservice = Cc['@mozilla.org/atom-service;1']
557 .getService(Ci.nsIAtomService);
558 b.web_navigation.documentCharsetInfo.forcedCharset =
559 atomservice.getAtom(forced_charset);
562 b.web_navigation.reload(flags);
566 interactive("reload",
567 "Reload the current document.\n" +
568 "If a prefix argument is specified, the cache is bypassed. If a "+
569 "DOM node is supplied via browser object, that node will be "+
572 check_buffer(I.buffer, content_buffer);
573 var element = yield read_browser_object(I);
574 reload(I.buffer, I.P, element, I.forced_charset);
578 * browserDOMWindow: intercept window opening
580 function initialize_browser_dom_window (window) {
581 window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
582 new browser_dom_window(window);
585 define_variable("browser_default_open_target", OPEN_NEW_BUFFER,
586 "Specifies how new window requests by content pages (e.g. by "+
587 "window.open from JavaScript or by using the target attribute of "+
588 "anchor and form elements) will be handled. This will generally "+
589 "be `OPEN_NEW_BUFFER', `OPEN_NEW_BUFFER_BACKGROUND', or "+
590 "`OPEN_NEW_WINDOW'.");
593 function browser_dom_window (window) {
594 this.window = window;
595 this.next_target = null;
597 browser_dom_window.prototype = {
598 constructor: browser_dom_window,
599 QueryInterface: generate_QI(Ci.nsIBrowserDOMWindow),
601 openURI: function (aURI, aOpener, aWhere, aContext) {
602 // Reference: http://www.xulplanet.com/references/xpcomref/ifaces/nsIBrowserDOMWindow.html
603 var target = this.next_target;
604 if (target == null || target == FOLLOW_DEFAULT)
605 target = browser_default_open_target;
606 this.next_target = null;
608 /* Determine the opener buffer */
609 var opener = get_buffer_from_frame(this.window, aOpener);
611 switch (browser_default_open_target) {
612 case OPEN_CURRENT_BUFFER:
614 case FOLLOW_CURRENT_FRAME:
616 case OPEN_NEW_BUFFER:
617 var buffer = new content_buffer(this.window, $opener = opener);
618 this.window.buffers.current = buffer;
619 return buffer.top_frame;
620 case OPEN_NEW_BUFFER_BACKGROUND:
621 var buffer = new content_buffer(this.window, $opener = opener);
622 return buffer.top_frame;
623 case OPEN_NEW_WINDOW:
624 default: /* shouldn't be needed */
626 /* We don't call make_window here, because that will result
627 * in the URL being loaded as the top-level document,
628 * instead of within a browser buffer. Instead, we can
629 * rely on Mozilla using browser.chromeURL. */
630 window_set_extra_arguments(
631 {initial_buffer_creator: buffer_creator(content_buffer, $opener = opener)}
638 add_hook("window_initialize_early_hook", initialize_browser_dom_window);
640 define_keywords("$display_name", "$enable", "$disable", "$doc");
641 function define_page_mode (name) {
643 var display_name = arguments.$display_name;
644 var enable = arguments.$enable;
645 var disable = arguments.$disable;
646 var doc = arguments.$doc;
647 define_buffer_mode(name,
648 $display_name = display_name,
649 $class = "page_mode",
650 $enable = function (buffer) {
652 local: { __proto__: buffer.local }
656 buffer.set_input_mode();
658 $disable = function (buffer) {
662 buffer.default_browser_object_classes = {};
663 buffer.set_input_mode();
667 ignore_function_for_get_caller_source_code_reference("define_page_mode");
670 define_variable("auto_mode_list", [],
671 "A list of mappings from URI regular expressions to page modes.");
673 function page_mode_auto_update (buffer) {
674 var uri = buffer.current_uri.spec;
675 var mode = predicate_alist_match(auto_mode_list, uri);
678 else if (buffer.page_mode)
679 conkeror[buffer.page_mode](buffer, false);
682 add_hook("content_buffer_location_change_hook", page_mode_auto_update);
684 provide("content-buffer");