diff options
Diffstat (limited to 'browser/components/extensions/parent/ext-sessions.js')
-rw-r--r-- | browser/components/extensions/parent/ext-sessions.js | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/browser/components/extensions/parent/ext-sessions.js b/browser/components/extensions/parent/ext-sessions.js new file mode 100644 index 0000000000..ace6bf87be --- /dev/null +++ b/browser/components/extensions/parent/ext-sessions.js @@ -0,0 +1,305 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var { ExtensionError, promiseObserved } = ExtensionUtils; + +ChromeUtils.defineESModuleGetters(this, { + AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs", + SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", +}); + +const SS_ON_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed"; + +const getRecentlyClosed = (maxResults, extension) => { + let recentlyClosed = []; + + // Get closed windows + // Closed private windows are not stored in sessionstore, we do + // not need to check access for that. + let closedWindowData = SessionStore.getClosedWindowData(); + for (let window of closedWindowData) { + recentlyClosed.push({ + lastModified: window.closedAt, + window: Window.convertFromSessionStoreClosedData(extension, window), + }); + } + + // Get closed tabs + // Private closed tabs are in sessionstore if the owning window is still open . + for (let window of windowTracker.browserWindows()) { + if (!extension.canAccessWindow(window)) { + continue; + } + let closedTabData = SessionStore.getClosedTabDataForWindow(window); + for (let tab of closedTabData) { + recentlyClosed.push({ + lastModified: tab.closedAt, + tab: Tab.convertFromSessionStoreClosedData(extension, tab, window), + }); + } + } + + // Sort windows and tabs + recentlyClosed.sort((a, b) => b.lastModified - a.lastModified); + return recentlyClosed.slice(0, maxResults); +}; + +const createSession = async function createSession( + restored, + extension, + sessionId +) { + if (!restored) { + throw new ExtensionError( + `Could not restore object using sessionId ${sessionId}.` + ); + } + let sessionObj = { lastModified: Date.now() }; + if (restored.isChromeWindow) { + await promiseObserved( + "sessionstore-single-window-restored", + subject => subject == restored + ); + sessionObj.window = extension.windowManager.convert(restored, { + populate: true, + }); + return sessionObj; + } + sessionObj.tab = extension.tabManager.convert(restored); + return sessionObj; +}; + +const getEncodedKey = function getEncodedKey(extensionId, key) { + // Throw if using a temporary extension id. + if (AddonManagerPrivate.isTemporaryInstallID(extensionId)) { + let message = + "Sessions API storage methods will not work with a temporary addon ID. " + + "Please add an explicit addon ID to your manifest."; + throw new ExtensionError(message); + } + + return `extension:${extensionId}:${key}`; +}; + +this.sessions = class extends ExtensionAPIPersistent { + PERSISTENT_EVENTS = { + onChanged({ fire }) { + let observer = () => { + fire.async(); + }; + + Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED); + return { + unregister() { + Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED); + }, + convert(_fire) { + fire = _fire; + }, + }; + }, + }; + + getAPI(context) { + let { extension } = context; + + function getTabParams(key, id) { + let encodedKey = getEncodedKey(extension.id, key); + let tab = tabTracker.getTab(id); + if (!context.canAccessWindow(tab.ownerGlobal)) { + throw new ExtensionError(`Invalid tab ID: ${id}`); + } + return { encodedKey, tab }; + } + + function getWindowParams(key, id) { + let encodedKey = getEncodedKey(extension.id, key); + let win = windowTracker.getWindow(id, context); + return { encodedKey, win }; + } + + function getClosedIdFromSessionId(sessionId) { + // sessionId is a string, but internally closedId values are integers. + // convertFromSessionStoreClosedData in ext-browser.js does the opposite conversion. + let closedId = parseInt(sessionId, 10); + if (Number.isInteger(closedId)) { + return closedId; + } + throw new ExtensionError(`Invalid sessionId: ${sessionId}.`); + } + + return { + sessions: { + async getRecentlyClosed(filter) { + await SessionStore.promiseInitialized; + let maxResults = + filter.maxResults == undefined + ? this.MAX_SESSION_RESULTS + : filter.maxResults; + return getRecentlyClosed(maxResults, extension); + }, + + async forgetClosedTab(windowId, sessionId) { + await SessionStore.promiseInitialized; + let window = windowTracker.getWindow(windowId, context); + let closedTabData = SessionStore.getClosedTabDataForWindow(window); + let closedId = getClosedIdFromSessionId(sessionId); + + let closedTabIndex = closedTabData.findIndex(closedTab => { + return closedTab.closedId === closedId; + }); + + if (closedTabIndex < 0) { + throw new ExtensionError( + `Could not find closed tab using sessionId ${sessionId}.` + ); + } + + SessionStore.forgetClosedTab(window, closedTabIndex); + }, + + async forgetClosedWindow(sessionId) { + await SessionStore.promiseInitialized; + let closedWindowData = SessionStore.getClosedWindowData(); + let closedId = getClosedIdFromSessionId(sessionId); + let closedWindowIndex = closedWindowData.findIndex(closedWindow => { + return closedWindow.closedId === closedId; + }); + + if (closedWindowIndex < 0) { + throw new ExtensionError( + `Could not find closed window using sessionId ${sessionId}.` + ); + } + + SessionStore.forgetClosedWindow(closedWindowIndex); + }, + + async restore(sessionId) { + await SessionStore.promiseInitialized; + let session; + let closedId; + if (sessionId) { + closedId = getClosedIdFromSessionId(sessionId); + } + let targetWindow; + + // closedId is internally represented as an integer and could be 0. + if (closedId !== undefined) { + if (SessionStore.getObjectTypeForClosedId(closedId) == "tab") { + // we want to restore the tab to the original window is was closed from + targetWindow = SessionStore.getWindowForTabClosedId( + closedId, + extension.privateBrowsingAllowed + ); + } + session = SessionStore.undoCloseById( + closedId, + extension.privateBrowsingAllowed, + targetWindow // ignored if we are restoring a window + ); + } else if (SessionStore.lastClosedObjectType == "window") { + // If the most recently closed object is a window, just undo closing the most recent window. + session = SessionStore.undoCloseWindow(0); + } else { + // It is a tab, and we cannot call SessionStore.undoCloseTab without a window, + // so we must find the tab in which case we can just use its closedId. + let recentlyClosedTabs = []; + for (let window of windowTracker.browserWindows()) { + let closedTabData = + SessionStore.getClosedTabDataForWindow(window); + for (let tab of closedTabData) { + recentlyClosedTabs.push(tab); + } + } + + if (recentlyClosedTabs.length) { + // Sort the tabs. + recentlyClosedTabs.sort((a, b) => b.closedAt - a.closedAt); + + // Use the closedId of the most recently closed tab to restore it. + closedId = recentlyClosedTabs[0].closedId; + // we want the tab to be re-opened into the same window it was closed from + targetWindow = SessionStore.getWindowForTabClosedId( + closedId, + extension.privateBrowsingAllowed + ); + session = SessionStore.undoCloseById( + closedId, + extension.privateBrowsingAllowed, + targetWindow + ); + } + } + return createSession(session, extension, closedId); + }, + + setTabValue(tabId, key, value) { + let { tab, encodedKey } = getTabParams(key, tabId); + + SessionStore.setCustomTabValue( + tab, + encodedKey, + JSON.stringify(value) + ); + }, + + async getTabValue(tabId, key) { + let { tab, encodedKey } = getTabParams(key, tabId); + + let value = SessionStore.getCustomTabValue(tab, encodedKey); + if (value) { + return JSON.parse(value); + } + + return undefined; + }, + + removeTabValue(tabId, key) { + let { tab, encodedKey } = getTabParams(key, tabId); + + SessionStore.deleteCustomTabValue(tab, encodedKey); + }, + + setWindowValue(windowId, key, value) { + let { win, encodedKey } = getWindowParams(key, windowId); + + SessionStore.setCustomWindowValue( + win, + encodedKey, + JSON.stringify(value) + ); + }, + + async getWindowValue(windowId, key) { + let { win, encodedKey } = getWindowParams(key, windowId); + + let value = SessionStore.getCustomWindowValue(win, encodedKey); + if (value) { + return JSON.parse(value); + } + + return undefined; + }, + + removeWindowValue(windowId, key) { + let { win, encodedKey } = getWindowParams(key, windowId); + + SessionStore.deleteCustomWindowValue(win, encodedKey); + }, + + onChanged: new EventManager({ + context, + module: "sessions", + event: "onChanged", + extensionApi: this, + }).api(), + }, + }; + } +}; |