Code cleanup; use XPCOMUtils for some purposes
[conkeror.git] / modules / content-buffer.js
blob434e215935d93aec3aa6e3fea3e85df3176ad2c0
1 require_later("content-buffer-input.js");
3 define_buffer_local_hook("content_buffer_finished_loading_hook");
4 define_buffer_local_hook("content_buffer_progress_change_hook");
5 define_buffer_local_hook("content_buffer_location_change_hook");
6 define_buffer_local_hook("content_buffer_status_change_hook");
7 define_buffer_local_hook("content_buffer_focus_change_hook");
8 define_buffer_local_hook("content_buffer_overlink_change_hook");
10 define_current_buffer_hook("current_content_buffer_finished_loading_hook", "content_buffer_finished_loading_hook");
11 define_current_buffer_hook("current_content_buffer_progress_change_hook", "content_buffer_progress_change_hook");
12 define_current_buffer_hook("current_content_buffer_location_change_hook", "content_buffer_location_change_hook");
13 define_current_buffer_hook("current_content_buffer_status_change_hook", "content_buffer_status_change_hook");
14 define_current_buffer_hook("current_content_buffer_focus_change_hook", "content_buffer_focus_change_hook");
15 define_current_buffer_hook("current_content_buffer_overlink_change_hook", "content_buffer_overlink_change_hook");
17 /* If browser is null, create a new browser */
18 define_keywords("$load");
19 function content_buffer(window, element)
21     keywords(arguments);
22     conkeror.buffer.call(this, window, element, forward_keywords(arguments));
24     this.browser.addProgressListener(this);
25     var buffer = this;
26     this.browser.addEventListener("DOMTitleChanged", function (event) {
27             buffer_title_change_hook.run(buffer);
28         }, true /* capture */, false /* ignore untrusted events */);
30     this.browser.addEventListener("scroll", function (event) {
31             buffer_scroll_hook.run(buffer);
32         }, true /* capture */, false /* ignore untrusted events */);
34     this.browser.addEventListener("focus", function (event) {
35             content_buffer_focus_change_hook.run(buffer, event);
36         }, true /* capture */, false /* ignore untrusted events */);
38     this.browser.addEventListener("mouseover", function (event) {
39             if (event.target instanceof Ci.nsIDOMHTMLAnchorElement) {
40                 content_buffer_overlink_change_hook.run(buffer, event.target.href);
41                 buffer.current_overlink = event.target;
42             }
43         }, true, false);
45     this.browser.addEventListener("mouseout", function (event) {
46             if (buffer.current_overlink == event.target) {
47                 buffer.current_overlink = null;
48                 content_buffer_overlink_change_hook.run(buffer, "");
49             }
50         }, true, false);
52     this.browser.addEventListener("mousedown", function (event) {
53             buffer.last_user_input_received = Date.now();
54         }, true, false);
56     this.browser.addEventListener("keypress", function (event) {
57             buffer.last_user_input_received = Date.now();
58         }, true, false);
60     buffer.last_user_input_received = null;
62     /* FIXME: Add a handler for blocked popups, and also PopupWindow event */
63     /*
64     this.browser.addEventListener("DOMPopupBlocked", function (event) {
65             dumpln("PopupWindow: " + event);
66         }, true, false);
67     */
69     content_buffer_normal_input_mode(this);
71     var load_spec = arguments.$load;
72     if (load_spec)
73         this.load(load_spec);
75 content_buffer.prototype = {
76     constructor : content_buffer,
78     get scrollX () { return this.top_frame.scrollX; },
79     get scrollY () { return this.top_frame.scrollY; },
80     get scrollMaxX () { return this.top_frame.scrollMaxX; },
81     get scrollMaxY () { return this.top_frame.scrollMaxY; },
84     /* Used to display the correct URI when the buffer opens initially
85      * even before loading has progressed far enough for currentURI to
86      * contain the correct URI. */
87     _display_URI : null,
89     get display_URI_string () {
90         if (this._display_URI)
91             return this._display_URI;
92         return this.current_URI.spec;
93     },
95     get title() { return this.browser.contentTitle; },
96     get description () { return this.display_URI_string; },
98     load : function (load_spec) {
99         apply_load_spec(this, load_spec);
100     },
102     _requests_started: 0,
103     _requests_finished: 0,
105     /* nsIWebProgressListener */
106     QueryInterface: generate_QI(Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference),
108     // This method is called to indicate state changes.
109     onStateChange: function(webProgress, request, stateFlags, status) {
110         const WPL = Components.interfaces.nsIWebProgressListener;
111         /*
112         var flagstr = "";
113         if (stateFlags & WPL.STATE_START)
114             flagstr += ",start";
115         if (stateFlags & WPL.STATE_STOP)
116             flagstr += ",stop";
117         if (stateFlags & WPL.STATE_IS_REQUEST)
118             flagstr += ",request";
119         if (stateFlags & WPL.STATE_IS_DOCUMENT)
120             flagstr += ",document";
121         if (stateFlags & WPL.STATE_IS_NETWORK)
122             flagstr += ",network";
123         if (stateFlags & WPL.STATE_IS_WINDOW)
124             flagstr += ",window";
125         dumpln("onStateChange: " + flagstr + ", status: " + status);
126         */
127         if (stateFlags & WPL.STATE_IS_REQUEST) {
128             if (stateFlags & WPL.STATE_START) {
129                 if (this._requests_started == 0)  {
130                     // Reset the time at which the last user input was received for the page
131                     this.last_user_input_received = 0;
132                 }
133                 this._requests_started++;
134             } else if (stateFlags & WPL.STATE_STOP) {
135                 this._requests_finished++;
136             }
137         }
138         if ((stateFlags & WPL.STATE_STOP) && (this._requests_finished == this._requests_started)) {
139             this._requests_finished = this._requests_started = 0;
140             content_buffer_finished_loading_hook.run(this);
141         }
142     },
144     /* This method is called to indicate progress changes for the currently
145        loading page. */
146     onProgressChange: function(webProgress, request, curSelf, maxSelf,
147                                curTotal, maxTotal) {
148         content_buffer_progress_change_hook.run(this, request, curSelf, maxSelf, curTotal, maxTotal);
149     },
151     /* This method is called to indicate a change to the current location.
152        The url can be gotten as location.spec. */
153     onLocationChange : function(webProgress, request, location) {
154         /* Use the real location URI now */
155         this._display_URI = null;
156         content_buffer_location_change_hook.run(this, request, location);
157     },
159     // This method is called to indicate a status changes for the currently
160     // loading page.  The message is already formatted for display.
161     // Status messages could be displayed in the minibuffer output area.
162     onStatusChange: function(webProgress, request, status, msg) {
163         content_buffer_status_change_hook.run(this, request, status, msg);
164     },
166     // This method is called when the security state of the browser changes.
167     onSecurityChange: function(webProgress, request, state) {
168         const WPL = Components.interfaces.nsIWebProgressListener;
170         if (state & WPL.STATE_IS_INSECURE) {
171             // update visual indicator
172         } else {
173             var level = "unknown";
174             if (state & WPL.STATE_IS_SECURE) {
175                 if (state & WPL.STATE_SECURE_HIGH)
176                     level = "high";
177                 else if (state & WPL.STATE_SECURE_MED)
178                     level = "medium";
179                 else if (state & WPL.STATE_SECURE_LOW)
180                     level = "low";
181             } else if (state & WPL_STATE_IS_BROKEN) {
182                 level = "mixed";
183             }
184             // provide a visual indicator of the security state here.
185         }
186     },
188     /* Inherit from buffer */
190     __proto__ : buffer.prototype
194 add_hook("current_content_buffer_finished_loading_hook",
195          function (buffer) {
196                  buffer.window.minibuffer.show("Done");
197          });
199 add_hook("current_content_buffer_status_change_hook",
200          function (buffer, request, status, msg) {
201              buffer.window.minibuffer.show(msg);
202          });
206 //RETROJ: this may be improperly named.  it can read either an url or a
207 //        webjump from the minibuffer, but it will always return an url.
208 I.url_or_webjump = interactive_method(
209     $async = function (ctx, cont) {
210         keywords(arguments, $prompt = "URL:", $history = "url", $initial_value = "");
211         var completions = arguments.$completions;
212         if (completions === undefined)
213         {
214             completions = [];
215             for (var x in gWebJumpLocations)
216                 completions.push([x,x]);
217         }
218         ctx.window.minibuffer.read_with_completion(
219             $prompt = arguments.$prompt,
220             $history = arguments.$history,
221             $completions = completions,
222             $initial_value = arguments.$initial_value,
223             $select,
224             $allow_non_matches,
225             $callback = function (match,s) {
226                 if (s == "") // well-formedness check. (could be better!)
227                     throw ("invalid url or webjump (\""+s+"\")");
228                 cont(get_url_or_webjump (s));
229             });
230     });
232 I.current_frame_url = interactive_method(
233     $sync = function (ctx) {
234         var buffer = ctx.buffer;
235         if (!(buffer instanceof content_buffer))
236             throw new Error("Current buffer is of invalid type");
237         return buffer.focused_frame.location.href;
238     });
240 // This name should probably change
241 I.current_url = interactive_method(
242     $sync = function (ctx) {
243         var buffer = ctx.buffer;
244         if (!(buffer instanceof content_buffer))
245             throw new Error("Current buffer is of invalid type");
246         return buffer.current_URI.spec;
247     });
249 I.focused_link_url = interactive_method(
250     $sync = function (ctx) {
251         var buffer = ctx.buffer;
252         if (!(buffer instanceof content_buffer))
253             throw new Error("Current buffer is of invalid type");
254         // -- Focused link element
255         ///JJF: check for errors or wrong element type.
256         return get_link_location (buffer.focused_element);
257     });
259 I.content_charset = interactive_method(
260     $sync = function (ctx) {
261         var buffer = ctx.buffer;
262         if (!(buffer instanceof content_buffer))
263             throw new Error("Current buffer is of invalid type");
264         // -- Charset of content area of focusedWindow
265         var focusedWindow = buffer.focused_frame;
266         if (focusedWindow)
267             return focusedWindow.document.characterSet;
268         else
269             return null;
270     });
273 I.content_selection = interactive_method(
274     $sync = function (ctx) {
275         // -- Selection of content area of focusedWindow
276         var focusedWindow = this.buffers.current.focused_frame;
277         return focusedWindow.getSelection ();
278     });
280 function overlink_update_status(buffer, text) {
281     if (text.length > 0)
282         buffer.window.minibuffer.show("Link: " + text);
283     else
284         buffer.window.minibuffer.show("");
287 define_global_mode("overlink_mode",
288                    function () {
289                        add_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
290                    },
291                    function () {
292                        remove_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
293                    });
295 overlink_mode(true);
298 function document_load_spec(doc) {
299     var sh_entry = get_SHEntry_for_document(doc);
300     var result = {url: doc.location.href};
301     result.document = doc;
302     if (sh_entry != null) {
303         result.cache_key = sh_entry;
304         result.referrer = sh_entry.referrerURI;
305         result.post_data = sh_entry.postData;
306     }
307     return result;
310 /* Target can be either a content_buffer or an nsIWebNavigation */
311 function apply_load_spec(target, load_spec) {
312     var url, flags, referrer, post_data;
313     if (typeof(load_spec) == "string") {
314         url = load_spec;
315         flags = null;
316         referrer = null;
317         post_data = null;
318     } else {
319         url = load_spec.url;
320         flags = load_spec.flags;
321         referrer = load_spec.referrer;
322         post_data = load_spec.post_data;
323     }
324     if (flags == null)
325         flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
326     if (target instanceof content_buffer) {
327         target._display_URI = url;
328         target = target.web_navigation;
329     }
330     target.loadURI(url, flags, referrer, post_data, null /* headers */);
333 function open_in_browser(buffer, target, load_spec)
335     switch (target) {
336     case OPEN_CURRENT_BUFFER:
337     case FOLLOW_DEFAULT:
338     case FOLLOW_CURRENT_FRAME:
339     case FOLLOW_TOP_FRAME:
340         if (buffer instanceof content_buffer)  {
341             apply_load_spec(buffer, load_spec);
342             break;
343         }
344         target = OPEN_NEW_BUFFER;
345         // If the current buffer is not a content_buffer, use a new buffer.
346     default:
347         create_buffer(buffer.window,
348                       buffer_creator(content_buffer,
349                                      $load = load_spec,
350                                      $configuration = buffer.configuration),
351                       target);
352         break;
353     }
356 interactive("open-url",
357             "Open a URL, reusing the current buffer by default",
358             open_in_browser,
359             I.current_buffer, $$ = I.browse_target("open"),
360             I.url_or_webjump($prompt = I.bind(browse_target_prompt, $$)));
362 interactive("find-alternate-url",
363             "Edit the current URL in the minibuffer",
364             open_in_browser,
365             I.current_buffer, $$ = I.browse_target("open"),
366             I.url_or_webjump($prompt = I.bind(browse_target_prompt, $$),
367                              $initial_value = I.current_url));
369 interactive("find-url",
370             "Open a URL in a new buffer",
371             open_in_browser,
372             I.current_buffer, $$ = I.browse_target("find-url"),
373             I.url_or_webjump($prompt = I.bind(browse_target_prompt, $$)));
374 default_browse_targets["find-url"] = [OPEN_NEW_BUFFER, OPEN_NEW_WINDOW];
377 function go_up (b, target)
379     open_in_browser(b, target, b.current_URI.resolve (".."));
381 interactive("go-up",
382             "Go to the parent directory of the current URL",
383             go_up,
384             I.current_buffer(content_buffer),
385             I.browse_target("go-up"));
386 default_browse_targets["go-up"] = "open";
389 function go_back (b, prefix)
391     if (prefix < 0)
392         go_forward(b, -prefix);
394     if (b.web_navigation.canGoBack)
395     {
396         var hist = b.web_navigation.sessionHistory;
397         var idx = hist.index - prefix;
398         if (idx < 0)
399             idx = 0;
400         b.web_navigation.gotoIndex(idx);
401     } else
402         throw interactive_error("Can't go back");
404 interactive("go-back",
405             "Go back in the session hisory for the current buffer.",
406             go_back, I.current_buffer(content_buffer), I.p);
409 function go_forward (b, prefix)
411     if (prefix < 0)
412         go_back(b, -prefix);
414     if (b.web_navigation.canGoForward)
415     {
416         var hist = b.web_navigation.sessionHistory;
417         var idx = hist.index + prefix;
418         if (idx >= hist.count) idx = hist.count-1;
419         b.web_navigation.gotoIndex(idx);
420     } else
421         throw interactive_error("Can't go forward");
423 interactive("go-forward",
424             "Go back in the session hisory for the current buffer.",
425             go_forward, I.current_buffer(content_buffer), I.p);
427 function stop_loading (b)
429     b.web_navigation.stop(Ci.nsIWebNavigation.STOP_NETWORK);
431 interactive("stop-loading",
432             "Stop loading the current document.",
433             stop_loading, I.current_buffer(content_buffer));
435 function reload (b, bypass_cache)
437     var flags = bypass_cache != null ?
438         Ci.nsIWebNavigation.LOAD_FLAGS_NONE : Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
439     b.web_navigation.reload(flags);
441 interactive("reload",
442             "Reload the current document.\n" +
443             "If a prefix argument is specified, the cache is bypassed.",
444             reload, I.current_buffer(content_buffer), I.P);
447  * browserDOMWindow: intercept window opening
448  */
449 function initialize_browser_dom_window(window) {
450     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
451         new browser_dom_window(window);
454 /* USER PREFERENCE */
455 /* This will generally be OPEN_NEW_BUFFER, OPEN_NEW_BUFFER_BACKGROUND, or OPEN_NEW_WINDOW */
456 var browser_default_open_target = OPEN_NEW_BUFFER;
458 function browser_dom_window(window) {
459     this.window = window;
460     this.next_target = null;
462 browser_dom_window.prototype = {
463     QueryInterface: generate_QI(Ci.nsIBrowserDOMWindow),
465     openURI : function(aURI, aOpener, aWhere, aContext) {
467         // Reference: http://www.xulplanet.com/references/xpcomref/ifaces/nsIBrowserDOMWindow.html
468         var target = this.next_target;
469         if (target == null || target == FOLLOW_DEFAULT)
470             target = browser_default_open_target;
471         this.next_target = null;
473         /* Determine the opener buffer */
474         var opener_buffer = get_buffer_from_frame(this.window, aOpener.top);
475         var config = opener_buffer ? opener_buffer.configuration : null;
477         switch (browser_default_open_target) {
478         case OPEN_CURRENT_BUFFER:
479         case FOLLOW_TOP_FRAME:
480             return aOpener.top;
481         case FOLLOW_CURRENT_FRAME:
482             return aOpener;
483         case OPEN_NEW_BUFFER:
484             var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
485             this.window.buffers.current = buffer;
486             return buffer.top_frame;
487         case OPEN_NEW_BUFFER_BACKGROUND:
488             var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
489             return buffer.top_frame;
490         case OPEN_NEW_WINDOW:
491         default: /* shouldn't be needed */
493             /* We don't call make_window here, because that will result
494              * in the URL being loaded as the top-level document,
495              * instead of within a browser buffer.  Instead, we can
496              * rely on Mozilla using browser.chromeURL. */
497             window_set_extra_arguments({initial_buffer_configuration: config});
498             return null;
499         }
500     }
503 add_hook("window_initialize_early_hook", initialize_browser_dom_window);