xkcd-mode: use buffer_loaded_hook instead of buffer_dom_content_loaded_hook
[conkeror.git] / modules / session.js
blob33d60871bbd53553d2818ac5fedaaa639fee57c5
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  */
35 in_module(null);
38     //// Manual sessions. ////
41     let _session_dir_default = Cc["@mozilla.org/file/directory_service;1"]
42         .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
43     _session_dir_default.append("sessions");
44     if (! _session_dir_default.exists())
45         _session_dir_default.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
47     define_variable("session_dir", _session_dir_default,
48         "Default directory for save/load interactive commands.");
50     /**
51      * session_get generates and returns a structure containing session
52      * data for the current group of open windows.
53      */
54     function session_get () {
55         let windows = {};
56         let x = 0;
57         for_each_window(function (w) {
58             let buffers = [];
59             w.buffers.for_each(function (b) {
60                 if (b instanceof content_buffer)
61                     buffers.push(b.display_uri_string);
62             });
63             if (buffers.length == 0)
64                 return;
65             windows[x] = buffers;
66             x++;
67         });
68         return windows;
69     }
71     /**
72      * session_load loads the given session (given as a data structure),
73      * with optional window as the first window to load the session into,
74      * with optional buffer_idx as the buffer index at which to begin
75      * overwriting existing buffers.
76      */
77     function session_load (session, window, buffer_idx) {
78         if (! (session[0] && session[0][0]))
79             throw new Error("Invalid 'session' argument.");
80         let s = 0;
81         if (window) {
82             let bi = buffer_idx != undefined ?
83                 buffer_idx : window.buffers.count;
85             // first kill special buffers slated for recycling.
86             let (b, i = (bi == 0 ? 1 : bi),
87                  safe2kill = bi > 0)
88             {
89                 while ((b = window.buffers.get_buffer(i))) {
90                     if (b instanceof content_buffer) {
91                         safe2kill = true;
92                         ++i;
93                     } else
94                         kill_buffer(b, true);
95                 }
96                 if (bi == 0 &&
97                     (b = window.buffers.get_buffer(0)) &&
98                     !(b instanceof content_buffer))
99                 {
100                     if (! safe2kill)
101                         create_buffer(window,
102                                       buffer_creator(content_buffer),
103                                       OPEN_NEW_BUFFER_BACKGROUND);
104                     kill_buffer(b, true);
105                 }
106             }
108             // it is now safe to recycle the remaining buffers.
109             for (let i = 0; session[s][i] != undefined; ++i, ++bi) {
110                 let b = window.buffers.get_buffer(bi);
111                 if (b) {
112                     try {
113                         let history = b.web_navigation.sessionHistory;
114                         history.PurgeHistory(history.count);
115                     } catch (e) {}
116                     b.load(session[s][i]);
117                 } else {
118                     let c = buffer_creator(content_buffer, $load = session[s][i]);
119                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
120                 }
121             }
122             for (let b = window.buffers.get_buffer(bi); b;
123                  b = window.buffers.get_buffer(bi))
124             {
125                 kill_buffer(b, true);
126             }
127             ++s;
128         }
130         function make_init_hook (session) {
131             function init_hook (window) {
132                 for (let i = 1; session[i] != undefined; ++i) {
133                     let c = buffer_creator(content_buffer, $load = session[i]);
134                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
135                 }
136             }
137             return init_hook;
138         }
140         for (; session[s] != undefined; ++s) {
141             let w = make_window(buffer_creator(content_buffer,
142                                                $load = session[s][0]));
143             add_hook.call(w, "window_initialize_late_hook",
144                           make_init_hook(session[s]));
145         }
146     }
148     /**
149      * session_load_window_new loads the given session into new windows.
150      */
151     function session_load_window_new (session) {
152         session_load(session);
153     }
155     /**
156      * session_load_window_current loads the given session, with the
157      * session's first window being appended to window.  No existing
158      * buffers will be overwritten.
159      */
160     function session_load_window_current (session, window) {
161         let w = window ? window : get_recent_conkeror_window();
162         session_load(session, w);
163     }
165     /**
166      * session_load_window_current loads the given session, with the
167      * session's first window replacing the given window.  All buffers in
168      * the given window will be overwritten.
169      */
170     function session_load_window_current_replace (session, window) {
171         let w = window ? window : get_recent_conkeror_window();
172         session_load(session, w, 0);
173     }
175     /**
176      * session_write writes the given session to the file given by path.
177      */
178     function session_write (path, session) {
179         if (! (path instanceof Ci.nsIFile))
180             path = make_file(path);
181         if (! session)
182             session = session_get();
183         write_text_file(path, JSON.stringify(session));
184     }
186     /**
187      * session_read reads session data from the given file path,
188      * and returns a decoded session structure.
189      */
190     function session_read (path) {
191         if (! (path instanceof Ci.nsIFile))
192             path = make_file(path);
193         return JSON.parse(read_text_file(path));
194     }
196     /**
197      * session_remove deletes the given session file.
198      */
199     function session_remove (path) {
200         if (! (path instanceof Ci.nsIFile))
201             path = make_file(path);
202         path.remove(false);
203     }
205     let _session_prompt_file = function (I) {
206         yield co_return(
207             yield I.minibuffer.read_file_path(
208                 $prompt = "Session file:",
209                 $initial_value = session_dir.path,
210                 $history = "save"
211             )
212         );
213     };
215     let _session_file_not_found = function (I, file) {
216         let mb = I ? I.minibuffer : get_recent_conkeror_window().minibuffer;
217         let msg = "Session file not found: " + file.path;
218         mb.message(msg);
219         dumpln(msg);
220     }
222     interactive("session-save",
223         "Save the current session.",
224         function (I) {
225             session_write(make_file(yield _session_prompt_file(I)),
226                           session_get());
227         });
229     interactive("session-load-window-new",
230         "Load a session in a new window.", 
231         function (I) {
232             let file = make_file(yield _session_prompt_file(I));
233             if (! file.exists())
234                 _session_file_not_found(I, file);
235             else
236                 session_load_window_new(session_read(file));
237         });
239     interactive("session-load-window-current",
240         "Load a session in new buffers in the current window.",
241         function (I) {
242             let file = make_file(yield _session_prompt_file(I));
243             if (! file.exists())
244                 _session_file_not_found(I, file);
245             else
246                 session_load_window_current(session_read(file), I.window);
247         });
249     interactive("session-load-window-current-replace", 
250         "Replace all buffers in the current window with buffers "+
251         "in the saved session.",
252         function (I) {
253             let file = make_file(yield _session_prompt_file(I));
254             if (! file.exists())
255                 _session_file_not_found(I, file);
256             else
257                 session_load_window_current_replace(session_read(file),
258                                                     I.window, 0);
259         });
261     interactive("session-remove",
262         "Remove a session file.",
263         function (I) {
264             let file = make_file(yield _session_prompt_file(I));
265             if (! file.exists())
266                 _session_file_not_found(I, file);
267             else
268                 session_remove(file);
269         });
272     //// Auto-save sessions. ////
275     define_variable("session_auto_save_file", "auto-save",
276         "Default filename for the auto-save session.");
278     define_variable("session_auto_save_auto_load", false,
279         'Whether to load the auto-saved session when the browser is started. '+
280         'May be true, false, or "prompt".');
282     function session_auto_save_load_window_new () {
283         session_load_window_new(_session_auto_save_cached);
284     }
286     function session_auto_save_load_window_current (window) {
287         session_load_window_current(_session_auto_save_cached, window);
288     }
290     function session_auto_save_load_window_current_replace (window) {
291         session_load_window_current_replace(_session_auto_save_cached, window);
292     }
293     
294     define_variable("session_auto_save_auto_load_fn",
295         null,
296         "Function to be called to load the auto-saved session at start-up " +
297         "when URLs are given on the command-line. May be " +
298         "session_auto_save_load_window_new, " + 
299         "session_auto_save_load_window_current, or null. If null, the" +
300         "session will not be auto-loaded when URLs are given.");
302     // Supported values:
303     //   undefined - we have not tried to cache the auto-save.
304     //   null      - we have tried to cache the auto-save, but it didn't exist.
305     //   object    - the cached session object for the auto-save.
306     let _session_auto_save_cached = undefined;
308     let _session_auto_save_file_get = function () {
309         if (session_auto_save_file instanceof Ci.nsIFile)
310             return session_auto_save_file;
311         let f = session_dir.clone();
312         f.append(session_auto_save_file);
313         return f;
314     };
316     function session_auto_save_save () {
317         let f = _session_auto_save_file_get();
318         let s = session_get();
319         if (s[0])
320             session_write(f, s);
321         else if (f.exists())
322             f.remove(false);
323     }
325     function session_auto_save_remove () {
326         let f = _session_auto_save_file_get();
327         if (f.exists())
328             f.remove(false);
329     }
331     let _session_auto_save_auto_load = function (user_gave_urls) {
332         if (! session_auto_save_auto_load)
333             return;
334         if (! _session_auto_save_cached) {
335             _session_file_not_found(null, _session_auto_save_file_get());
336             return;
337         }
338         let do_load = false;
339         let window = get_recent_conkeror_window();
340         if (session_auto_save_auto_load == true)
341             do_load = true;
342         else if (session_auto_save_auto_load == "prompt" && !user_gave_urls) {
343             do_load = (yield window.minibuffer.read_single_character_option(
344                 $prompt = "Load auto-saved session? (y/n)",
345                 $options = ["y", "n"]
346             )) == "y";
347         } else
348             throw new Error("Invalid value for session_auto_save_auto_load: " +
349                             session_auto_save_auto_load);
350         if (! do_load)
351             return;
352         if (user_gave_urls) {
353             if (session_auto_save_auto_load_fn)
354                 session_auto_save_auto_load_fn(window);
355         } else
356             session_auto_save_load_window_current_replace(window);
357     };
359     interactive("session-auto-save-load-window-new",
360         "Load the auto-save session in a new window.",
361         function (I) { 
362             if (_session_auto_save_cached == null)
363                 _session_file_not_found(I, _session_auto_save_file_get());
364             else
365                 session_auto_save_load_window_new();
366         });
368     interactive("session-auto-save-load-window-current",
369         "Load the auto-save session in new buffers in the current window.",
370         function (I) {
371             if (_session_auto_save_cached == null)
372                 _session_file_not_found(I, _session_auto_save_file_get());
373             else
374                 session_auto_save_load_window_current(I.window);
375         });
377     interactive("session-auto-save-load-window-current-replace",
378         "Replace all buffers in the current window with buffers in the "+
379         "auto-saved session.",
380         function (I) {
381             if (_session_auto_save_cached == null)
382                 _session_file_not_found(I, _session_auto_save_file_get());
383             else
384                 session_auto_save_load_window_current_replace(I.window);
385         });
387     interactive("session-auto-save-remove",
388                 "Remove the auto-save session",
389                 session_auto_save_remove);
392     //// auto-save-session-mode ////
395     let _session_auto_save_mode_bootstrap = function (b) {
396         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
397         add_hook("create_buffer_hook", session_auto_save_save);
398         add_hook("kill_buffer_hook", session_auto_save_save);
399         add_hook("content_buffer_location_change_hook", session_auto_save_save);
400         let user_gave_urls = false;
401         for (let i = 0; i < command_line.length; ++i) {
402             if (command_line[i][0] != '-') {
403                 user_gave_urls = true;
404                 break;
405             }
406         }
407         co_call(_session_auto_save_auto_load(user_gave_urls));
408     };
410     let _session_auto_save_mode_enable = function () {
411         if (_session_auto_save_cached == undefined) {
412             let f = _session_auto_save_file_get();
413             _session_auto_save_cached = f.exists() ? session_read(f) : null;
414         }
415         if (conkeror_started) {
416             add_hook("create_buffer_hook", session_auto_save_save);
417             add_hook("kill_buffer_hook", session_auto_save_save);
418             add_hook("content_buffer_location_change_hook", session_auto_save_save);
419         } else
420             add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
421     };
423     let _session_auto_save_mode_disable = function () {
424         remove_hook("create_buffer_hook", session_auto_save_save);
425         remove_hook("kill_buffer_hook", session_auto_save_save);
426         remove_hook("content_buffer_location_change_hook", session_auto_save_save);
427         // Just in case.
428         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
429     };
431     define_global_mode("session_auto_save_mode",
432                        _session_auto_save_mode_enable,
433                        _session_auto_save_mode_disable);
435     session_auto_save_mode(true);
438 provide("session");