Show more links on mouse over
[conkeror.git] / modules / content-buffer.js
blob87c99d1bb4dabf2d1025c1624df810e96bffbb6f
1 /**
2  * (C) Copyright 2004-2007 Shawn Betts
3  * (C) Copyright 2007-2008 John J. Foerch
4  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
5  *
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();
36     try {
38         conkeror.buffer.call(this, window, element, forward_keywords(arguments));
40         this.browser.addProgressListener(this);
41         var buffer = this;
42         this.browser.addEventListener("DOMTitleChanged", function (event) {
43                                           buffer_title_change_hook.run(buffer);
44                                       }, true /* capture */, false /* ignore untrusted events */);
46         this.browser.addEventListener("scroll", function (event) {
47                                           buffer_scroll_hook.run(buffer);
48                                       }, true /* capture */, false /* ignore untrusted events */);
50         this.browser.addEventListener("focus", function (event) {
51                                           content_buffer_focus_change_hook.run(buffer, event);
52                                       }, true /* capture */, false /* ignore untrusted events */);
54         this.browser.addEventListener("mouseover", function (event) {
55                                           var node = event.target;
56                                           while (node && !(node instanceof Ci.nsIDOMHTMLAnchorElement))
57                                               node = node.parentNode;
58                                           if (node) {
59                                               content_buffer_overlink_change_hook.run(buffer, node.href);
60                                               buffer.current_overlink = event.target;
61                                           }
62                                       }, true, false);
64         this.browser.addEventListener("mouseout", function (event) {
65                                           if (buffer.current_overlink == event.target) {
66                                               buffer.current_overlink = null;
67                                               content_buffer_overlink_change_hook.run(buffer, "");
68                                           }
69                                       }, true, false);
71         this.browser.addEventListener("mousedown", function (event) {
72                                           buffer.last_user_input_received = Date.now();
73                                       }, true, false);
75         this.browser.addEventListener("keypress", function (event) {
76                                           buffer.last_user_input_received = Date.now();
77                                       }, true, false);
79         this.browser.addEventListener("DOMLinkAdded", function (event) {
80                                           content_buffer_dom_link_added_hook.run(buffer, event);
81                                       }, true, false);
83         buffer.last_user_input_received = null;
85         /* FIXME: Add a handler for blocked popups, and also PopupWindow event */
86         /*
87          this.browser.addEventListener("DOMPopupBlocked", function (event) {
88          dumpln("PopupWindow: " + event);
89          }, true, false);
90          */
92         normal_input_mode(this);
94         this.ignore_initial_blank = true;
96         var lspec = arguments.$load;
97         if (lspec) {
98             if (lspec.url == "about:blank")
99                 this.ignore_initial_blank = false;
100             else {
101                 /* Ensure that an existing load of about:blank is stopped */
102                 this.web_navigation.stop(Ci.nsIWebNavigation.STOP_ALL);
104                 this.load(lspec);
105             }
106         }
107     } finally {
108         this.constructor_end();
109     }
111 content_buffer.prototype = {
112     constructor : content_buffer,
114     get scrollX () { return this.top_frame.scrollX; },
115     get scrollY () { return this.top_frame.scrollY; },
116     get scrollMaxX () { return this.top_frame.scrollMaxX; },
117     get scrollMaxY () { return this.top_frame.scrollMaxY; },
120     /* Used to display the correct URI when the buffer opens initially
121      * even before loading has progressed far enough for currentURI to
122      * contain the correct URI. */
123     _display_URI : null,
125     get display_URI_string () {
126         if (this._display_URI)
127             return this._display_URI;
128         if (this.current_URI)
129             return this.current_URI.spec;
130         return "";
131     },
133     get title() { return this.browser.contentTitle; },
134     get description () { return this.display_URI_string; },
136     load : function (load_spec) {
137         apply_load_spec(this, load_spec);
138     },
140     _request_count: 0,
142     loading : false,
144     /* nsIWebProgressListener */
145     QueryInterface: generate_QI(Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference),
147     // This method is called to indicate state changes.
148     onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
150         const WPL = Components.interfaces.nsIWebProgressListener;
152         var flagstr = "";
153         if (aStateFlags & WPL.STATE_START)
154             flagstr += ",start";
155         if (aStateFlags & WPL.STATE_STOP)
156             flagstr += ",stop";
157         if (aStateFlags & WPL.STATE_IS_REQUEST)
158             flagstr += ",request";
159         if (aStateFlags & WPL.STATE_IS_DOCUMENT)
160             flagstr += ",document";
161         if (aStateFlags & WPL.STATE_IS_NETWORK)
162             flagstr += ",network";
163         if (aStateFlags & WPL.STATE_IS_WINDOW)
164             flagstr += ",window";
165         dumpln("onStateChange: " + flagstr + ", status: " + aStatus);
167         if (!aRequest)
168             return;
171         if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
172             this._request_count++;
173         }
174         else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
175             const NS_ERROR_UNKNOWN_HOST = 2152398878;
176             if (--this._request_count > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
177                 // to prevent bug 235825: wait for the request handled
178                 // by the automatic keyword resolver
179                 return;
180             }
181             // since we (try to) only handle STATE_STOP of the last request,
182             // the count of open requests should now be 0
183             this._request_count = 0;
184         }
186         if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
187             aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
188             // It's okay to clear what the user typed when we start
189             // loading a document. If the user types, this counter gets
190             // set to zero, if the document load ends without an
191             // onLocationChange, this counter gets decremented
192             // (so we keep it while switching tabs after failed loads)
193             //dumpln("*** started loading");
194             this.loading = true;
195             content_buffer_started_loading_hook.run(this);
196             this.last_user_input_received = null;
197         }
198         else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
199                  aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
200             if (this.loading == true)  {
201                 //dumpln("*** finished loading");
202                 content_buffer_finished_loading_hook.run(this);
203                 this.loading = false;
204             }
205         }
207         if (aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP |
208                            Ci.nsIWebProgressListener.STATE_START)) {
209             if (!this.loading)
210                 this.set_default_message("Done");
211         }
212     },
214     /* This method is called to indicate progress changes for the currently
215        loading page. */
216     onProgressChange: function(webProgress, request, curSelf, maxSelf,
217                                curTotal, maxTotal) {
218         content_buffer_progress_change_hook.run(this, request, curSelf, maxSelf, curTotal, maxTotal);
219     },
221     /* This method is called to indicate a change to the current location.
222        The url can be gotten as location.spec. */
223     onLocationChange : function(webProgress, request, location) {
224         /* Attempt to ignore onLocationChange calls due to the initial
225          * loading of about:blank by all xul:browser elements. */
226         if (location.spec == "about:blank" && this.ignore_initial_blank)
227             return;
229         this.ignore_initial_blank = false;
231         //dumpln("spec: " + location.spec  +" ;;; " + this.display_URI_string);
232         /* Use the real location URI now */
233         this._display_URI = null;
234         content_buffer_location_change_hook.run(this, request, location);
235         this.last_user_input_received = null;
236         buffer_description_change_hook.run(this);
237     },
239     // This method is called to indicate a status changes for the currently
240     // loading page.  The message is already formatted for display.
241     // Status messages could be displayed in the minibuffer output area.
242     onStatusChange: function(webProgress, request, status, msg) {
243         this.set_default_message(msg);
244         content_buffer_status_change_hook.run(this, request, status, msg);
245     },
247     // This method is called when the security state of the browser changes.
248     onSecurityChange: function(webProgress, request, state) {
249         /* FIXME: currently this isn't used */
251         /*
252         const WPL = Components.interfaces.nsIWebProgressListener;
254         if (state & WPL.STATE_IS_INSECURE) {
255             // update visual indicator
256         } else {
257             var level = "unknown";
258             if (state & WPL.STATE_IS_SECURE) {
259                 if (state & WPL.STATE_SECURE_HIGH)
260                     level = "high";
261                 else if (state & WPL.STATE_SECURE_MED)
262                     level = "medium";
263                 else if (state & WPL.STATE_SECURE_LOW)
264                     level = "low";
265             } else if (state & WPL_STATE_IS_BROKEN) {
266                 level = "mixed";
267             }
268             // provide a visual indicator of the security state here.
269         }
270         */
271     },
273     /* Inherit from buffer */
275     __proto__ : buffer.prototype
279 add_hook("current_content_buffer_finished_loading_hook",
280          function (buffer) {
281                  buffer.window.minibuffer.show("Done");
282          });
284 add_hook("current_content_buffer_status_change_hook",
285          function (buffer, request, status, msg) {
286              buffer.set_default_message(msg);
287          });
290 define_variable("url_completion_use_webjumps", true, "Specifies whether URL completion should complete webjumps.");
291 define_variable("url_completion_use_bookmarks", true, "Specifies whether URL completion should complete bookmarks.");
292 define_variable("url_completion_use_history", false,
293                      "Specifies whether URL completion should complete using browser history.");
295 minibuffer_auto_complete_preferences["url"] = true;
296 minibuffer.prototype.read_url = function () {
297     keywords(arguments, $prompt = "URL:", $history = "url", $initial_value = "",
298              $use_webjumps = url_completion_use_webjumps,
299              $use_history = url_completion_use_history,
300              $use_bookmarks = url_completion_use_bookmarks);
301     var completer = url_completer ($use_webjumps = arguments.$use_webjumps,
302         $use_bookmarks = arguments.$use_bookmarks,
303         $use_history = arguments.$use_history);
304     var result = yield this.read(
305         $prompt = arguments.$prompt,
306         $history = arguments.$history,
307         $completer = completer,
308         $initial_value = arguments.$initial_value,
309         $auto_complete = "url",
310         $select,
311         $match_required = false);
312     if (result == "") // well-formedness check. (could be better!)
313         throw ("invalid url or webjump (\""+ result +"\")");
314     yield co_return(result);
317 I.content_charset = interactive_method(
318     $sync = function (ctx) {
319         var buffer = ctx.buffer;
320         if (!(buffer instanceof content_buffer))
321             throw new Error("Current buffer is of invalid type");
322         // -- Charset of content area of focusedWindow
323         var focusedWindow = buffer.focused_frame;
324         if (focusedWindow)
325             return focusedWindow.document.characterSet;
326         else
327             return null;
328     });
331 I.content_selection = interactive_method(
332     $sync = function (ctx) {
333         // -- Selection of content area of focusedWindow
334         var focusedWindow = this.buffers.current.focused_frame;
335         return focusedWindow.getSelection ();
336     });
338 function overlink_update_status(buffer, text) {
339     if (text.length > 0)
340         buffer.window.minibuffer.show("Link: " + text);
341     else
342         buffer.window.minibuffer.show("");
345 define_global_mode("overlink_mode",
346                    function () {
347                        add_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
348                    },
349                    function () {
350                        remove_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
351                    });
353 overlink_mode(true);
356 function go_back (b, prefix)
358     if (prefix < 0)
359         go_forward(b, -prefix);
361     check_buffer(b, content_buffer);
363     if (b.web_navigation.canGoBack)
364     {
365         var hist = b.web_navigation.sessionHistory;
366         var idx = hist.index - prefix;
367         if (idx < 0)
368             idx = 0;
369         b.web_navigation.gotoIndex(idx);
370     } else
371         throw interactive_error("Can't go back");
373 interactive(
374     "go-back",
375     "Go back in the session history for the current buffer.",
376     function (I) {go_back(I.buffer, I.p);});
378 function go_forward (b, prefix)
380     if (prefix < 0)
381         go_back(b, -prefix);
383     check_buffer(b, content_buffer);
385     if (b.web_navigation.canGoForward)
386     {
387         var hist = b.web_navigation.sessionHistory;
388         var idx = hist.index + prefix;
389         if (idx >= hist.count) idx = hist.count-1;
390         b.web_navigation.gotoIndex(idx);
391     } else
392         throw interactive_error("Can't go forward");
394 interactive("go-forward",
395             "Go back in the session hisory for the current buffer.",
396             function (I) {go_forward(I.buffer, I.p);});
398 function stop_loading (b)
400     check_buffer(b, content_buffer);
401     b.web_navigation.stop(Ci.nsIWebNavigation.STOP_NETWORK);
403 interactive("stop-loading",
404             "Stop loading the current document.",
405             function (I) {stop_loading(I.buffer);});
407 function reload (b, bypass_cache)
409     check_buffer(b, content_buffer);
410     var flags = bypass_cache != null ?
411         Ci.nsIWebNavigation.LOAD_FLAGS_NONE : Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
412     b.web_navigation.reload(flags);
414 interactive("reload",
415             "Reload the current document.\n" +
416             "If a prefix argument is specified, the cache is bypassed.",
417             function (I) {reload(I.buffer, I.P);});
420  * browserDOMWindow: intercept window opening
421  */
422 function initialize_browser_dom_window(window) {
423     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
424         new browser_dom_window(window);
427 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'.");
429 function browser_dom_window(window) {
430     this.window = window;
431     this.next_target = null;
433 browser_dom_window.prototype = {
434     QueryInterface: generate_QI(Ci.nsIBrowserDOMWindow),
436     openURI : function(aURI, aOpener, aWhere, aContext) {
438         // Reference: http://www.xulplanet.com/references/xpcomref/ifaces/nsIBrowserDOMWindow.html
439         var target = this.next_target;
440         if (target == null || target == FOLLOW_DEFAULT)
441             target = browser_default_open_target;
442         this.next_target = null;
444         /* Determine the opener buffer */
445         var opener_buffer = get_buffer_from_frame(this.window, aOpener.top);
446         var config = opener_buffer ? opener_buffer.configuration : null;
448         switch (browser_default_open_target) {
449         case OPEN_CURRENT_BUFFER:
450         case FOLLOW_TOP_FRAME:
451             return aOpener.top;
452         case FOLLOW_CURRENT_FRAME:
453             return aOpener;
454         case OPEN_NEW_BUFFER:
455             var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
456             this.window.buffers.current = buffer;
457             return buffer.top_frame;
458         case OPEN_NEW_BUFFER_BACKGROUND:
459             var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
460             return buffer.top_frame;
461         case OPEN_NEW_WINDOW:
462         default: /* shouldn't be needed */
464             /* We don't call make_window here, because that will result
465              * in the URL being loaded as the top-level document,
466              * instead of within a browser buffer.  Instead, we can
467              * rely on Mozilla using browser.chromeURL. */
468             window_set_extra_arguments(
469                 {initial_buffer_creator: buffer_creator(content_buffer, $configuration = config)}
470             );
471             return null;
472         }
473     }
476 add_hook("window_initialize_early_hook", initialize_browser_dom_window);
478 define_keywords("$enable", "$disable", "$doc");
479 function define_page_mode(name, display_name) {
480     keywords(arguments);
481     var enable = arguments.$enable;
482     var disable = arguments.$disable;
483     var doc = arguments.$doc;
484     define_buffer_mode(name, display_name,
485                        $class = "page_mode",
486                        $enable = enable,
487                        $disable = function (buffer) {
488                            if (disable)
489                                disable(buffer);
490                            buffer.local_variables = {};
491                            buffer.default_browser_object_classes = {};
492                        },
493                        $doc = doc);
495 ignore_function_for_get_caller_source_code_reference("define_page_mode");
497 define_variable("auto_mode_list", [], "A list of mappings from URI regular expressions to page modes.");
498 function page_mode_auto_update(buffer) {
499     var uri = buffer.current_URI.spec;
500     var mode = predicate_alist_match(auto_mode_list, uri);
501     if (mode)
502         mode(buffer, true);
503     else if (buffer.page_mode)
504         conkeror[buffer.page_mode](buffer, false);
507 add_hook("content_buffer_location_change_hook", page_mode_auto_update);