ext-sessions.js (9993B)
1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2 /* vim: set sts=2 sw=2 et tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 "use strict"; 8 9 var { ExtensionError, promiseObserved } = ExtensionUtils; 10 11 ChromeUtils.defineESModuleGetters(this, { 12 AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs", 13 SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", 14 }); 15 16 const SS_ON_CLOSED_OBJECTS_CHANGED = "sessionstore-closed-objects-changed"; 17 18 const getRecentlyClosed = (maxResults, extension) => { 19 let recentlyClosed = []; 20 21 // Get closed windows 22 // Closed private windows are not stored in sessionstore, we do 23 // not need to check access for that. 24 let closedWindowData = SessionStore.getClosedWindowData(); 25 for (let window of closedWindowData) { 26 recentlyClosed.push({ 27 lastModified: window.closedAt, 28 window: Window.convertFromSessionStoreClosedData(extension, window), 29 }); 30 } 31 32 // Get closed tabs 33 // Private closed tabs are in sessionstore if the owning window is still open . 34 for (let window of windowTracker.browserWindows()) { 35 if (!extension.canAccessWindow(window)) { 36 continue; 37 } 38 let closedTabData = SessionStore.getClosedTabDataForWindow(window); 39 for (let tab of closedTabData) { 40 recentlyClosed.push({ 41 lastModified: tab.closedAt, 42 tab: Tab.convertFromSessionStoreClosedData(extension, tab, window), 43 }); 44 } 45 } 46 47 // Sort windows and tabs 48 recentlyClosed.sort((a, b) => b.lastModified - a.lastModified); 49 return recentlyClosed.slice(0, maxResults); 50 }; 51 52 const createSession = async function createSession( 53 restored, 54 extension, 55 sessionId 56 ) { 57 if (!restored) { 58 throw new ExtensionError( 59 `Could not restore object using sessionId ${sessionId}.` 60 ); 61 } 62 let sessionObj = { lastModified: Date.now() }; 63 if (restored.isChromeWindow) { 64 await promiseObserved( 65 "sessionstore-single-window-restored", 66 subject => subject == restored 67 ); 68 sessionObj.window = extension.windowManager.convert(restored, { 69 populate: true, 70 }); 71 return sessionObj; 72 } 73 sessionObj.tab = extension.tabManager.convert(restored); 74 return sessionObj; 75 }; 76 77 const getEncodedKey = function getEncodedKey(extensionId, key) { 78 // Throw if using a temporary extension id. 79 if (AddonManagerPrivate.isTemporaryInstallID(extensionId)) { 80 let message = 81 "Sessions API storage methods will not work with a temporary addon ID. " + 82 "Please add an explicit addon ID to your manifest."; 83 throw new ExtensionError(message); 84 } 85 86 return `extension:${extensionId}:${key}`; 87 }; 88 89 this.sessions = class extends ExtensionAPIPersistent { 90 PERSISTENT_EVENTS = { 91 onChanged({ fire }) { 92 let observer = () => { 93 fire.async(); 94 }; 95 96 Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED); 97 return { 98 unregister() { 99 Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED); 100 }, 101 convert(_fire) { 102 fire = _fire; 103 }, 104 }; 105 }, 106 }; 107 108 getAPI(context) { 109 let { extension } = context; 110 111 function getTabParams(key, id) { 112 let encodedKey = getEncodedKey(extension.id, key); 113 let tab = tabTracker.getTab(id); 114 if (!context.canAccessWindow(tab.ownerGlobal)) { 115 throw new ExtensionError(`Invalid tab ID: ${id}`); 116 } 117 return { encodedKey, tab }; 118 } 119 120 function getWindowParams(key, id) { 121 let encodedKey = getEncodedKey(extension.id, key); 122 let win = windowTracker.getWindow(id, context); 123 return { encodedKey, win }; 124 } 125 126 function getClosedIdFromSessionId(sessionId) { 127 // sessionId is a string, but internally closedId values are integers. 128 // convertFromSessionStoreClosedData in ext-browser.js does the opposite conversion. 129 let closedId = parseInt(sessionId, 10); 130 if (Number.isInteger(closedId)) { 131 return closedId; 132 } 133 throw new ExtensionError(`Invalid sessionId: ${sessionId}.`); 134 } 135 136 return { 137 sessions: { 138 async getRecentlyClosed(filter) { 139 await SessionStore.promiseInitialized; 140 let maxResults = 141 filter.maxResults == undefined 142 ? this.MAX_SESSION_RESULTS 143 : filter.maxResults; 144 return getRecentlyClosed(maxResults, extension); 145 }, 146 147 async forgetClosedTab(windowId, sessionId) { 148 await SessionStore.promiseInitialized; 149 let window = windowTracker.getWindow(windowId, context); 150 let closedTabData = SessionStore.getClosedTabDataForWindow(window); 151 let closedId = getClosedIdFromSessionId(sessionId); 152 153 let closedTabIndex = closedTabData.findIndex(closedTab => { 154 return closedTab.closedId === closedId; 155 }); 156 157 if (closedTabIndex < 0) { 158 throw new ExtensionError( 159 `Could not find closed tab using sessionId ${sessionId}.` 160 ); 161 } 162 163 SessionStore.forgetClosedTab(window, closedTabIndex); 164 }, 165 166 async forgetClosedWindow(sessionId) { 167 await SessionStore.promiseInitialized; 168 let closedWindowData = SessionStore.getClosedWindowData(); 169 let closedId = getClosedIdFromSessionId(sessionId); 170 let closedWindowIndex = closedWindowData.findIndex(closedWindow => { 171 return closedWindow.closedId === closedId; 172 }); 173 174 if (closedWindowIndex < 0) { 175 throw new ExtensionError( 176 `Could not find closed window using sessionId ${sessionId}.` 177 ); 178 } 179 180 SessionStore.forgetClosedWindow(closedWindowIndex); 181 }, 182 183 async restore(sessionId) { 184 await SessionStore.promiseInitialized; 185 let session; 186 let closedId; 187 if (sessionId) { 188 closedId = getClosedIdFromSessionId(sessionId); 189 } 190 let targetWindow; 191 192 // closedId is internally represented as an integer and could be 0. 193 if (closedId !== undefined) { 194 if (SessionStore.getObjectTypeForClosedId(closedId) == "tab") { 195 // we want to restore the tab to the original window is was closed from 196 targetWindow = SessionStore.getWindowForTabClosedId( 197 closedId, 198 extension.privateBrowsingAllowed 199 ); 200 } 201 session = SessionStore.undoCloseById( 202 closedId, 203 extension.privateBrowsingAllowed, 204 targetWindow // ignored if we are restoring a window 205 ); 206 } else if (SessionStore.lastClosedObjectType == "window") { 207 // If the most recently closed object is a window, just undo closing the most recent window. 208 session = SessionStore.undoCloseWindow(0); 209 } else { 210 // It is a tab, and we cannot call SessionStore.undoCloseTab without a window, 211 // so we must find the tab in which case we can just use its closedId. 212 let recentlyClosedTabs = []; 213 for (let window of windowTracker.browserWindows()) { 214 let closedTabData = 215 SessionStore.getClosedTabDataForWindow(window); 216 for (let tab of closedTabData) { 217 recentlyClosedTabs.push(tab); 218 } 219 } 220 221 if (recentlyClosedTabs.length) { 222 // Sort the tabs. 223 recentlyClosedTabs.sort((a, b) => b.closedAt - a.closedAt); 224 225 // Use the closedId of the most recently closed tab to restore it. 226 closedId = recentlyClosedTabs[0].closedId; 227 // we want the tab to be re-opened into the same window it was closed from 228 targetWindow = SessionStore.getWindowForTabClosedId( 229 closedId, 230 extension.privateBrowsingAllowed 231 ); 232 session = SessionStore.undoCloseById( 233 closedId, 234 extension.privateBrowsingAllowed, 235 targetWindow 236 ); 237 } 238 } 239 return createSession(session, extension, closedId); 240 }, 241 242 setTabValue(tabId, key, value) { 243 let { tab, encodedKey } = getTabParams(key, tabId); 244 245 SessionStore.setCustomTabValue( 246 tab, 247 encodedKey, 248 JSON.stringify(value) 249 ); 250 }, 251 252 async getTabValue(tabId, key) { 253 let { tab, encodedKey } = getTabParams(key, tabId); 254 255 let value = SessionStore.getCustomTabValue(tab, encodedKey); 256 if (value) { 257 return JSON.parse(value); 258 } 259 260 return undefined; 261 }, 262 263 removeTabValue(tabId, key) { 264 let { tab, encodedKey } = getTabParams(key, tabId); 265 266 SessionStore.deleteCustomTabValue(tab, encodedKey); 267 }, 268 269 setWindowValue(windowId, key, value) { 270 let { win, encodedKey } = getWindowParams(key, windowId); 271 272 SessionStore.setCustomWindowValue( 273 win, 274 encodedKey, 275 JSON.stringify(value) 276 ); 277 }, 278 279 async getWindowValue(windowId, key) { 280 let { win, encodedKey } = getWindowParams(key, windowId); 281 282 let value = SessionStore.getCustomWindowValue(win, encodedKey); 283 if (value) { 284 return JSON.parse(value); 285 } 286 287 return undefined; 288 }, 289 290 removeWindowValue(windowId, key) { 291 let { win, encodedKey } = getWindowParams(key, windowId); 292 293 SessionStore.deleteCustomWindowValue(win, encodedKey); 294 }, 295 296 onChanged: new EventManager({ 297 context, 298 module: "sessions", 299 event: "onChanged", 300 extensionApi: this, 301 }).api(), 302 }, 303 }; 304 } 305 };