2 * (C) Copyright 2009 Nicholas A. Zigarovich
4 * Use, modification, and distribution are subject to the terms specified in the
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.
23 * - This module does not work correctly with daemon mode.
27 * - Inhibit loading of the homepage in windows' initial buffers when auto-
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.
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, 0755);
45 define_variable("session_dir", _session_dir_default,
46 "Default directory for save/load interactive commands.");
48 define_variable("session_save_hist_index", false,
49 "Whether to store last-accessed order in the sessions file.");
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
57 function session_token () {}
58 session_token.prototype = {
59 constructor: session_token
63 * session_get generates and returns a structure containing session
64 * data for the current group of open windows.
66 function session_get () {
69 for_each_window(function (w) {
71 w.buffers.for_each(function (b) {
72 if (b instanceof content_buffer) {
73 if (!(session_save_hist_index)) {
74 buffers.push(b.display_uri_string);
77 buffers.push({url: b.display_uri_string, hist_index: w.buffers.buffer_history.indexOf(b)});
81 if (buffers.length == 0)
90 * session_load loads the given session (given as a data structure),
91 * with optional window as the first window to load the session into,
92 * with optional buffer_idx as the buffer index at which to begin
93 * overwriting existing buffers.
95 function session_load (session, window, buffer_idx) {
96 if (! (session[0] && session[0][0]))
97 throw new Error("Invalid 'session' argument.");
99 var opener = new session_token();
101 let bi = buffer_idx != undefined ?
102 buffer_idx : window.buffers.count;
104 // first kill special buffers slated for recycling.
105 let (b, i = (bi == 0 ? 1 : bi),
108 while ((b = window.buffers.get_buffer(i))) {
109 if (b instanceof content_buffer) {
113 kill_buffer(b, true);
116 (b = window.buffers.get_buffer(0)) &&
117 !(b instanceof content_buffer))
120 create_buffer(window,
121 buffer_creator(content_buffer,
123 OPEN_NEW_BUFFER_BACKGROUND);
124 kill_buffer(b, true);
128 // it is now safe to recycle the remaining buffers.
129 for (let i = 0; session[s][i] != undefined; ++i, ++bi) {
130 let b = window.buffers.get_buffer(bi);
133 let history = b.web_navigation.sessionHistory;
134 history.PurgeHistory(history.count);
136 b.load(session[s][i].url);
138 let c = buffer_creator(content_buffer,
139 $load = session[s][i].url,
141 $position = buffer_position_end);
142 create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
145 for (let b = window.buffers.get_buffer(bi); b;
146 b = window.buffers.get_buffer(bi))
148 kill_buffer(b, true);
150 if ('hist_index' in session[s][0]) {
151 var ts = session[s].slice(0);
152 ts.sort(function (b,a) { return a.hist_index - b.hist_index; });
153 for (let i = 0, m = ts.length; i < m; ++i) {
154 for (let j = 0, n = window.buffers.count; j < n; j++) {
155 var b = window.buffers.get_buffer(j);
156 if (ts[i].url == b.display_uri_string) {
157 window.buffers.buffer_history.splice(window.buffers.buffer_history.indexOf(b), 1);
158 window.buffers.buffer_history.unshift(b);
163 switch_to_buffer(window, window.buffers.buffer_history[0]);
168 function make_init_hook (session) {
169 function init_hook (window) {
170 for (let i = 1; session[i] != undefined; ++i) {
171 let c = buffer_creator(content_buffer,
172 $load = session[i].url,
174 $position = buffer_position_end);
175 create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
177 if ('hist_index' in session[0]) {
178 var ts = session.slice(0);
179 ts.sort(function (b,a) { return a.hist_index - b.hist_index; });
180 for (let i = 0, n = ts.length; i < n; ++i) {
181 for (let j = 0, m = window.buffers.count; j < m; j++) {
182 var b = window.buffers.get_buffer(j);
183 if (ts[i].url == b.display_uri_string) {
184 window.buffers.buffer_history.splice(window.buffers.buffer_history.indexOf(b), 1);
185 window.buffers.buffer_history.unshift(b);
190 switch_to_buffer(window, window.buffers.buffer_history[0]);
196 for (; session[s] != undefined; ++s) {
197 let w = make_window(buffer_creator(content_buffer,
198 $load = session[s][0].url,
200 add_hook.call(w, "window_initialize_late_hook",
201 make_init_hook(session[s]));
206 * session_load_window_new loads the given session into new windows.
208 function session_load_window_new (session) {
209 session_load(session);
213 * session_load_window_current loads the given session, with the
214 * session's first window being appended to window. No existing
215 * buffers will be overwritten.
217 function session_load_window_current (session, window) {
218 let w = window ? window : get_recent_conkeror_window();
219 session_load(session, w);
223 * session_load_window_current loads the given session, with the
224 * session's first window replacing the given window. All buffers in
225 * the given window will be overwritten.
227 function session_load_window_current_replace (session, window) {
228 let w = window ? window : get_recent_conkeror_window();
229 session_load(session, w, 0);
233 * session_write writes the given session to the file given by path.
235 function session_write (path, session) {
236 if (! (path instanceof Ci.nsIFile))
237 path = make_file(path);
239 session = session_get();
240 write_text_file(path, JSON.stringify(session));
244 * session_read reads session data from the given file path,
245 * and returns a decoded session structure.
247 function session_read (path) {
248 if (! (path instanceof Ci.nsIFile))
249 path = make_file(path);
250 var rv = JSON.parse(read_text_file(path));
252 for (let e = 0; e < rv[i].length; e++) {
253 if (typeof rv[i][e] == "string") {
254 rv[i][e] = {url: rv[i][e]};
262 * session_remove deletes the given session file.
264 function session_remove (path) {
265 if (! (path instanceof Ci.nsIFile))
266 path = make_file(path);
270 let _session_prompt_file = function (I) {
272 yield I.minibuffer.read_file_path(
273 $prompt = "Session file:",
274 $initial_value = session_dir.path,
280 let _session_file_not_found = function (I, file) {
281 let mb = I ? I.minibuffer : get_recent_conkeror_window().minibuffer;
282 let msg = "Session file not found: " + file.path;
287 interactive("session-save",
288 "Save the current session.",
290 session_write(make_file(yield _session_prompt_file(I)),
294 interactive("session-load-window-new",
295 "Load a session in a new window.",
297 let file = make_file(yield _session_prompt_file(I));
299 _session_file_not_found(I, file);
301 session_load_window_new(session_read(file));
304 interactive("session-load-window-current",
305 "Load a session in new buffers in the current window.",
307 let file = make_file(yield _session_prompt_file(I));
309 _session_file_not_found(I, file);
311 session_load_window_current(session_read(file), I.window);
314 interactive("session-load-window-current-replace",
315 "Replace all buffers in the current window with buffers "+
316 "in the saved session.",
318 let file = make_file(yield _session_prompt_file(I));
320 _session_file_not_found(I, file);
322 session_load_window_current_replace(session_read(file),
326 interactive("session-remove",
327 "Remove a session file.",
329 let file = make_file(yield _session_prompt_file(I));
331 _session_file_not_found(I, file);
333 session_remove(file);
337 //// Auto-save sessions. ////
340 define_variable("session_auto_save_file", "auto-save",
341 "Default filename for the auto-save session.");
343 define_variable("session_auto_save_auto_load", false,
344 'Whether to load the auto-saved session when the browser is started. '+
345 'May be true, false, or "prompt".');
347 function session_auto_save_load_window_new () {
348 session_load_window_new(_session_auto_save_cached);
351 function session_auto_save_load_window_current (window) {
352 session_load_window_current(_session_auto_save_cached, window);
355 function session_auto_save_load_window_current_replace (window) {
356 session_load_window_current_replace(_session_auto_save_cached, window);
359 define_variable("session_auto_save_auto_load_fn",
361 "Function to be called to load the auto-saved session at start-up " +
362 "when URLs are given on the command-line. May be " +
363 "session_auto_save_load_window_new, " +
364 "session_auto_save_load_window_current, or null. If null, the" +
365 "session will not be auto-loaded when URLs are given.");
368 // undefined - we have not tried to cache the auto-save.
369 // null - we have tried to cache the auto-save, but it didn't exist.
370 // object - the cached session object for the auto-save.
371 let _session_auto_save_cached = undefined;
373 let _session_auto_save_file_get = function () {
374 if (session_auto_save_file instanceof Ci.nsIFile)
375 return session_auto_save_file;
376 let f = session_dir.clone();
377 f.append(session_auto_save_file);
381 function session_auto_save_save () {
382 let f = _session_auto_save_file_get();
383 let s = session_get();
390 function session_auto_save_remove () {
391 let f = _session_auto_save_file_get();
396 let _session_auto_save_auto_load = function (user_gave_urls) {
397 if (! session_auto_save_auto_load)
399 if (! _session_auto_save_cached) {
400 _session_file_not_found(null, _session_auto_save_file_get());
404 let window = get_recent_conkeror_window();
405 if (session_auto_save_auto_load == true)
407 else if (session_auto_save_auto_load == "prompt" && !user_gave_urls) {
408 do_load = (yield window.minibuffer.read_single_character_option(
409 $prompt = "Load auto-saved session? (y/n)",
410 $options = ["y", "n"]
413 throw new Error("Invalid value for session_auto_save_auto_load: " +
414 session_auto_save_auto_load);
417 if (user_gave_urls) {
418 if (session_auto_save_auto_load_fn)
419 session_auto_save_auto_load_fn(window);
421 session_auto_save_load_window_current_replace(window);
424 interactive("session-auto-save-load-window-new",
425 "Load the auto-save session in a new window.",
427 if (_session_auto_save_cached == null)
428 _session_file_not_found(I, _session_auto_save_file_get());
430 session_auto_save_load_window_new();
433 interactive("session-auto-save-load-window-current",
434 "Load the auto-save session in new buffers in the current window.",
436 if (_session_auto_save_cached == null)
437 _session_file_not_found(I, _session_auto_save_file_get());
439 session_auto_save_load_window_current(I.window);
442 interactive("session-auto-save-load-window-current-replace",
443 "Replace all buffers in the current window with buffers in the "+
444 "auto-saved session.",
446 if (_session_auto_save_cached == null)
447 _session_file_not_found(I, _session_auto_save_file_get());
449 session_auto_save_load_window_current_replace(I.window);
452 interactive("session-auto-save-remove",
453 "Remove the auto-save session",
454 session_auto_save_remove);
457 //// auto-save-session-mode ////
460 let _session_auto_save_mode_bootstrap = function (b) {
461 remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
462 add_hook("create_buffer_hook", session_auto_save_save);
463 add_hook("kill_buffer_hook", session_auto_save_save);
464 add_hook("move_buffer_hook", session_auto_save_save);
465 add_hook("content_buffer_location_change_hook", session_auto_save_save);
466 add_hook("select_buffer_hook", session_auto_save_save);
467 let user_gave_urls = false;
468 for (let i = 0; i < command_line.length; ++i) {
469 if (command_line[i][0] != '-') {
470 user_gave_urls = true;
474 co_call(_session_auto_save_auto_load(user_gave_urls));
477 let _session_auto_save_mode_enable = function () {
478 if (_session_auto_save_cached == undefined) {
479 let f = _session_auto_save_file_get();
480 _session_auto_save_cached = f.exists() ? session_read(f) : null;
482 if (conkeror_started) {
483 add_hook("create_buffer_hook", session_auto_save_save);
484 add_hook("kill_buffer_hook", session_auto_save_save);
485 add_hook("move_buffer_hook", session_auto_save_save);
486 add_hook("content_buffer_location_change_hook", session_auto_save_save);
487 add_hook("select_buffer_hook", session_auto_save_save);
489 add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
492 let _session_auto_save_mode_disable = function () {
493 remove_hook("create_buffer_hook", session_auto_save_save);
494 remove_hook("kill_buffer_hook", session_auto_save_save);
495 remove_hook("move_buffer_hook", session_auto_save_save);
496 remove_hook("content_buffer_location_change_hook", session_auto_save_save);
497 remove_hook("select_buffer_hook", session_auto_save_save);
499 remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
502 define_global_mode("session_auto_save_mode",
503 _session_auto_save_mode_enable,
504 _session_auto_save_mode_disable);
506 session_auto_save_mode(true);