Debian: Declare compliance with Debian Policy 4.1.1
[conkeror.git] / modules / session.js
blob806f2606bc62110a0eb9e5ca80eef8836467cbe8
1 /**
2  * (C) Copyright 2009 Nicholas A. Zigarovich
3  *
4  * Use, modification, and distribution are subject to the terms specified in the
5  * COPYING file.
6 **/
8 /* TODO
9  *
10  * Features:
11  *
12  * - A session-load-window-replace command which is like
13  *   window-current-replace, but which also recycles existing windows.
14  * - A session-load-window-current-flatten command which loads all buffers in a
15  *   multi-window section in the current window.
16  * - Session files should be more human readable.
17  * - Ability to store arbitrary session data, for example, a buffer's history.
18  * - The previous two features depend on a parser/generator for retroj's
19  *   structured plaintext format.
20  *
21  * Bugs, critical:
22  *
23  * - This module does not work correctly with daemon mode.
24  *
25  * Bugs, deferred:
26  *
27  * - Inhibit loading of the homepage in windows' initial buffers when auto-
28  *   loading a session.
29  * - Auto-save the session when the last conkeror window is closed by a
30  *   window manager event. Currently no session is saved.
31  * - Consider how and which errors should be handled. Too often we silently
32  *   fail and return without telling the user why we are doing so.
33  */
36     //// Manual sessions. ////
39     let _session_dir_default = Cc["@mozilla.org/file/directory_service;1"]
40         .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
41     _session_dir_default.append("sessions");
42     if (! _session_dir_default.exists())
43         _session_dir_default.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
45     define_variable("session_dir", _session_dir_default,
46         "Default directory for save/load interactive commands.");
48     define_variable("session_save_buffer_access_order", false,
49         "Whether to store last-accessed order in the sessions file.");
51     /**
52      * session_token is instantiated for the $opener property of buffers
53      * created by the session.  This allows the possibility of knowing,
54      * during buffer init, whether the buffer was created by a session or
55      * by other means.
56      */
57     var session_token = function session_token () {}
58     session_token.prototype = {
59         constructor: session_token
60     };
62     /**
63      * session_get generates and returns a structure containing session
64      * data for the current group of open windows.
65      */
66     var session_get = function session_get () {
67         let windows = {};
68         let x = 0;
69         for_each_window(function (w) {
70             let buffers = [];
71             w.buffers.for_each(function (b) {
72                 if (b instanceof content_buffer) {
73                     if (session_save_buffer_access_order) {
74                         buffers.push({
75                             url: b.display_uri_string,
76                             access_index: w.buffers.buffer_history.indexOf(b)
77                         });
78                     } else {
79                         buffers.push(b.display_uri_string);
80                     }
81                 }
82             });
83             if (buffers.length == 0)
84                 return;
85             windows[x] = buffers;
86             x++;
87         });
88         return windows;
89     }
91     /**
92      * session_load loads the given session (given as a data structure),
93      * with optional window as the first window to load the session into,
94      * with optional buffer_idx as the buffer index at which to begin
95      * overwriting existing buffers.
96      */
97     var session_load = function session_load (session, window, buffer_idx) {
98         if (! (session[0] && session[0][0]))
99             throw new Error("Invalid 'session' argument.");
100         let s = 0;
101         var opener = new session_token();
102         if (window) {
103             let bi = buffer_idx != undefined ?
104                 buffer_idx : window.buffers.count;
106             // first kill special buffers slated for recycling.
107             {
108                 let b, i = (bi == 0 ? 1 : bi),
109                     safe2kill = bi > 0;
110                 while ((b = window.buffers.get_buffer(i))) {
111                     if (b instanceof content_buffer) {
112                         safe2kill = true;
113                         ++i;
114                     } else
115                         kill_buffer(b, true);
116                 }
117                 if (bi == 0 &&
118                     (b = window.buffers.get_buffer(0)) &&
119                     !(b instanceof content_buffer))
120                 {
121                     if (! safe2kill)
122                         create_buffer(window,
123                                       buffer_creator(content_buffer,
124                                                      $opener = opener),
125                                       OPEN_NEW_BUFFER_BACKGROUND);
126                     kill_buffer(b, true);
127                 }
128             }
130             // it is now safe to recycle the remaining buffers.
131             for (let i = 0; session[s][i] != undefined; ++i, ++bi) {
132                 let b = window.buffers.get_buffer(bi);
133                 if (b) {
134                     try {
135                         let history = b.web_navigation.sessionHistory;
136                         history.PurgeHistory(history.count);
137                     } catch (e) {}
138                     b.load(session[s][i].url);
139                 } else {
140                     let c = buffer_creator(content_buffer,
141                                            $load = session[s][i].url,
142                                            $opener = opener,
143                                            $position = buffer_position_end);
144                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
145                 }
146             }
147             for (let b = window.buffers.get_buffer(bi); b;
148                  b = window.buffers.get_buffer(bi))
149             {
150                 kill_buffer(b, true);
151             }
152             if ('access_index' in session[s][0]) {
153                 var ts = session[s].slice(0);
154                 ts.sort(function (a, b) { return b.access_index - a.access_index; });
155                 for (let i = 0, m = ts.length; i < m; ++i) {
156                     for (let j = 0, n = window.buffers.count; j < n; j++) {
157                         var b = window.buffers.get_buffer(j);
158                         if (ts[i].url == b.display_uri_string) {
159                             window.buffers.buffer_history.splice(
160                                 window.buffers.buffer_history.indexOf(b), 1);
161                             window.buffers.buffer_history.unshift(b);
162                             break;
163                         }
164                     }
165                 }
166                 switch_to_buffer(window, window.buffers.buffer_history[0]);
167             }
168             ++s;
169         }
171         function make_init_hook (session) {
172             function init_hook (window) {
173                 for (let i = 1; session[i] != undefined; ++i) {
174                     let c = buffer_creator(content_buffer,
175                                            $load = session[i].url,
176                                            $opener = opener,
177                                            $position = buffer_position_end);
178                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
179                 }
180                 if ('access_index' in session[0]) {
181                     var ts = session.slice(0);
182                     ts.sort(function (a, b) { return b.access_index - a.access_index; });
183                     for (let i = 0, n = ts.length; i < n; ++i) {
184                         for (let j = 0, m = window.buffers.count; j < m; j++) {
185                             var b = window.buffers.get_buffer(j);
186                             if (ts[i].url == b.display_uri_string) {
187                                 window.buffers.buffer_history.splice(
188                                     window.buffers.buffer_history.indexOf(b), 1);
189                                 window.buffers.buffer_history.unshift(b);
190                                 break;
191                             }
192                         }
193                     }
194                     switch_to_buffer(window, window.buffers.buffer_history[0]);
195                 }
196             }
197             return init_hook;
198         }
200         for (; session[s] != undefined; ++s) {
201             let w = make_window(buffer_creator(content_buffer,
202                                                $load = session[s][0].url,
203                                                $opener = opener));
204             add_hook.call(w, "window_initialize_late_hook",
205                           make_init_hook(session[s]));
206         }
207     }
209     /**
210      * session_load_window_new loads the given session into new windows.
211      */
212     var session_load_window_new = function session_load_window_new (session) {
213         session_load(session);
214     }
216     /**
217      * session_load_window_current loads the given session, with the
218      * session's first window being appended to window.  No existing
219      * buffers will be overwritten.
220      */
221     var session_load_window_current =
222         function session_load_window_current (session, window) {
223             let w = window ? window : get_recent_conkeror_window();
224             session_load(session, w);
225         }
227     /**
228      * session_load_window_current loads the given session, with the
229      * session's first window replacing the given window.  All buffers in
230      * the given window will be overwritten.
231      */
232     var session_load_window_current_replace =
233         function session_load_window_current_replace (session, window) {
234             let w = window ? window : get_recent_conkeror_window();
235             session_load(session, w, 0);
236         }
238     /**
239      * session_write writes the given session to the file given by path.
240      */
241     var session_write = function session_write (path, session) {
242         if (! (path instanceof Ci.nsIFile))
243             path = make_file(path);
244         if (! session)
245             session = session_get();
246         write_text_file(path, JSON.stringify(session));
247     }
249     /**
250      * session_read reads session data from the given file path,
251      * and returns a decoded session structure.
252      */
253     var session_read = function session_read (path) {
254         if (! (path instanceof Ci.nsIFile))
255             path = make_file(path);
256         var rv = JSON.parse(read_text_file(path));
257         for (var i in rv) {
258             for (let e = 0, n = rv[i].length; e < n; ++e) {
259                 if (typeof rv[i][e] == "string")
260                     rv[i][e] = { url: rv[i][e] };
261             }
262         }
263         return rv;
264     }
266     /**
267      * session_remove deletes the given session file.
268      */
269     var session_remove = function session_remove (path) {
270         if (! (path instanceof Ci.nsIFile))
271             path = make_file(path);
272         path.remove(false);
273     }
275     let _session_prompt_file = function (I) {
276         yield co_return(
277             yield I.minibuffer.read_file_path(
278                 $prompt = "Session file:",
279                 $initial_value = session_dir.path,
280                 $history = "save"
281             )
282         );
283     };
285     let _session_file_not_found = function (I, file) {
286         let mb = I ? I.minibuffer : get_recent_conkeror_window().minibuffer;
287         let msg = "Session file not found: " + file.path;
288         mb.message(msg);
289         dumpln(msg);
290     }
292     interactive("session-save",
293         "Save the current session.",
294         function (I) {
295             session_write(make_file(yield _session_prompt_file(I)),
296                           session_get());
297         });
299     interactive("session-load-window-new",
300         "Load a session in a new window.",
301         function (I) {
302             let file = make_file(yield _session_prompt_file(I));
303             if (! file.exists())
304                 _session_file_not_found(I, file);
305             else
306                 session_load_window_new(session_read(file));
307         });
309     interactive("session-load-window-current",
310         "Load a session in new buffers in the current window.",
311         function (I) {
312             let file = make_file(yield _session_prompt_file(I));
313             if (! file.exists())
314                 _session_file_not_found(I, file);
315             else
316                 session_load_window_current(session_read(file), I.window);
317         });
319     interactive("session-load-window-current-replace",
320         "Replace all buffers in the current window with buffers "+
321         "in the saved session.",
322         function (I) {
323             let file = make_file(yield _session_prompt_file(I));
324             if (! file.exists())
325                 _session_file_not_found(I, file);
326             else
327                 session_load_window_current_replace(session_read(file),
328                                                     I.window, 0);
329         });
331     interactive("session-remove",
332         "Remove a session file.",
333         function (I) {
334             let file = make_file(yield _session_prompt_file(I));
335             if (! file.exists())
336                 _session_file_not_found(I, file);
337             else
338                 session_remove(file);
339         });
342     //// Auto-save sessions. ////
345     define_variable("session_auto_save_file", "auto-save",
346         "Default filename for the auto-save session.");
348     define_variable("session_auto_save_auto_load", false,
349         'Whether to load the auto-saved session when the browser is started. '+
350         'May be true, false, or "prompt".');
352     var session_auto_save_load_window_new =
353         function session_auto_save_load_window_new () {
354             session_load_window_new(_session_auto_save_cached);
355         }
357     var session_auto_save_load_window_current =
358         function session_auto_save_load_window_current (window) {
359             session_load_window_current(_session_auto_save_cached, window);
360         }
362     var session_auto_save_load_window_current_replace =
363         function session_auto_save_load_window_current_replace (window) {
364             session_load_window_current_replace(_session_auto_save_cached, window);
365         }
367     define_variable("session_auto_save_auto_load_fn",
368         null,
369         "Function to be called to load the auto-saved session at start-up " +
370         "when URLs are given on the command-line. May be " +
371         "session_auto_save_load_window_new, " +
372         "session_auto_save_load_window_current, or null. If null, the" +
373         "session will not be auto-loaded when URLs are given.");
375     // Supported values:
376     //   undefined - we have not tried to cache the auto-save.
377     //   null      - we have tried to cache the auto-save, but it didn't exist.
378     //   object    - the cached session object for the auto-save.
379     let _session_auto_save_cached = undefined;
381     let _session_auto_save_file_get = function () {
382         if (session_auto_save_file instanceof Ci.nsIFile)
383             return session_auto_save_file;
384         let f = session_dir.clone();
385         f.append(session_auto_save_file);
386         return f;
387     };
389     var session_auto_save_save = function session_auto_save_save () {
390         let f = _session_auto_save_file_get();
391         let s = session_get();
392         if (s[0])
393             session_write(f, s);
394         else if (f.exists())
395             f.remove(false);
396     }
398     var session_auto_save_remove = function session_auto_save_remove () {
399         let f = _session_auto_save_file_get();
400         if (f.exists())
401             f.remove(false);
402     }
404     let _session_auto_save_auto_load = function (user_gave_urls) {
405         if (! session_auto_save_auto_load)
406             return;
407         if (! _session_auto_save_cached) {
408             _session_file_not_found(null, _session_auto_save_file_get());
409             return;
410         }
411         let do_load = false;
412         let window = get_recent_conkeror_window();
413         if (session_auto_save_auto_load == true)
414             do_load = true;
415         else if (session_auto_save_auto_load == "prompt" && !user_gave_urls) {
416             do_load = (yield window.minibuffer.read_single_character_option(
417                 $prompt = "Load auto-saved session? (y/n)",
418                 $options = ["y", "n"]
419             )) == "y";
420         } else
421             throw new Error("Invalid value for session_auto_save_auto_load: " +
422                             session_auto_save_auto_load);
423         if (! do_load)
424             return;
425         if (user_gave_urls) {
426             if (session_auto_save_auto_load_fn)
427                 session_auto_save_auto_load_fn(window);
428         } else
429             session_auto_save_load_window_current_replace(window);
430     };
432     interactive("session-auto-save-load-window-new",
433         "Load the auto-save session in a new window.",
434         function (I) {
435             if (_session_auto_save_cached == null)
436                 _session_file_not_found(I, _session_auto_save_file_get());
437             else
438                 session_auto_save_load_window_new();
439         });
441     interactive("session-auto-save-load-window-current",
442         "Load the auto-save session in new buffers in the current window.",
443         function (I) {
444             if (_session_auto_save_cached == null)
445                 _session_file_not_found(I, _session_auto_save_file_get());
446             else
447                 session_auto_save_load_window_current(I.window);
448         });
450     interactive("session-auto-save-load-window-current-replace",
451         "Replace all buffers in the current window with buffers in the "+
452         "auto-saved session.",
453         function (I) {
454             if (_session_auto_save_cached == null)
455                 _session_file_not_found(I, _session_auto_save_file_get());
456             else
457                 session_auto_save_load_window_current_replace(I.window);
458         });
460     interactive("session-auto-save-remove",
461                 "Remove the auto-save session",
462                 session_auto_save_remove);
465     //// auto-save-session-mode ////
468     let _session_auto_save_mode_bootstrap = function (b) {
469         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
470         add_hook("create_buffer_hook", session_auto_save_save);
471         add_hook("kill_buffer_hook", session_auto_save_save);
472         add_hook("move_buffer_hook", session_auto_save_save);
473         add_hook("content_buffer_location_change_hook", session_auto_save_save);
474         add_hook("select_buffer_hook", session_auto_save_save);
475         let user_gave_urls = false;
476         for (let i = 0; i < command_line.length; ++i) {
477             if (command_line[i][0] != '-') {
478                 user_gave_urls = true;
479                 break;
480             }
481         }
482         spawn(_session_auto_save_auto_load(user_gave_urls));
483     };
485     let _session_auto_save_mode_enable = function () {
486         if (_session_auto_save_cached == undefined) {
487             let f = _session_auto_save_file_get();
488             _session_auto_save_cached = f.exists() ? session_read(f) : null;
489         }
490         if (conkeror_started) {
491             add_hook("create_buffer_hook", session_auto_save_save);
492             add_hook("kill_buffer_hook", session_auto_save_save);
493             add_hook("move_buffer_hook", session_auto_save_save);
494             add_hook("content_buffer_location_change_hook", session_auto_save_save);
495             add_hook("select_buffer_hook", session_auto_save_save);
496         } else
497             add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
498     };
500     let _session_auto_save_mode_disable = function () {
501         remove_hook("create_buffer_hook", session_auto_save_save);
502         remove_hook("kill_buffer_hook", session_auto_save_save);
503         remove_hook("move_buffer_hook", session_auto_save_save);
504         remove_hook("content_buffer_location_change_hook", session_auto_save_save);
505         remove_hook("select_buffer_hook", session_auto_save_save);
506         // Just in case.
507         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
508     };
510     define_global_mode("session_auto_save_mode",
511                        _session_auto_save_mode_enable,
512                        _session_auto_save_mode_disable);
514     session_auto_save_mode(true);
517 provide("session");