summaryrefslogtreecommitdiffstats
path: root/browser/components/extensions/parent/ext-sessions.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/extensions/parent/ext-sessions.js')
-rw-r--r--browser/components/extensions/parent/ext-sessions.js305
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(),
+ },
+ };
+ }
+};