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
, 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.");
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 var session_token
= 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 var session_get
= 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_buffer_access_order
) {
75 url
: b
.display_uri_string
,
76 access_index
: w
.buffers
.buffer_history
.indexOf(b
)
79 buffers
.push(b
.display_uri_string
);
83 if (buffers
.length
== 0)
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.
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.");
101 var opener
= new session_token();
103 let bi
= buffer_idx
!= undefined ?
104 buffer_idx
: window
.buffers
.count
;
106 // first kill special buffers slated for recycling.
108 let b
, i
= (bi
== 0 ? 1 : bi
),
110 while ((b
= window
.buffers
.get_buffer(i
))) {
111 if (b
instanceof content_buffer
) {
115 kill_buffer(b
, true);
118 (b
= window
.buffers
.get_buffer(0)) &&
119 !(b
instanceof content_buffer
))
122 create_buffer(window
,
123 buffer_creator(content_buffer
,
125 OPEN_NEW_BUFFER_BACKGROUND
);
126 kill_buffer(b
, true);
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
);
135 let history
= b
.web_navigation
.sessionHistory
;
136 history
.PurgeHistory(history
.count
);
138 b
.load(session
[s
][i
].url
);
140 let c
= buffer_creator(content_buffer
,
141 $load
= session
[s
][i
].url
,
143 $position
= buffer_position_end
);
144 create_buffer(window
, c
, OPEN_NEW_BUFFER_BACKGROUND
);
147 for (let b
= window
.buffers
.get_buffer(bi
); b
;
148 b
= window
.buffers
.get_buffer(bi
))
150 kill_buffer(b
, true);
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
);
166 switch_to_buffer(window
, window
.buffers
.buffer_history
[0]);
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
,
177 $position
= buffer_position_end
);
178 create_buffer(window
, c
, OPEN_NEW_BUFFER_BACKGROUND
);
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
);
194 switch_to_buffer(window
, window
.buffers
.buffer_history
[0]);
200 for (; session
[s
] != undefined; ++s
) {
201 let w
= make_window(buffer_creator(content_buffer
,
202 $load
= session
[s
][0].url
,
204 add_hook
.call(w
, "window_initialize_late_hook",
205 make_init_hook(session
[s
]));
210 * session_load_window_new loads the given session into new windows.
212 var session_load_window_new
= function session_load_window_new (session
) {
213 session_load(session
);
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.
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
);
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.
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);
239 * session_write writes the given session to the file given by path.
241 var session_write
= function session_write (path
, session
) {
242 if (! (path
instanceof Ci
.nsIFile
))
243 path
= make_file(path
);
245 session
= session_get();
246 write_text_file(path
, JSON
.stringify(session
));
250 * session_read reads session data from the given file path,
251 * and returns a decoded session structure.
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
));
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
] };
267 * session_remove deletes the given session file.
269 var session_remove
= function session_remove (path
) {
270 if (! (path
instanceof Ci
.nsIFile
))
271 path
= make_file(path
);
275 let _session_prompt_file = function (I
) {
277 yield I
.minibuffer
.read_file_path(
278 $prompt
= "Session file:",
279 $initial_value
= session_dir
.path
,
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
;
292 interactive("session-save",
293 "Save the current session.",
295 session_write(make_file(yield _session_prompt_file(I
)),
299 interactive("session-load-window-new",
300 "Load a session in a new window.",
302 let file
= make_file(yield _session_prompt_file(I
));
304 _session_file_not_found(I
, file
);
306 session_load_window_new(session_read(file
));
309 interactive("session-load-window-current",
310 "Load a session in new buffers in the current window.",
312 let file
= make_file(yield _session_prompt_file(I
));
314 _session_file_not_found(I
, file
);
316 session_load_window_current(session_read(file
), I
.window
);
319 interactive("session-load-window-current-replace",
320 "Replace all buffers in the current window with buffers "+
321 "in the saved session.",
323 let file
= make_file(yield _session_prompt_file(I
));
325 _session_file_not_found(I
, file
);
327 session_load_window_current_replace(session_read(file
),
331 interactive("session-remove",
332 "Remove a session file.",
334 let file
= make_file(yield _session_prompt_file(I
));
336 _session_file_not_found(I
, file
);
338 session_remove(file
);
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
);
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
);
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
);
367 define_variable("session_auto_save_auto_load_fn",
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.");
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
);
389 var session_auto_save_save
= function session_auto_save_save () {
390 let f
= _session_auto_save_file_get();
391 let s
= session_get();
398 var session_auto_save_remove
= function session_auto_save_remove () {
399 let f
= _session_auto_save_file_get();
404 let _session_auto_save_auto_load = function (user_gave_urls
) {
405 if (! session_auto_save_auto_load
)
407 if (! _session_auto_save_cached
) {
408 _session_file_not_found(null, _session_auto_save_file_get());
412 let window
= get_recent_conkeror_window();
413 if (session_auto_save_auto_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"]
421 throw new Error("Invalid value for session_auto_save_auto_load: " +
422 session_auto_save_auto_load
);
425 if (user_gave_urls
) {
426 if (session_auto_save_auto_load_fn
)
427 session_auto_save_auto_load_fn(window
);
429 session_auto_save_load_window_current_replace(window
);
432 interactive("session-auto-save-load-window-new",
433 "Load the auto-save session in a new window.",
435 if (_session_auto_save_cached
== null)
436 _session_file_not_found(I
, _session_auto_save_file_get());
438 session_auto_save_load_window_new();
441 interactive("session-auto-save-load-window-current",
442 "Load the auto-save session in new buffers in the current window.",
444 if (_session_auto_save_cached
== null)
445 _session_file_not_found(I
, _session_auto_save_file_get());
447 session_auto_save_load_window_current(I
.window
);
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.",
454 if (_session_auto_save_cached
== null)
455 _session_file_not_found(I
, _session_auto_save_file_get());
457 session_auto_save_load_window_current_replace(I
.window
);
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;
482 spawn(_session_auto_save_auto_load(user_gave_urls
));
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;
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
);
497 add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap
);
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
);
507 remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap
);
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);