Moved browser_buffer code to browser_buffer.js module; added overlink-mode
[conkeror.git] / modules / browser_buffer.js
blob56c5fa5f7849e10855935ebdbf619a81c5b0c9b9
1 define_buffer_local_hook("browser_buffer_finished_loading_hook");
2 define_buffer_local_hook("browser_buffer_progress_change_hook");
3 define_buffer_local_hook("browser_buffer_location_change_hook");
4 define_buffer_local_hook("browser_buffer_status_change_hook");
5 define_buffer_local_hook("browser_buffer_focus_change_hook");
6 define_buffer_local_hook("browser_buffer_overlink_change_hook");
8 define_current_buffer_hook("current_browser_buffer_finished_loading_hook", "browser_buffer_finished_loading_hook");
9 define_current_buffer_hook("current_browser_buffer_progress_change_hook", "browser_buffer_progress_change_hook");
10 define_current_buffer_hook("current_browser_buffer_location_change_hook", "browser_buffer_location_change_hook");
11 define_current_buffer_hook("current_browser_buffer_status_change_hook", "browser_buffer_status_change_hook");
12 define_current_buffer_hook("current_browser_buffer_focus_change_hook", "browser_buffer_focus_change_hook");
13 define_current_buffer_hook("current_browser_buffer_overlink_change_hook", "browser_buffer_overlink_change_hook");
15 /* If browser is null, create a new browser */
16 function browser_buffer(frame, browser)
18     this.is_browser_buffer = true;
19     this.frame = frame;
20     if (browser == null)
21     {
22         browser = create_XUL(frame, "browser");
23         browser.setAttribute("type", "content");
24         browser.setAttribute("flex", "1");
25         browser.setAttribute("usechromesheets",
26                              "chrome://conkeror/content/numbering.css, "
27                              + "chrome://global/skin/xulscrollbars.css");
28         this.frame.buffers.container.appendChild(browser);
29     }
30     this.element = browser;
31     this.element.conkeror_buffer_object = this;
32     this.element.addProgressListener(this);
33     var buffer = this;
34     this.element.addEventListener("DOMTitleChanged", function (event) {
35             buffer_title_change_hook.run(buffer);
36         }, true /* capture */, false /* ignore untrusted events */);
37     this.element.addEventListener("scroll", function (event) {
38             buffer_scroll_hook.run(buffer);
39         }, true /* capture */, false /* ignore untrusted events */);
41     this.element.addEventListener("focus", function (event) {
42             browser_buffer_focus_change_hook.run(buffer);
43         }, true /* capture */, false /* ignore untrusted events */);
45     this.element.addEventListener("mouseover", function (event) {
46             if (event.target instanceof frame.HTMLAnchorElement) {
47                 browser_buffer_overlink_change_hook.run(buffer, event.target.href);
48                 buffer.current_overlink = event.target;
49             }
50         }, true, false);
52     this.element.addEventListener("mouseout", function (event) {
53             if (buffer.current_overlink == event.target) {
54                 buffer.current_overlink = null;
55                 browser_buffer_overlink_change_hook.run(buffer, "");
56             }
57         }, true, false);
60 browser_buffer.prototype = {
61     constructor : browser_buffer.constructor,
63     get content_window() {
64         return this.element.contentWindow;
65     },
67     get content_document() {
68         return this.element.contentDocument;
69     },
71     get title() {
72         return this.element.contentTitle;
73     },
75     get scrollX () { return this.content_window.scrollX; },
76     get scrollY () { return this.content_window.scrollY; },
77     get scrollMaxX () { return this.content_window.scrollMaxX; },
78     get scrollMaxY () { return this.content_window.scrollMaxY; },
81     /* Used to display the correct URI when the buffer opens initially
82      * even before loading has progressed far enough for currentURI to
83      * contain the correct URI. */
84     _display_URI : null,
86     get current_URI () {
87         return this.element.currentURI;
88     },
90     get display_URI_string () {
91         if (this._display_URI)
92             return this._display_URI;
93         return this.current_URI.spec;
94     },
96     get name () {
97         return this.display_URI_string;
98     },
100     get web_navigation () {
101         return this.element.webNavigation;
102     },
104     get doc_shell () {
105         return this.element.docShell;
106     },
108     get markup_document_viewer () {
109         return this.element.markupDocumentViewer;
110     },
112     load_URI : function (URI_s) {
113         this._display_URI = URI_s;
114         this.web_navigation.loadURI(URI_s, Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE,
115                                     null /* referrer */,
116                                     null /* post data */,
117                                     null /* headers */);
118     },
120     on_switch_to : function () {
121         this.element.setAttribute("type", "content-primary");
122     },
124     on_switch_away : function () {
125         this.element.setAttribute("type", "content");
126     },
128     _requests_started: 0,
129     _requests_finished: 0,
131     /* nsIWebProgressListener */
132     QueryInterface: function(iid) {
133         if (iid.equals(Components.interfaces.nsIWebProgressListener) ||
134             iid.equals(Components.interfaces.nsISupportsWeakReference) ||
135             iid.equals(Components.interfaces.nsISupports))
136             return this;
137         throw Components.results.NS_ERROR_NO_INTERFACE;
138     },
140     // This method is called to indicate state changes.
141     onStateChange: function(webProgress, request, stateFlags, status) {
142         const WPL = Components.interfaces.nsIWebProgressListener;
143         /*
144         var flagstr = "";
145         if (stateFlags & WPL.STATE_START)
146             flagstr += ",start";
147         if (stateFlags & WPL.STATE_STOP)
148             flagstr += ",stop";
149         if (stateFlags & WPL.STATE_IS_REQUEST)
150             flagstr += ",request";
151         if (stateFlags & WPL.STATE_IS_DOCUMENT)
152             flagstr += ",document";
153         if (stateFlags & WPL.STATE_IS_NETWORK)
154             flagstr += ",network";
155         if (stateFlags & WPL.STATE_IS_WINDOW)
156             flagstr += ",window";
157         dumpln("onStateChange: " + flagstr + ", status: " + status);
158         */
159         if (stateFlags & WPL.STATE_IS_REQUEST) {
160             if (stateFlags & WPL.STATE_START) {
161                 this._requests_started++;
162             } else if (stateFlags & WPL.STATE_STOP) {
163                 this._requests_finished++;
164             }
165         }
166         if ((stateFlags & WPL.STATE_STOP) && (this._requests_finished == this._requests_started)) {
167             this._requests_finished = this._requests_started = 0;
168             browser_buffer_finished_loading_hook.run(this);
169         }
170     },
172     /* This method is called to indicate progress changes for the currently
173        loading page. */
174     onProgressChange: function(webProgress, request, curSelf, maxSelf,
175                                curTotal, maxTotal) {
176         browser_buffer_progress_change_hook.run(this, request, curSelf, maxSelf, curTotal, maxTotal);
177     },
179     /* This method is called to indicate a change to the current location.
180        The url can be gotten as location.spec. */
181     onLocationChange : function(webProgress, request, location) {
182         /* Use the real location URI now */
183         this._display_URI = null;
184         browser_buffer_location_change_hook.run(this, request, location);
185     },
187     // This method is called to indicate a status changes for the currently
188     // loading page.  The message is already formatted for display.
189     // Status messages could be displayed in the minibuffer output area.
190     onStatusChange: function(webProgress, request, status, msg) {
191         browser_buffer_status_change_hook.run(this, request, status, msg);
192     },
194     // This method is called when the security state of the browser changes.
195     onSecurityChange: function(webProgress, request, state) {
196         const WPL = Components.interfaces.nsIWebProgressListener;
198         if (state & WPL.STATE_IS_INSECURE) {
199             // update visual indicator
200         } else {
201             var level = "unknown";
202             if (state & WPL.STATE_IS_SECURE) {
203                 if (state & WPL.STATE_SECURE_HIGH)
204                     level = "high";
205                 else if (state & WPL.STATE_SECURE_MED)
206                     level = "medium";
207                 else if (state & WPL.STATE_SECURE_LOW)
208                     level = "low";
209             } else if (state & WPL_STATE_IS_BROKEN) {
210                 level = "mixed";
211             }
212             // provide a visual indicator of the security state here.
213         }
214     },
216     child_element : function (element)
217     {
218         return (element && this.child_window(element.ownerDocument.defaultView));
219     },
221     child_window : function (win)
222     {
223         return (win && win.top == this.content_window);
224     },
226     focused_window : function ()
227     {
228         var win = this.frame.document.commandDispatcher.focusedWindow;
229         if (this.child_window(win))
230             return win;
231         return this.content_window;
232     },
234     focused_element : function ()
235     {
236         var element = this.frame.document.commandDispatcher.focusedElement;
237         if (this.child_element(element))
238             return element;
239         return null;
240     },
242     do_command : function (command)
243     {
244         function attempt_command(element, command)
245         {
246             var controller;
247             if (element.controllers
248                 && (controller = element.controllers.getControllerForCommand(command)) != null
249                 && controller.isCommandEnabled(command))
250             {
251                 controller.doCommand(command);
252                 return true;
253             }
254             return false;
255         }
257         var element = this.focused_element();
258         if (element && attempt_command(element, command))
259             return;
260         var win = this.focused_window();
261         do  {
262             if (attempt_command(win, command))
263                 return;
264             win = win.parent;
265         } while (win);
266     },
268     /* Inherit from buffer */
270     __proto__ : buffer.prototype
274 add_hook("current_browser_buffer_finished_loading_hook",
275          function (buffer) {
276                  buffer.frame.minibuffer.show("Done");
277          });
279 add_hook("current_browser_buffer_status_change_hook",
280          function (buffer, request, status, msg) {
281              buffer.frame.minibuffer.show(msg);
282          });
286 //RETROJ: this may be improperly named.  it can read either an url or a
287 //        webjump from the minibuffer, but it will always return an url.
288 I.url_or_webjump = interactive_method(
289     $async = function (ctx, cont) {
290         keywords(arguments, $prompt = "URL:", $history = "url", $initial_value = "");
291         var completions = arguments.$completions;
292         if (completions === undefined)
293         {
294             completions = [];
295             for (var x in gWebJumpLocations)
296                 completions.push([x,x]);
297         }
298         ctx.frame.minibuffer.read_with_completion(
299             $prompt = arguments.$prompt,
300             $history = arguments.$history,
301             $completions = completions,
302             $initial_value = arguments.$initial_value,
303             $select,
304             $allow_non_matches,
305             $callback = function (match,s) {
306                 if (s == "") // well-formedness check. (could be better!)
307                     throw ("invalid url or webjump (\""+s+"\")");
308                 cont(get_url_or_webjump (s));
309             });
310     });
313 I.current_buffer_window = interactive_method(
314     $sync = function (ctx) {
315         var buffer = ctx.frame.buffers.current;
316         if (!(buffer instanceof browser_buffer))
317             throw new Error("Current buffer is of invalid type");
318         return buffer.content_window;
319     });
321 // This name should perhaps change
322 I.current_buffer_document = interactive_method(
323     $sync = function (ctx) {
324         var buffer = ctx.frame.buffers.current;
325         if (!(buffer instanceof browser_buffer))
326             throw new Error("Current buffer is of invalid type");
327         return buffer.content_document;
328     });
330 // This name should perhaps change
331 I.active_document = I.current_buffer_document;
333 I.current_frameset_frame = interactive_method(
334     $sync = function (ctx) {
335         var buffer = ctx.frame.buffers.current;
336         if (!(buffer instanceof browser_buffer))
337             throw new Error("Current buffer is of invalid type");
338         return buffer.focused_window();
339     });
341 I.current_frameset_frame_url = interactive_method(
342     $sync = function (ctx) {
343         var buffer = ctx.frame.buffers.current;
344         if (!(buffer instanceof browser_buffer))
345             throw new Error("Current buffer is of invalid type");
346         return buffer.focused_window().location.href;
347     });
349 // This name should probably change
350 I.current_url = interactive_method(
351     $sync = function (ctx) {
352         var buffer = ctx.frame.buffers.current;
353         if (!(buffer instanceof browser_buffer))
354             throw new Error("Current buffer is of invalid type");
355         return buffer.current_URI.spec;
356     });
359 I.focused_link_url = interactive_method(
360     $sync = function (ctx) {
361         var buffer = ctx.frame.buffers.current;
362         if (!(buffer instanceof browser_buffer))
363             throw new Error("Current buffer is of invalid type");
364         // -- Focused link element
365         ///JJF: check for errors or wrong element type.
366         return get_link_location (buffer.focused_element());
367     });
369 function overlink_update_status(buffer, text) {
370     if (text.length > 0)
371         buffer.frame.minibuffer.show("Link: " + text);
372     else
373         buffer.frame.minibuffer.show("");
376 define_global_mode("overlink_mode",
377                    function () {
378                        add_hook("current_browser_buffer_overlink_change_hook", overlink_update_status);
379                    },
380                    function () {
381                        remove_hook("current_browser_buffer_overlink_change_hook", overlink_update_status);
382                    });
384 overlink_mode(true);