Add generic label mechanism
[conkeror.git] / modules / content-buffer.js
blob1cd28a4260efe3feb970b3bc1a9697920ab200d5
1 /**
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
7 * COPYING file.
8 **/
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)
34 keywords(arguments);
35 this.constructor_begin();
37 conkeror.buffer.call(this, window, element, forward_keywords(arguments));
39 this.browser.addProgressListener(this);
40 var buffer = 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;
58 }, true, false);
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, "");
65 }, true, false);
67 this.browser.addEventListener("mousedown", function (event) {
68 buffer.last_user_input_received = Date.now();
69 }, true, false);
71 this.browser.addEventListener("keypress", function (event) {
72 buffer.last_user_input_received = Date.now();
73 }, true, false);
75 this.browser.addEventListener("DOMLinkAdded", function (event) {
76 content_buffer_dom_link_added_hook.run(buffer, event);
77 }, true, false);
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);
85 }, true, false);
88 normal_input_mode(this);
90 this.ignore_initial_blank = true;
92 var lspec = arguments.$load;
93 if (lspec) {
94 if (lspec.url == "about:blank")
95 this.ignore_initial_blank = false;
96 else {
97 /* Ensure that an existing load of about:blank is stopped */
98 this.web_navigation.stop(Ci.nsIWebNavigation.STOP_ALL);
100 this.load(lspec);
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. */
118 _display_URI : null,
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;
125 return "";
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);
135 _request_count: 0,
137 loading : false,
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;
147 var flagstr = "";
148 if (aStateFlags & WPL.STATE_START)
149 flagstr += ",start";
150 if (aStateFlags & WPL.STATE_STOP)
151 flagstr += ",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);
162 if (!aRequest)
163 return;
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
174 return;
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");
189 this.loading = true;
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)) {
204 if (!this.loading)
205 this.set_default_message("Done");
209 /* This method is called to indicate progress changes for the currently
210 loading page. */
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)
222 return;
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
251 } else {
252 var level = "unknown";
253 if (state & WPL.STATE_IS_SECURE) {
254 if (state & WPL.STATE_SECURE_HIGH)
255 level = "high";
256 else if (state & WPL.STATE_SECURE_MED)
257 level = "medium";
258 else if (state & WPL.STATE_SECURE_LOW)
259 level = "low";
260 } else if (state & WPL_STATE_IS_BROKEN) {
261 level = "mixed";
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",
275 function (buffer) {
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",
305 $select,
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;
319 if (focusedWindow)
320 return focusedWindow.document.characterSet;
321 else
322 return null;
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) {
334 if (text.length > 0)
335 buffer.window.minibuffer.show("Link: " + text);
336 else
337 buffer.window.minibuffer.show("");
340 define_global_mode("overlink_mode",
341 function () {
342 add_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
344 function () {
345 remove_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
348 overlink_mode(true);
350 function open_in_browser(buffer, target, lspec)
352 switch (target) {
353 case OPEN_CURRENT_BUFFER:
354 case FOLLOW_DEFAULT:
355 case FOLLOW_CURRENT_FRAME:
356 case FOLLOW_TOP_FRAME:
357 if (buffer instanceof content_buffer) {
358 apply_load_spec(buffer, lspec);
359 break;
361 target = OPEN_NEW_BUFFER;
362 // If the current buffer is not a content_buffer, use a new buffer.
363 default:
364 create_buffer(buffer.window,
365 buffer_creator(content_buffer,
366 $load = lspec,
367 $configuration = buffer.configuration),
368 target);
369 break;
373 interactive("find-alternate-url",
374 "Edit the current URL in the minibuffer",
375 function (I) {
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",
385 function (I) {
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",
395 function (I) {
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",
404 function (I) {
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;
416 var up;
417 if (url.param != "" || url.query != "")
418 up = url.filePath;
419 else if (url.fileName != "")
420 up = ".";
421 else
422 up = "..";
423 open_in_browser(b, target, b.current_URI.resolve (up));
425 interactive("go-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)
433 if (prefix < 0)
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;
442 if (idx < 0)
443 idx = 0;
444 b.web_navigation.gotoIndex(idx);
445 } else
446 throw interactive_error("Can't go back");
448 interactive(
449 "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)
455 if (prefix < 0)
456 go_back(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);
466 } else
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:
526 return aOpener.top;
527 case FOLLOW_CURRENT_FRAME:
528 return aOpener;
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});
544 return null;
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) {
553 keywords(arguments);
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",
559 $enable = enable,
560 $disable = function (buffer) {
561 if (disable)
562 disable(buffer);
563 buffer.local_variables = {};
565 $doc = doc);
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);
573 if (mode)
574 mode(buffer, true);
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);