Add initial OpenSearch search engine support
[conkeror.git] / modules / content-buffer.js
blob2775369d5cb520484f5801a184b7eb39316dba3e
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             this.last_user_input_received = null;
183         }
184         else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
185                  aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
186             if (this.loading == true)  {
187                 //dumpln("*** finished loading");
188                 content_buffer_finished_loading_hook.run(this);
189                 this.loading = false;
190             }
191         }
193         if (aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP |
194                            Ci.nsIWebProgressListener.STATE_START)) {
195             if (!this.loading)
196                 this.set_default_message("Done");
197         }
198     },
200     /* This method is called to indicate progress changes for the currently
201        loading page. */
202     onProgressChange: function(webProgress, request, curSelf, maxSelf,
203                                curTotal, maxTotal) {
204         content_buffer_progress_change_hook.run(this, request, curSelf, maxSelf, curTotal, maxTotal);
205     },
207     /* This method is called to indicate a change to the current location.
208        The url can be gotten as location.spec. */
209     onLocationChange : function(webProgress, request, location) {
210         /* Attempt to ignore onLocationChange calls due to the initial
211          * loading of about:blank by all xul:browser elements. */
212         if (location.spec == "about:blank" && this.ignore_initial_blank)
213             return;
215         this.ignore_initial_blank = false;
217         //dumpln("spec: " + location.spec  +" ;;; " + this.display_URI_string);
218         /* Use the real location URI now */
219         this._display_URI = null;
220         content_buffer_location_change_hook.run(this, request, location);
221         this.last_user_input_received = null;
222         buffer_description_change_hook.run(this);
223     },
225     // This method is called to indicate a status changes for the currently
226     // loading page.  The message is already formatted for display.
227     // Status messages could be displayed in the minibuffer output area.
228     onStatusChange: function(webProgress, request, status, msg) {
229         this.set_default_message(msg);
230         content_buffer_status_change_hook.run(this, request, status, msg);
231     },
233     // This method is called when the security state of the browser changes.
234     onSecurityChange: function(webProgress, request, state) {
235         /* FIXME: currently this isn't used */
237         /*
238         const WPL = Components.interfaces.nsIWebProgressListener;
240         if (state & WPL.STATE_IS_INSECURE) {
241             // update visual indicator
242         } else {
243             var level = "unknown";
244             if (state & WPL.STATE_IS_SECURE) {
245                 if (state & WPL.STATE_SECURE_HIGH)
246                     level = "high";
247                 else if (state & WPL.STATE_SECURE_MED)
248                     level = "medium";
249                 else if (state & WPL.STATE_SECURE_LOW)
250                     level = "low";
251             } else if (state & WPL_STATE_IS_BROKEN) {
252                 level = "mixed";
253             }
254             // provide a visual indicator of the security state here.
255         }
256         */
257     },
259     /* Inherit from buffer */
261     __proto__ : buffer.prototype
265 add_hook("current_content_buffer_finished_loading_hook",
266          function (buffer) {
267                  buffer.window.minibuffer.show("Done");
268          });
270 add_hook("current_content_buffer_status_change_hook",
271          function (buffer, request, status, msg) {
272              buffer.set_default_message(msg);
273          });
276 define_variable("url_completion_use_webjumps", true, "Specifies whether URL completion should complete webjumps.");
277 define_variable("url_completion_use_bookmarks", true, "Specifies whether URL completion should complete bookmarks.");
278 define_variable("url_completion_use_history", false,
279                      "Specifies whether URL completion should complete using browser history.");
281 minibuffer_auto_complete_preferences["url"] = true;
282 minibuffer.prototype.read_url = function () {
283     keywords(arguments, $prompt = "URL:", $history = "url", $initial_value = "",
284              $use_webjumps = url_completion_use_webjumps,
285              $use_history = url_completion_use_history,
286              $use_bookmarks = url_completion_use_bookmarks);
287     var completer = url_completer ($use_webjumps = arguments.$use_webjumps,
288         $use_bookmarks = arguments.$use_bookmarks,
289         $use_history = arguments.$use_history);
290     var result = yield this.read(
291         $prompt = arguments.$prompt,
292         $history = arguments.$history,
293         $completer = completer,
294         $initial_value = arguments.$initial_value,
295         $auto_complete = "url",
296         $select,
297         $match_required = false);
298     if (result == "") // well-formedness check. (could be better!)
299         throw ("invalid url or webjump (\""+ result +"\")");
300     yield co_return(get_url_or_webjump(result));
303 I.content_charset = interactive_method(
304     $sync = function (ctx) {
305         var buffer = ctx.buffer;
306         if (!(buffer instanceof content_buffer))
307             throw new Error("Current buffer is of invalid type");
308         // -- Charset of content area of focusedWindow
309         var focusedWindow = buffer.focused_frame;
310         if (focusedWindow)
311             return focusedWindow.document.characterSet;
312         else
313             return null;
314     });
317 I.content_selection = interactive_method(
318     $sync = function (ctx) {
319         // -- Selection of content area of focusedWindow
320         var focusedWindow = this.buffers.current.focused_frame;
321         return focusedWindow.getSelection ();
322     });
324 function overlink_update_status(buffer, text) {
325     if (text.length > 0)
326         buffer.window.minibuffer.show("Link: " + text);
327     else
328         buffer.window.minibuffer.show("");
331 define_global_mode("overlink_mode",
332                    function () {
333                        add_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
334                    },
335                    function () {
336                        remove_hook("current_content_buffer_overlink_change_hook", overlink_update_status);
337                    });
339 overlink_mode(true);
341 function open_in_browser(buffer, target, lspec)
343     switch (target) {
344     case OPEN_CURRENT_BUFFER:
345     case FOLLOW_DEFAULT:
346     case FOLLOW_CURRENT_FRAME:
347     case FOLLOW_TOP_FRAME:
348         if (buffer instanceof content_buffer)  {
349             apply_load_spec(buffer, lspec);
350             break;
351         }
352         target = OPEN_NEW_BUFFER;
353         // If the current buffer is not a content_buffer, use a new buffer.
354     default:
355         create_buffer(buffer.window,
356                       buffer_creator(content_buffer,
357                                      $load = lspec,
358                                      $configuration = buffer.configuration),
359                       target);
360         break;
361     }
364 interactive("open-url",
365             "Open a URL, reusing the current buffer by default",
366             function (I) {
367                 var target = I.browse_target("open");
368                 open_in_browser(I.buffer, target,
369                                 (yield I.minibuffer.read_url($prompt = browse_target_prompt(target))));
370             });
372 interactive("find-alternate-url",
373             "Edit the current URL in the minibuffer",
374             function (I) {
375                 var target = I.browse_target("open");
376                 check_buffer(I.buffer, content_buffer);
377                 open_in_browser(I.buffer, target,
378                                 (yield I.minibuffer.read_url($prompt = browse_target_prompt(target),
379                                                              $initial_value = I.buffer.display_URI_string)));
380             });
382 interactive("find-url",
383             "Open a URL in a new buffer",
384             function (I) {
385                 var target = I.browse_target("find-url");
386                 open_in_browser(I.buffer, target,
387                                 (yield I.minibuffer.read_url($prompt = browse_target_prompt(target))));
388             });
389 default_browse_targets["find-url"] = [OPEN_NEW_BUFFER, OPEN_NEW_WINDOW];
391 function go_up (b, target)
393     var url = Cc["@mozilla.org/network/standard-url;1"]
394         .createInstance (Ci.nsIURL);
395     url.spec = b.current_URI.spec;
396     var up;
397     if (url.param != "" || url.query != "")
398         up = url.filePath;
399     else if (url.fileName != "")
400         up = ".";
401     else
402         up = "..";
403     open_in_browser(b, target, b.current_URI.resolve (up));
405 interactive("go-up",
406             "Go to the parent directory of the current URL",
407             function (I) { go_up(check_buffer(I.buffer, content_buffer),  I.browse_target("go-up")); });
408 default_browse_targets["go-up"] = "open";
411 function go_back (b, prefix)
413     if (prefix < 0)
414         go_forward(b, -prefix);
416     check_buffer(b, content_buffer);
418     if (b.web_navigation.canGoBack)
419     {
420         var hist = b.web_navigation.sessionHistory;
421         var idx = hist.index - prefix;
422         if (idx < 0)
423             idx = 0;
424         b.web_navigation.gotoIndex(idx);
425     } else
426         throw interactive_error("Can't go back");
428 interactive(
429     "go-back",
430     "Go back in the session hisory for the current buffer.",
431     function (I) {go_back(I.buffer, I.p);});
433 function go_forward (b, prefix)
435     if (prefix < 0)
436         go_back(b, -prefix);
438     check_buffer(b, content_buffer);
440     if (b.web_navigation.canGoForward)
441     {
442         var hist = b.web_navigation.sessionHistory;
443         var idx = hist.index + prefix;
444         if (idx >= hist.count) idx = hist.count-1;
445         b.web_navigation.gotoIndex(idx);
446     } else
447         throw interactive_error("Can't go forward");
449 interactive("go-forward",
450             "Go back in the session hisory for the current buffer.",
451             function (I) {go_forward(I.buffer, I.p);});
453 function stop_loading (b)
455     check_buffer(b, content_buffer);
456     b.web_navigation.stop(Ci.nsIWebNavigation.STOP_NETWORK);
458 interactive("stop-loading",
459             "Stop loading the current document.",
460             function (I) {stop_loading(I.buffer);});
462 function reload (b, bypass_cache)
464     check_buffer(b, content_buffer);
465     var flags = bypass_cache != null ?
466         Ci.nsIWebNavigation.LOAD_FLAGS_NONE : Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
467     b.web_navigation.reload(flags);
469 interactive("reload",
470             "Reload the current document.\n" +
471             "If a prefix argument is specified, the cache is bypassed.",
472             function (I) {reload(I.buffer, I.P);});
475  * browserDOMWindow: intercept window opening
476  */
477 function initialize_browser_dom_window(window) {
478     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
479         new browser_dom_window(window);
482 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'.");
484 function browser_dom_window(window) {
485     this.window = window;
486     this.next_target = null;
488 browser_dom_window.prototype = {
489     QueryInterface: generate_QI(Ci.nsIBrowserDOMWindow),
491     openURI : function(aURI, aOpener, aWhere, aContext) {
493         // Reference: http://www.xulplanet.com/references/xpcomref/ifaces/nsIBrowserDOMWindow.html
494         var target = this.next_target;
495         if (target == null || target == FOLLOW_DEFAULT)
496             target = browser_default_open_target;
497         this.next_target = null;
499         /* Determine the opener buffer */
500         var opener_buffer = get_buffer_from_frame(this.window, aOpener.top);
501         var config = opener_buffer ? opener_buffer.configuration : null;
503         switch (browser_default_open_target) {
504         case OPEN_CURRENT_BUFFER:
505         case FOLLOW_TOP_FRAME:
506             return aOpener.top;
507         case FOLLOW_CURRENT_FRAME:
508             return aOpener;
509         case OPEN_NEW_BUFFER:
510             var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
511             this.window.buffers.current = buffer;
512             return buffer.top_frame;
513         case OPEN_NEW_BUFFER_BACKGROUND:
514             var buffer = new content_buffer(this.window, null /* element */, $configuration = config);
515             return buffer.top_frame;
516         case OPEN_NEW_WINDOW:
517         default: /* shouldn't be needed */
519             /* We don't call make_window here, because that will result
520              * in the URL being loaded as the top-level document,
521              * instead of within a browser buffer.  Instead, we can
522              * rely on Mozilla using browser.chromeURL. */
523             window_set_extra_arguments({initial_buffer_configuration: config});
524             return null;
525         }
526     }
529 add_hook("window_initialize_early_hook", initialize_browser_dom_window);
531 define_keywords("$enable", "$disable", "$doc");
532 function define_page_mode(name, display_name) {
533     keywords(arguments);
534     var enable = arguments.$enable;
535     var disable = arguments.$disable;
536     var doc = arguments.$doc;
537     define_buffer_mode(name, display_name,
538                        $class = "page_mode",
539                        $enable = enable,
540                        $disable = function (buffer) {
541                            if (disable)
542                                disable(buffer);
543                            buffer.local_variables = {};
544                        },
545                        $doc = doc);
547 ignore_function_for_get_caller_source_code_reference("define_page_mode");
549 define_variable("auto_mode_list", [], "A list of mappings from URI regular expressions to page modes.");
550 function page_mode_auto_update(buffer) {
551     var uri = buffer.current_URI.spec;
552     var mode = predicate_alist_match(auto_mode_list, uri);
553     if (mode)
554         mode(buffer, true);
555     else if (buffer.page_mode)
556         conkeror[buffer.page_mode](buffer, false);
559 add_hook("content_buffer_location_change_hook", page_mode_auto_update);