Revamped and simplified load_spec infrastructure
[conkeror.git] / modules / content-buffer.js
blobe2236ead18e028b07fdb1d121faafc237a67f137
1 require("load-spec.js");
3 require_later("content-buffer-input.js");
5 define_buffer_local_hook("content_buffer_finished_loading_hook");
6 define_buffer_local_hook("content_buffer_started_loading_hook");
7 define_buffer_local_hook("content_buffer_progress_change_hook");
8 define_buffer_local_hook("content_buffer_location_change_hook");
9 define_buffer_local_hook("content_buffer_status_change_hook");
10 define_buffer_local_hook("content_buffer_focus_change_hook");
11 define_buffer_local_hook("content_buffer_overlink_change_hook");
12 define_buffer_local_hook("content_buffer_dom_link_added_hook");
14 define_current_buffer_hook("current_content_buffer_finished_loading_hook", "content_buffer_finished_loading_hook");
15 define_current_buffer_hook("current_content_buffer_progress_change_hook", "content_buffer_progress_change_hook");
16 define_current_buffer_hook("current_content_buffer_location_change_hook", "content_buffer_location_change_hook");
17 define_current_buffer_hook("current_content_buffer_status_change_hook", "content_buffer_status_change_hook");
18 define_current_buffer_hook("current_content_buffer_focus_change_hook", "content_buffer_focus_change_hook");
19 define_current_buffer_hook("current_content_buffer_overlink_change_hook", "content_buffer_overlink_change_hook");
21 /* If browser is null, create a new browser */
22 define_keywords("$load");
23 function content_buffer(window, element)
25     keywords(arguments);
26     this.constructor_begin();
28     conkeror.buffer.call(this, window, element, forward_keywords(arguments));
30     this.browser.addProgressListener(this);
31     var buffer = this;
32     this.browser.addEventListener("DOMTitleChanged", function (event) {
33             buffer_title_change_hook.run(buffer);
34         }, true /* capture */, false /* ignore untrusted events */);
36     this.browser.addEventListener("scroll", function (event) {
37             buffer_scroll_hook.run(buffer);
38         }, true /* capture */, false /* ignore untrusted events */);
40     this.browser.addEventListener("focus", function (event) {
41             content_buffer_focus_change_hook.run(buffer, event);
42         }, true /* capture */, false /* ignore untrusted events */);
44     this.browser.addEventListener("mouseover", function (event) {
45             if (event.target instanceof Ci.nsIDOMHTMLAnchorElement) {
46                 content_buffer_overlink_change_hook.run(buffer, event.target.href);
47                 buffer.current_overlink = event.target;
48             }
49         }, true, false);
51     this.browser.addEventListener("mouseout", function (event) {
52             if (buffer.current_overlink == event.target) {
53                 buffer.current_overlink = null;
54                 content_buffer_overlink_change_hook.run(buffer, "");
55             }
56         }, true, false);
58     this.browser.addEventListener("mousedown", function (event) {
59             buffer.last_user_input_received = Date.now();
60         }, true, false);
62     this.browser.addEventListener("keypress", function (event) {
63             buffer.last_user_input_received = Date.now();
64         }, true, false);
66     this.browser.addEventListener("DOMLinkAdded", function (event) {
67             content_buffer_dom_link_added_hook.run(buffer, event);
68         }, true, false);
70     buffer.last_user_input_received = null;
72     /* FIXME: Add a handler for blocked popups, and also PopupWindow event */
73     /*
74     this.browser.addEventListener("DOMPopupBlocked", function (event) {
75             dumpln("PopupWindow: " + event);
76         }, true, false);
77     */
79     normal_input_mode(this);
81     this.ignore_initial_blank = true;
83     var lspec = arguments.$load;
84     if (lspec) {
85         if (lspec.url == "about:blank")
86             this.ignore_initial_blank = false;
87         else {
88             /* Ensure that an existing load of about:blank is stopped */
89             this.web_navigation.stop(Ci.nsIWebNavigation.STOP_ALL);
91             this.load(lspec);
92         }
93     }
95     this.constructor_end();
97 content_buffer.prototype = {
98     constructor : content_buffer,
100     get scrollX () { return this.top_frame.scrollX; },
101     get scrollY () { return this.top_frame.scrollY; },
102     get scrollMaxX () { return this.top_frame.scrollMaxX; },
103     get scrollMaxY () { return this.top_frame.scrollMaxY; },
106     /* Used to display the correct URI when the buffer opens initially
107      * even before loading has progressed far enough for currentURI to
108      * contain the correct URI. */
109     _display_URI : null,
111     get display_URI_string () {
112         if (this._display_URI)
113             return this._display_URI;
114         if (this.current_URI)
115             return this.current_URI.spec;
116         return "";
117     },
119     get title() { return this.browser.contentTitle; },
120     get description () { return this.display_URI_string; },
122     load : function (load_spec) {
123         apply_load_spec(this, load_spec);
124     },
126     _request_count: 0,
128     loading : false,
130     /* nsIWebProgressListener */
131     QueryInterface: generate_QI(Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference),
133     // This method is called to indicate state changes.
134     onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
136         const WPL = Components.interfaces.nsIWebProgressListener;
138         var flagstr = "";
139         if (aStateFlags & WPL.STATE_START)
140             flagstr += ",start";
141         if (aStateFlags & WPL.STATE_STOP)
142             flagstr += ",stop";
143         if (aStateFlags & WPL.STATE_IS_REQUEST)
144             flagstr += ",request";
145         if (aStateFlags & WPL.STATE_IS_DOCUMENT)
146             flagstr += ",document";
147         if (aStateFlags & WPL.STATE_IS_NETWORK)
148             flagstr += ",network";
149         if (aStateFlags & WPL.STATE_IS_WINDOW)
150             flagstr += ",window";
151         dumpln("onStateChange: " + flagstr + ", status: " + aStatus);
153         if (!aRequest)
154             return;
157         if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
158             this._request_count++;
159         }
160         else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
161             const NS_ERROR_UNKNOWN_HOST = 2152398878;
162             if (--this._request_count > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
163                 // to prevent bug 235825: wait for the request handled
164                 // by the automatic keyword resolver
165                 return;
166             }
167             // since we (try to) only handle STATE_STOP of the last request,
168             // the count of open requests should now be 0
169             this._request_count = 0;
170         }
172         if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
173             aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
174             // It's okay to clear what the user typed when we start
175             // loading a document. If the user types, this counter gets
176             // set to zero, if the document load ends without an
177             // onLocationChange, this counter gets decremented
178             // (so we keep it while switching tabs after failed loads)
179             //dumpln("*** started loading");
180             this.loading = true;
181             content_buffer_started_loading_hook.run(this);
182         }
183         else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
184                  aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
185             if (this.loading == true)  {
186                 //dumpln("*** finished loading");
187                 content_buffer_finished_loading_hook.run(this);
188                 this.loading = false;
189             }
190         }
192         if (aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP |
193                            Ci.nsIWebProgressListener.STATE_START)) {
194             if (!this.loading)
195                 this.set_default_message("Done");
196         }
197     },
199     /* This method is called to indicate progress changes for the currently
200        loading page. */
201     onProgressChange: function(webProgress, request, curSelf, maxSelf,
202                                curTotal, maxTotal) {
203         content_buffer_progress_change_hook.run(this, request, curSelf, maxSelf, curTotal, maxTotal);
204     },
206     /* This method is called to indicate a change to the current location.
207        The url can be gotten as location.spec. */
208     onLocationChange : function(webProgress, request, location) {
209         /* Attempt to ignore onLocationChange calls due to the initial
210          * loading of about:blank by all xul:browser elements. */
211         if (location.spec == "about:blank" && this.ignore_initial_blank)
212             return;
214         this.ignore_initial_blank = false;
216         //dumpln("spec: " + location.spec  +" ;;; " + this.display_URI_string);
217         /* Use the real location URI now */
218         this._display_URI = null;
219         content_buffer_location_change_hook.run(this, request, location);
220         buffer_description_change_hook.run(this);
221     },
223     // This method is called to indicate a status changes for the currently
224     // loading page.  The message is already formatted for display.
225     // Status messages could be displayed in the minibuffer output area.
226     onStatusChange: function(webProgress, request, status, msg) {
227         this.set_default_message(msg);
228         content_buffer_status_change_hook.run(this, request, status, msg);
229     },
231     // This method is called when the security state of the browser changes.
232     onSecurityChange: function(webProgress, request, state) {
233         /* FIXME: currently this isn't used */
235         /*
236         const WPL = Components.interfaces.nsIWebProgressListener;
238         if (state & WPL.STATE_IS_INSECURE) {
239             // update visual indicator
240         } else {
241             var level = "unknown";
242             if (state & WPL.STATE_IS_SECURE) {
243                 if (state & WPL.STATE_SECURE_HIGH)
244                     level = "high";
245                 else if (state & WPL.STATE_SECURE_MED)
246                     level = "medium";
247                 else if (state & WPL.STATE_SECURE_LOW)
248                     level = "low";
249             } else if (state & WPL_STATE_IS_BROKEN) {
250                 level = "mixed";
251             }
252             // provide a visual indicator of the security state here.
253         }
254         */
255     },
257     /* Inherit from buffer */
259     __proto__ : buffer.prototype
263 add_hook("current_content_buffer_finished_loading_hook",
264          function (buffer) {
265                  buffer.window.minibuffer.show("Done");
266          });
268 add_hook("current_content_buffer_status_change_hook",
269          function (buffer, request, status, msg) {
270              buffer.set_default_message(msg);
271          });
274 define_variable("url_completion_use_webjumps", true, "Specifies whether URL completion should complete webjumps.");
275 define_variable("url_completion_use_bookmarks", true, "Specifies whether URL completion should complete bookmarks.");
276 define_variable("url_completion_use_history", false,
277                      "Specifies whether URL completion should complete using browser history.");
279 minibuffer_auto_complete_preferences["url"] = true;
280 minibuffer.prototype.read_url = function () {
281     keywords(arguments, $prompt = "URL:", $history = "url", $initial_value = "",
282              $use_webjumps = url_completion_use_webjumps,
283              $use_history = url_completion_use_history,
284              $use_bookmarks = url_completion_use_bookmarks);
285     var completer = url_completer ($use_webjumps = arguments.$use_webjumps,
286         $use_bookmarks = arguments.$use_bookmarks,
287         $use_history = arguments.$use_history);
288     var result = yield this.read(
289         $prompt = arguments.$prompt,
290         $history = arguments.$history,
291         $completer = completer,
292         $initial_value = arguments.$initial_value,
293         $auto_complete = "url",
294         $select,
295         $match_required = false);
296     if (result == "") // well-formedness check. (could be better!)
297         throw ("invalid url or webjump (\""+ result +"\")");
298     yield co_return(get_url_or_webjump(result));
301 I.content_charset = interactive_method(
302     $sync = function (ctx) {
303         var buffer = ctx.buffer;
304         if (!(buffer instanceof content_buffer))
305             throw new Error("Current buffer is of invalid type");
306         // -- Charset of content area of focusedWindow
307         var focusedWindow = buffer.focused_frame;
308         if (focusedWindow)
309             return focusedWindow.document.characterSet;
310         else
311             return null;
312     });
315 I.content_selection = interactive_method(
316     $sync = function (ctx) {
317         // -- Selection of content area of focusedWindow
318         var focusedWindow = this.buffers.current.focused_frame;
319         return focusedWindow.getSelection ();
320     });
322 function overlink_update_status(buffer, text) {
323     if (text.length > 0)
324         buffer.window.minibuffer.show("Link: " + text);
325     else
326         buffer.window.minibuffer.show("");
329 define_global_mode("overlink_mode",
330                    function () {
331                        add_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
332                    },
333                    function () {
334                        remove_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
335                    });
337 overlink_mode(true);
339 function open_in_browser(buffer, target, lspec)
341     switch (target) {
342     case OPEN_CURRENT_BUFFER:
343     case FOLLOW_DEFAULT:
344     case FOLLOW_CURRENT_FRAME:
345     case FOLLOW_TOP_FRAME:
346         if (buffer instanceof content_buffer)  {
347             apply_load_spec(buffer, lspec);
348             break;
349         }
350         target = OPEN_NEW_BUFFER;
351         // If the current buffer is not a content_buffer, use a new buffer.
352     default:
353         create_buffer(buffer.window,
354                       buffer_creator(content_buffer,
355                                      $load = lspec,
356                                      $configuration = buffer.configuration),
357                       target);
358         break;
359     }
362 interactive("open-url",
363             "Open a URL, reusing the current buffer by default",
364             function (I) {
365                 var target = I.browse_target("open");
366                 open_in_browser(I.buffer, target,
367                                 (yield I.minibuffer.read_url($prompt = browse_target_prompt(target))));
368             });
370 interactive("find-alternate-url",
371             "Edit the current URL in the minibuffer",
372             function (I) {
373                 var target = I.browse_target("open");
374                 check_buffer(I.buffer, content_buffer);
375                 open_in_browser(I.buffer, target,
376                                 (yield I.minibuffer.read_url($prompt = browse_target_prompt(target),
377                                                              $initial_value = I.buffer.display_URI_string)));
378             });
380 interactive("find-url",
381             "Open a URL in a new buffer",
382             function (I) {
383                 var target = I.browse_target("find-url");
384                 open_in_browser(I.buffer, target,
385                                 (yield I.minibuffer.read_url($prompt = browse_target_prompt(target))));
386             });
387 default_browse_targets["find-url"] = [OPEN_NEW_BUFFER, OPEN_NEW_WINDOW];
389 function go_up (b, target)
391     var url = Cc["@mozilla.org/network/standard-url;1"]
392         .createInstance (Ci.nsIURL);
393     url.spec = b.current_URI.spec;
394     var up;
395     if (url.param != "" || url.query != "")
396         up = url.filePath;
397     else if (url.fileName != "")
398         up = ".";
399     else
400         up = "..";
401     open_in_browser(b, target, b.current_URI.resolve (up));
403 interactive("go-up",
404             "Go to the parent directory of the current URL",
405             function (I) { go_up(check_buffer(I.buffer, content_buffer),  I.browse_target("go-up")); });
406 default_browse_targets["go-up"] = "open";
409 function go_back (b, prefix)
411     if (prefix < 0)
412         go_forward(b, -prefix);
414     check_buffer(b, content_buffer);
416     if (b.web_navigation.canGoBack)
417     {
418         var hist = b.web_navigation.sessionHistory;
419         var idx = hist.index - prefix;
420         if (idx < 0)
421             idx = 0;
422         b.web_navigation.gotoIndex(idx);
423     } else
424         throw interactive_error("Can't go back");
426 interactive(
427     "go-back",
428     "Go back in the session hisory for the current buffer.",
429     function (I) {go_back(I.buffer, I.p);});
431 function go_forward (b, prefix)
433     if (prefix < 0)
434         go_back(b, -prefix);
436     check_buffer(b, content_buffer);
438     if (b.web_navigation.canGoForward)
439     {
440         var hist = b.web_navigation.sessionHistory;
441         var idx = hist.index + prefix;
442         if (idx >= hist.count) idx = hist.count-1;
443         b.web_navigation.gotoIndex(idx);
444     } else
445         throw interactive_error("Can't go forward");
447 interactive("go-forward",
448             "Go back in the session hisory for the current buffer.",
449             function (I) {go_forward(I.buffer, I.p);});
451 function stop_loading (b)
453     check_buffer(b, content_buffer);
454     b.web_navigation.stop(Ci.nsIWebNavigation.STOP_NETWORK);
456 interactive("stop-loading",
457             "Stop loading the current document.",
458             function (I) {stop_loading(I.buffer);});
460 function reload (b, bypass_cache)
462     check_buffer(b, content_buffer);
463     var flags = bypass_cache != null ?
464         Ci.nsIWebNavigation.LOAD_FLAGS_NONE : Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
465     b.web_navigation.reload(flags);
467 interactive("reload",
468             "Reload the current document.\n" +
469             "If a prefix argument is specified, the cache is bypassed.",
470             function (I) {reload(I.buffer, I.P);});
473  * browserDOMWindow: intercept window opening
474  */
475 function initialize_browser_dom_window(window) {
476     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
477         new browser_dom_window(window);
480 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'.");
482 function browser_dom_window(window) {
483     this.window = window;
484     this.next_target = null;
486 browser_dom_window.prototype = {
487     QueryInterface: generate_QI(Ci.nsIBrowserDOMWindow),
489     openURI : function(aURI, aOpener, aWhere, aContext) {
491         // Reference: http://www.xulplanet.com/references/xpcomref/ifaces/nsIBrowserDOMWindow.html
492         var target = this.next_target;
493         if (target == null || target == FOLLOW_DEFAULT)
494             target = browser_default_open_target;
495         this.next_target = null;
497         /* Determine the opener buffer */
498         var opener_buffer = get_buffer_from_frame(this.window, aOpener.top);
499         var config = opener_buffer ? opener_buffer.configuration : null;
501         switch (browser_default_open_target) {
502         case OPEN_CURRENT_BUFFER:
503         case FOLLOW_TOP_FRAME:
504             return aOpener.top;
505         case FOLLOW_CURRENT_FRAME:
506             return aOpener;
507         case OPEN_NEW_BUFFER:
508             var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
509             this.window.buffers.current = buffer;
510             return buffer.top_frame;
511         case OPEN_NEW_BUFFER_BACKGROUND:
512             var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
513             return buffer.top_frame;
514         case OPEN_NEW_WINDOW:
515         default: /* shouldn't be needed */
517             /* We don't call make_window here, because that will result
518              * in the URL being loaded as the top-level document,
519              * instead of within a browser buffer.  Instead, we can
520              * rely on Mozilla using browser.chromeURL. */
521             window_set_extra_arguments({initial_buffer_configuration: config});
522             return null;
523         }
524     }
527 add_hook("window_initialize_early_hook", initialize_browser_dom_window);
529 define_keywords("$enable", "$disable", "$doc");
530 function define_page_mode(name, display_name) {
531     keywords(arguments);
532     var enable = arguments.$enable;
533     var disable = arguments.$disable;
534     var doc = arguments.$doc;
535     define_buffer_mode(name, display_name,
536                        $class = "page_mode",
537                        $enable = enable,
538                        $disable = function (buffer) {
539                            if (disable)
540                                disable(buffer);
541                            buffer.local_variables = {};
542                        },
543                        $doc = doc);
545 ignore_function_for_get_caller_source_code_reference("define_page_mode");
547 define_variable("auto_mode_list", [], "A list of mappings from URI regular expressions to page modes.");
548 function page_mode_auto_update(buffer) {
549     var uri = buffer.current_URI.spec;
550     var mode = predicate_alist_match(auto_mode_list, uri);
551     if (mode)
552         mode(buffer, true);
553     else if (buffer.page_mode)
554         conkeror[buffer.page_mode](buffer, false);
557 add_hook("content_buffer_location_change_hook", page_mode_auto_update);