[Conkeror] [PATCH] Session management: restore saved buffers from a JSON file.
David House
dmhouse at gmail.com
Thu Sep 4 05:55:59 PDT 2008
This is the first round of session management support. It's not
feature-complete, and it's not bug free, but it's just about useful, so I
thought it was about time to ship some code. To restore the buffers at startup
that were open last time (in the correct windows), add the following line to
your RC file:
restore_session();
You can also use M-x restore-session at any time to load the stored buffers
(this does nothing to your currently-open buffers). At the moment, the following
restrictions are in place:
1. You must use conkeror -batch to start conkeror, otherwise you will get an
additional window containing the homepage.
2. By virtue of the above, opening conkeror via a remoting function (i.e.
clicking on a link in some other application to start conkeror) will open
your saved buffers and the new buffer in different windows.
3. You must quit conkeror using C-x C-c, rather than through your window
manager, if you want the session to be saved.
Work to be done:
1. Refactor the new modules/json.js to use modules/io.js.
2. Get rid of the code duplication in modules/buffers.js by making
modules/window.js aware of which windows aren't fully initialised.
3. Resolve the above restrictions
4. Add additional features: at a minimum I think we should also save buffer
history (so that `go-back' will work on restored buffers) and minibuffer
history.
---
components/application.js | 2 +
defaults/preferences/default-modules.js | 2 +
modules/buffer.js | 40 +++++++++++++++
modules/json.js | 66 +++++++++++++++++++++++++
modules/session.js | 81 +++++++++++++++++++++++++++++++
modules/window.js | 25 +++++----
6 files changed, 205 insertions(+), 11 deletions(-)
create mode 100644 modules/json.js
create mode 100644 modules/session.js
diff --git a/components/application.js b/components/application.js
index 035bb0e..1ccb844 100644
--- a/components/application.js
+++ b/components/application.js
@@ -33,6 +33,8 @@ application.prototype = {
Cc: Cc,
Ci: Ci,
Cr: Cr,
+ profile_dir: Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile).path,
/* Note: resource://app currently doesn't result in xpcnativewrappers=yes */
module_uri_prefix: "chrome://conkeror-modules/content/",
subscript_loader: Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader),
diff --git a/defaults/preferences/default-modules.js b/defaults/preferences/default-modules.js
index d4147a4..e7b6d5c 100644
--- a/defaults/preferences/default-modules.js
+++ b/defaults/preferences/default-modules.js
@@ -44,6 +44,8 @@ pref("conkeror.load.daemon", 1);
pref("conkeror.load.favicon", 1); // Enhances tab-bar mode
pref("conkeror.load.tab-bar", 0);
+pref("conkeror.load.session", 1);
+
// Extension support modules
// These modules will automatically skip loading if the associated extension is not enabled.
pref("conkeror.load.extensions/dom-inspector", 1);
diff --git a/modules/buffer.js b/modules/buffer.js
index 98b1f31..f405730 100644
--- a/modules/buffer.js
+++ b/modules/buffer.js
@@ -51,6 +51,46 @@ function buffer_creator(type) {
}
}
+/* NOTE this code is horifically duplicated from
+create_buffer_in_current_window. The correct way to fix this is to make
+window.js be aware of the currently uninitialised windows, which isn't too hard
+but I just haven't done it yet. */
+var mbc_queued_buffer_creators = {};
+function mbc_process_queued_buffer_creators(window) {
+ for (var i = 0; i < mbc_queued_buffer_creators[window.tag].length; i++) {
+ mbc_queued_buffer_creators[window.tag][i](window, null);
+ }
+ mbc_queued_buffer_creators[window.tag] = null;
+}
+
+/**
+ * A buffer creator to make many buffers at once. Example:
+ *
+ * var cr = many_buffers_creator(
+ * [buffer_creator(content_buffer, $load = "http://google.com"),
+ * buffer_creator(content_buffer, $load = "http://reddit.com")]);
+ * make_window(cr);
+ */
+function multiple_buffers_creator(creators) {
+ return function (window, element) {
+ // Conkeror must have at least one buffer, so load the homepage if
+ // nothing else is specified
+ if (creators.length == 0)
+ return new content_buffer(window, element, $load = homepage);
+
+ for (var i = 0; i < creators.length; i++) {
+ if (typeof mbc_queued_buffer_creators[window.tag] != "undefined") {
+ mbc_queued_buffer_creators[window.tag].push(creators[i]);
+ } else {
+ mbc_queued_buffer_creators[window.tag] = [];
+ window.buffers.current = creators[i](window, element);
+ add_hook.call(window, "window_initialize_late_hook",
+ mbc_process_queued_buffer_creators);
+ }
+ }
+ }
+}
+
define_variable("allow_browser_window_close", true,
"If this is set to true, if a content buffer page calls " +
"window.close() from JavaScript and is not prevented by the " +
diff --git a/modules/json.js b/modules/json.js
new file mode 100644
index 0000000..6ddd5f2
--- /dev/null
+++ b/modules/json.js
@@ -0,0 +1,66 @@
+/** JSON SUPPORT
+ * Utility class for encoding and decoding JSON.
+ */
+
+function json() {
+ this.json = Cc["@mozilla.org/dom/json;1"] .createInstance(Ci.nsIJSON);
+}
+
+/**
+ * Encode obj into JSON, return it.
+ */
+json.prototype.encode = function(obj) {
+ return this.json.encode(obj);
+}
+
+/**
+ * Decode str as a JSON string, and return the resulting object.
+ */
+json.prototype.decode = function(str) {
+ return this.json.decode(str);
+}
+
+/**
+ * Encode obj into JSON, and save it into path.
+ */
+json.prototype.encodeToFile = function(path, obj) {
+ // We should theoretically be able to use nsIJSON.encodeToStream to avoid
+ // doing the UTF encoding ourselves, but I couldn't get it to output
+ // anything other than an empty string.
+ var cos = Cc["@mozilla.org/intl/converter-output-stream;1"]
+ .createInstance(Ci.nsIConverterOutputStream);
+ var os = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ var file = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ os.init(file, -1, -1, 0);
+ cos.init(os, "UTF-8", 0, 0);
+ cos.writeString(this.json.encode(obj));
+ cos.close();
+}
+
+/**
+ * Read the file at path, decode it, and return the resulting object.
+ */
+json.prototype.decodeFromFile = function(path) {
+ var cis = Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Ci.nsIConverterInputStream);
+ var is = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ const replace = Ci .nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
+ var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ is.init(file, -1, 0, 0);
+ cis.init(is, "UTF-8", 1024, replace);
+
+ var str = {};
+ var input = "";
+ while (cis.readString(4096, str) != 0) {
+ input += str.value;
+ }
+ is.close();
+ return this.json.decode(input);
+}
+
+conkeror.json = new json();
\ No newline at end of file
diff --git a/modules/session.js b/modules/session.js
new file mode 100644
index 0000000..af4bbe1
--- /dev/null
+++ b/modules/session.js
@@ -0,0 +1,81 @@
+/** SESSION MANAGEMENT
+ * Writes buffers to disc via JSON at shutdown, and restores them on startup.
+ * If you wish restore the buffers at startup that you had open last time, you
+ * should just need to add the following line to your rc file:
+ *
+ * restore_session();
+ */
+
+require('json.js');
+
+function session_manager(sess_file) {
+ if (typeof sess_file == "undefined")
+ sess_file = profile_dir + "/session.json";
+ this.path = sess_file;
+ this.session = json.decodeFromFile(sess_file);
+}
+
+/**
+ * Fetch an array mapping window tags to lists of buffers
+ */
+session_manager.prototype.get_session = function () {
+ var ar = {};
+ for_each_window(function (w) {
+ ar[w.tag] = {buffers : []};
+ var bl = w.buffers.buffer_list;
+ for (var i = 0; i < bl.length; i++) {
+ if (bl[i].generated) continue;
+ ar[w.tag].buffers[i] = bl[i].browser.contentDocument.location.href;
+ }
+ });
+ return ar;
+}
+
+/**
+ * Encode a session to disk
+ */
+session_manager.prototype.save = function () {
+ json.encodeToFile(this.path, this.get_session());
+}
+
+add_hook("quit_hook",
+ function () { if (typeof session != "undefined") session.save(); });
+
+/**
+ * Restore from the session
+ */
+session_manager.prototype.restore = function () {
+ var creators;
+ for (var tag in session.session) {
+ creators = [];
+ for each (var url in session.session[tag].buffers) {
+ creators.push(buffer_creator(content_buffer, $load = url));
+ }
+ make_window(multiple_buffers_creator(creators), tag);
+ }
+}
+
+/**
+ * The main entry point. Load all the buffers from the session at `sess_file',
+ * using a default of <profile dir>/session.json. Thus, to use session
+ * management, adding the following to your rcfile should be all that's
+ * necessary:
+ *
+ * restore_session()
+ */
+function restore_session(sess_file) {
+ conkeror.session = new session_manager(sess_file);
+ session.restore();
+}
+
+interactive(
+ "restore-session",
+ "Prompt on the minibuffer for a session file to load, then open all the " +
+ "buffers listed in that file. The default path is " + profile_dir +
+ "/session.json.",
+ function (I) {
+ var sess_file = yield I.minibuffer.read_file_path(
+ $prompt = "Session file:",
+ $initial_value = profile_dir + "/session.json");
+ restore_session(sess_file);
+ });
diff --git a/modules/window.js b/modules/window.js
index 518c78e..c45eb08 100644
--- a/modules/window.js
+++ b/modules/window.js
@@ -57,22 +57,26 @@ function generate_new_window_tag(tag)
}
}
-function make_chrome_window(chrome_URI, args)
-{
- return window_watcher.openWindow(null, chrome_URI, "_blank",
+var chrome_uniquify_index = 0
+
+function make_chrome_window(chrome_url, args, tag) {
+ // We append a query guaranteed to be unique to the chrome URL in order to
+ // work around a bug in XULRunner. See http://conkeror.org/UpstreamBugs
+ url_with_query = chrome_url + "?i=" + chrome_uniquify_index;
+ chrome_uniquify_index++;
+ return window_watcher.openWindow(null, url_with_query, "_blank",
"chrome,dialog=no,all,resizable", args);
}
var conkeror_chrome_URI = "chrome://conkeror/content/conkeror.xul";
-function make_window(initial_buffer_creator, tag)
-{
+function make_window(initial_buffer_creator, tag) {
var args = { tag: tag,
initial_buffer_creator: initial_buffer_creator };
- var result = make_chrome_window(conkeror_chrome_URI, null);
- result.args = args;
- make_window_hook.run(result);
- return result;
+ var window = make_chrome_window(conkeror_chrome_URI, null, tag);
+ window.args = args;
+ make_window_hook.run(window);
+ return window;
}
function get_window_by_tag(tag)
@@ -163,8 +167,7 @@ function window_initialize(window)
// Set tag
var tag = null;
- if ('tag' in window.args)
- tag = window.args.tag;
+ if ('tag' in window.args) tag = window.args.tag;
window.tag = generate_new_window_tag(tag);
// Add a getBrowser() function to help certain extensions designed for Firefox work with conkeror
--
1.5.4.3
More information about the Conkeror
mailing list