michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["SessionMigration"]; michael@0: michael@0: const Cu = Components.utils; michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); michael@0: Cu.import("resource://gre/modules/Task.jsm", this); michael@0: Cu.import("resource://gre/modules/osfile.jsm", this); michael@0: michael@0: // An encoder to UTF-8. michael@0: XPCOMUtils.defineLazyGetter(this, "gEncoder", function () { michael@0: return new TextEncoder(); michael@0: }); michael@0: michael@0: // A decoder. michael@0: XPCOMUtils.defineLazyGetter(this, "gDecoder", function () { michael@0: return new TextDecoder(); michael@0: }); michael@0: michael@0: let SessionMigrationInternal = { michael@0: /** michael@0: * Convert the original session restore state into a minimal state. It will michael@0: * only contain: michael@0: * - open windows michael@0: * - with tabs michael@0: * - with history entries with only title, url michael@0: * - with pinned state michael@0: * - with tab group info (hidden + group id) michael@0: * - with selected tab info michael@0: * - with selected window info michael@0: * - with tabgroups info michael@0: * michael@0: * The complete state is then wrapped into the "about:welcomeback" page as michael@0: * form field info to be restored when restoring the state. michael@0: */ michael@0: convertState: function(aStateObj) { michael@0: let state = { michael@0: selectedWindow: aStateObj.selectedWindow, michael@0: _closedWindows: [] michael@0: }; michael@0: state.windows = aStateObj.windows.map(function(oldWin) { michael@0: var win = {extData: {}}; michael@0: win.tabs = oldWin.tabs.map(function(oldTab) { michael@0: var tab = {}; michael@0: // Keep only titles and urls for history entries michael@0: tab.entries = oldTab.entries.map(function(entry) { michael@0: return {url: entry.url, title: entry.title}; michael@0: }); michael@0: tab.index = oldTab.index; michael@0: tab.hidden = oldTab.hidden; michael@0: tab.pinned = oldTab.pinned; michael@0: // The tabgroup info is in the extData, so we need to get it out. michael@0: if (oldTab.extData && "tabview-tab" in oldTab.extData) { michael@0: tab.extData = {"tabview-tab": oldTab.extData["tabview-tab"]}; michael@0: } michael@0: return tab; michael@0: }); michael@0: // There are various tabgroup-related attributes that we need to get out michael@0: // of the session restore data for the window, too. michael@0: if (oldWin.extData) { michael@0: for (let k of Object.keys(oldWin.extData)) { michael@0: if (k.startsWith("tabview-")) { michael@0: win.extData[k] = oldWin.extData[k]; michael@0: } michael@0: } michael@0: } michael@0: win.selected = oldWin.selected; michael@0: win._closedTabs = []; michael@0: return win; michael@0: }); michael@0: let wrappedState = { michael@0: url: "about:welcomeback", michael@0: formdata: { michael@0: id: {"sessionData": state}, michael@0: xpath: {} michael@0: } michael@0: }; michael@0: return {windows: [{tabs: [{entries: [wrappedState]}]}]}; michael@0: }, michael@0: /** michael@0: * Asynchronously read session restore state (JSON) from a path michael@0: */ michael@0: readState: function(aPath) { michael@0: return Task.spawn(function() { michael@0: let bytes = yield OS.File.read(aPath); michael@0: let text = gDecoder.decode(bytes); michael@0: let state = JSON.parse(text); michael@0: throw new Task.Result(state); michael@0: }); michael@0: }, michael@0: /** michael@0: * Asynchronously write session restore state as JSON to a path michael@0: */ michael@0: writeState: function(aPath, aState) { michael@0: let bytes = gEncoder.encode(JSON.stringify(aState)); michael@0: return OS.File.writeAtomic(aPath, bytes, {tmpPath: aPath + ".tmp"}); michael@0: } michael@0: } michael@0: michael@0: let SessionMigration = { michael@0: /** michael@0: * Migrate a limited set of session data from one path to another. michael@0: */ michael@0: migrate: function(aFromPath, aToPath) { michael@0: return Task.spawn(function() { michael@0: let inState = yield SessionMigrationInternal.readState(aFromPath); michael@0: let outState = SessionMigrationInternal.convertState(inState); michael@0: // Unfortunately, we can't use SessionStore's own SessionFile to michael@0: // write out the data because it has a dependency on the profile dir michael@0: // being known. When the migration runs, there is no guarantee that michael@0: // that's true. michael@0: yield SessionMigrationInternal.writeState(aToPath, outState); michael@0: }); michael@0: } michael@0: };