User:Zeniko/Toolkit SessionStore Proposal

From MozillaWiki
Jump to: navigation, search

This is a proposal for generalizing Firefox's Session Restore component for all Toolkit applications.

Rationale

As of now, Session Restore is very browser specific. It doesn't (allow to) handle neither utility windows such as the Download Manager nor other applications such as Thunderbird. Factoring out the generally useful bits of the nsSessionStore component into a new mozISessionStore component living in Toolkit should make implementing crash recovering and session resuming simpler in all kinds of applications.

Overview

The non-browser specific bits of nsSessionStore mostly handle the update timing and a few general properties of the (browser) windows. This functionality will be exposed to applications/extensions through one single new interface: #mozISessionStore.

By itself, the new mozSessionStore component will track for all windows that opt-in:

  • URI (needed for identification/restoration)
  • dimensions
  • name and features (as used when opening the window)
  • owner (that one's AFAIK not serializable yet)
  • virtual desktop (not yet exposed through DOM or XPCOM, either)

After a crash, all windows will then be (offered to be) restored to their correct place, but probably with default content. If an application/extension wants further data to be tracked, it can either associate that data with the window through the mozISessionStore interface - or it can use a data provider (see #mozISessionDataProvider) for dynamically updating a more complex state.

Interfaces

mozISessionStore

This core interface will be implemented by the "@mozilla.org/toolkit/sessionstore;1" component (mozSessionStore.js). It mainly provides methods for accessing/restoring session states from extensions and for integration into an application's UI.

interface mozISessionStore : nsISupports
{
	// Returns the current application state serialized as a JSON string,
	// resp. restores a session from such a state, closing all open windows
	// (unless they've been stickied)
	AString getApplicationState();
	void setApplicationState(in AString aState);

	// Allows to manually add/remove session data providers - which is
	// mostly needed for components and modules
	void addDataProvider(in mozISessionDataProvider aDataProvider);
	void removeDataProvider(in mozISessionDataProvider aDataProvider);

	// Retrieves/restores the state of a single window as far as data is available
	// (passing in |null| to setWindowState will create a new window)
	AString getWindowState(in nsIDOMWindow aWindow);
	nsIDOMWindow setWindowState(in nsIDOMWindow aWindow, in AString aState);

	// Allows extensions to annotate windows without having to implement
	// their own mozISessionDataProvider
	AString getWindowValue(in nsIDOMWindow aWindow, in AString aKey);
	void setWindowValue(in nsIDOMWindow aWindow, in AString aKey, in AString aStringValue);
	void deleteWindowValue(in nsIDOMWindow aWindow, in AString aKey);

 	// Gets/sets window session specific flags (flags can be combined)
 	// (these flags can also be set on a window's root element)
 	unsinged long getWindowFlags(in nsIDOMWindow aWindow);
	void setWindowFlags(in nsIDOMWindow aWindow, in unsinged long aFlags);
	// Tracked windows will be restored after restarts/crashes
	// (this flag will be set implicitly for windows that implement a data provider)
	const long WINDOW_TRACK = 1;
	// Stickied windows won't be closed by setApplicationState
	// (windows not being tracked will be sticky by default)
	const long WINDOW_STICKY = 2;

	// File listeners can modify the data before it's used at startup and
	// right before it's written to disk (allowing for encryption, etc.).
	// (file listeners added atStart are less likely to encounter garbled data)
	void addFileListener(in mozISessionFileListener aListener, in boolean aAtStart);
	void removeFileListener(in mozISessionFileListener aListener);

	// Requests session data to be updated and written to disk
	void scheduleSave();

	// Restarts collecting all data from scratch and instantly updates the file
	void clearPrivateData();

	// Command line handlers shouldn't open a default window when startupState isn't 0
	readonly attribute long startupState;
	const long STATE_NORMAL = 0;
	const long STATE_RESUMING = 1;
	const long STATE_RECOVERING = 2;
};

mozISessionDataProvider

Windows and components wanting to save session data will have to implement this interface. Components also have to register themselves with mozISessionStore. OTOH if an object named WindowStore present on the global window object implements this interfaces, it's implicitly used.

interface mozISessionDataProvider : nsISupports
{
	// Data serialized as a string (should be JSON); getting this should update
	// the hasChanged attribute, setting it should trigger state restoration
	attribute AString data;

	// Indicates mozISessionStore whether updating data is needed at all
	readonly attribute boolean hasChanged;

	// The ID is used for re-identifying a provider when updating state data
	// and when restoring a session (can be an empty string for implicit data providers)
	readonly attribute AString ID;
}

mozISessionFileListener

File listeners can be used e.g. for encrypting/decrypting session data or for sanitizing data before it hits the disk.

interface mozISessionFileListener : nsISupports
{
	// Process the serialized state and return it for usage/writing to disk
	// (returning an empty string will abort session restoring/file writing)
	aString processRead(in AString aState);
	aString processWrite(in AString aState);
}

mozISessionRecoveryHandler

Recovery handler are responsible for UI for the case of a crash. The mozSessionStore component as shipped with Toolkit will use a simple prompt for this (as Firefox currently does), while e.g. Firefox might use its own recovery handler for displaying an error page instead.

interface mozISessionRecoveryHandler : nsISupports
{
	// Handle the crashed session state in an appropriate way and
	// return whether recovering should continue
	boolean recoverState(in AString aState);
}

mozISessionTabbedDataProvider

Windows offering tabbed interfaces can implement this interface for offering extensions a predictable API for tab handling.

interface mozISessionTabbedDataProvider : mozISessionDataProvider
{
	// cf. mozISessionStore::getWindowState / mozISessionStore::setWindowState
	AString getTabState(in nsIDOMNode aTab);
	nsIDOMNode setTabState(in nsIDOMNode aTab, in AString aState);

	// API stub for implementing undo-close-tab functionality
	AString getClosedTabData();
	void undoCloseTab(in unsigned long aIndex);

	// Clone a tab as thoroughly as possible (ignoring privacy settings)
	nsIDOMNode duplicateTab(in nsIDOMNode aTab);

	// cf. mozISessionStore::getWindowValue, et al.
	AString getTabValue(in nsIDOMNode aTab, in AString aKey);
	void setTabValue(in nsIDOMNode aTab, in AString aKey, in AString aStringValue);
	void deleteTabValue(in nsIDOMNode aTab, in AString aKey);

	// Persists a specific attribute on all tabs for this data provider
	void persistTabAttribute(in AString aName);
}

Note: This interface might not be shipped with Toolkit, as it's not required for mozSessionStore to work. Shipping it nonetheless might help extensions, though - else it'll probably be included at least in Firefox.

Settings

A mozISessionStore implementation could use the following preferences:

Preference name Default value Description
toolkit.sessionstore.interval 10000 The minimal time in milliseconds between two state updates.
toolkit.sessionstore.crashrecovery true Whether to restore crashed sessions. Privacy sensitive users should disable this setting.
toolkit.sessionstore.recovery_handler "@mozilla.org/toolkit/sessionstore;1" Name of the component implementing mozISessionRecoveryHandler. Applications/extensions not wanting the default recovery prompt can hook in here.
toolkit.sessionstore.resume_session 0 The start-up behavior, 0 meaning "don't restore the session", 1 meaning "restore the session once" (e.g. after an update/restart) and 2 meaning "always resume the session"

Note: Not registering any data provider will effectively disable the component (except for a "domwindowopened" observer waiting for windows registering themselves implicitly), hence there should be no need for toolkit.sessionstore.enabled.

Notifications and Events

A mozISessionStore implementation should also dispatch the following notifications:

sessionstore-restoring A session is about to be restored (dispatched before all windows are being closed)
sessionstore-restored All windows/data providers belonging to a session have been restored (their content might still be loading, though)
sessionstore-updating All session data is about to be updated (data being used by several data providers should be updated now - e.g. cookies when saved per window as Firefox currently does)
sessionstore-updated Session data has been written to disk (data collaboratively used can be discarded now)

Furthermore the WindowRestored event should be dispatched on all restored windows' root element, notifying extensions that setWindowValue now might return new values.

Code examples

A Window's Data Provider

A simple data provider which saves/restores the Download Manager's search state.

// These two modules aren't always needed
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/JSON.jsm");

// Implicit data provider
var WindowStore = {
	QueryInterface: XPCOMUtils.generateQI([Components.interfaces.mozISessionDataProvider]),

	ID:         "",   // data provider will be identified through the window's URI only
	hasChanged: true, // we always dynamically update the value (no caching implemented)

	get data() {
		return JSON.toString({
			search: document.getElementById("searchbar").value
		});
	},
	set data(aValue) {
		document.getElementById("searchbar").value = JSON.fromString(aValue).search;
		buildDownloadList();
	}
};

Minimal Code For Having a Window Restored

For simplicity, flags for a XUL window/dialog can also be set through a DOM attribute, e.g.:

<window sessionstore="track">
</window>

These flags are however not dynamically updated - instead they're just read once onLoad and then handled internally. They will then have to be changed through mozISessionStore::setWindowFlags.

Future of Firefox's Session Restore

nsSessionStore will still offer the same API, although most of it will be proxy methods for either the here proposed mozSessionStore component or the browser windows' mozISessionTabbedDataProvider implementations. These latter will contain most code related to actually saving/restoring the browser's session state.

nsSessionStore will further implement it's own mozISessionRecoveryHandler in order to offer an error page inside a browser tab from which tabs/windows of the crashed session can be unselected before the session is restored.

nsSessionStartup will go away, as mozSessionStore will take over most of it and nsSessionStore the rest. The only reason for nsSessionStartup was start-up performance - and with much code being moved into (an overlay to) browser.xul, there's no further reason for component splitting.

End users should however only note that utility windows such as the Downloads Manager are now restored as well.