TabManager.sys.mjs (8254B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs", 9 EventPromise: "chrome://remote/content/shared/Sync.sys.mjs", 10 MobileTabBrowser: "chrome://remote/content/shared/MobileTabBrowser.sys.mjs", 11 UserContextManager: 12 "chrome://remote/content/shared/UserContextManager.sys.mjs", 13 windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs", 14 }); 15 16 class TabManagerClass { 17 /** 18 * Retrieve all the tabs in open browser windows. 19 * 20 * @returns {Array<Tab>} 21 * All the open browser tabs. Will return an empty list if tab browser 22 * is not available or tabs are undefined. 23 */ 24 get allTabs() { 25 return lazy.windowManager.windows.flatMap(win => 26 this.getTabsForWindow(win) 27 ); 28 } 29 30 /** 31 * Get the linked `xul:browser` for the specified tab. 32 * 33 * @param {Tab} tab 34 * The tab whose browser needs to be returned. 35 * 36 * @returns {XULBrowser|null} 37 * The linked browser for the tab or `null` if no browser can be found. 38 */ 39 getBrowserForTab(tab) { 40 return tab?.linkedBrowser ?? null; 41 } 42 43 /** 44 * Retrieve all the browser elements from tabs as contained in open windows. 45 * 46 * By default excludes browsers for unloaded tabs. 47 * 48 * @param {object=} options 49 * @param {boolean=} options.unloaded 50 * Pass true to also retrieve browsers for unloaded tabs. Defaults to 51 * false. 52 * 53 * @returns {Array<XULBrowser>} 54 * All the found <xul:browser>s. Will return an empty array if 55 * no windows and tabs can be found. 56 */ 57 getBrowsers(options = {}) { 58 const { unloaded = false } = options; 59 60 return this.allTabs 61 .map(tab => this.getBrowserForTab(tab)) 62 .filter(browser => { 63 return ( 64 browser !== null && 65 (unloaded || 66 this.isValidCanonicalBrowsingContext(browser.browsingContext)) 67 ); 68 }); 69 } 70 71 /** 72 * Return the tab browser for the specified chrome window. 73 * 74 * @param {ChromeWindow} win 75 * Window whose <code>tabbrowser</code> needs to be accessed. 76 * 77 * @returns {TabBrowser|null} 78 * Tab browser or `null` if it's not a browser window. 79 */ 80 getTabBrowser(win) { 81 if (!win) { 82 return null; 83 } 84 85 if (lazy.AppInfo.isAndroid) { 86 return new lazy.MobileTabBrowser(win); 87 } else if (lazy.AppInfo.isFirefox) { 88 return win.gBrowser; 89 } 90 91 return null; 92 } 93 94 /** 95 * Create a new tab. 96 * 97 * @param {object} options 98 * @param {boolean=} options.focus 99 * Set to true if the new tab should be focused (selected). Defaults to 100 * false. `false` value is not properly supported on Android, additional 101 * focus of previously selected tab is required after initial navigation. 102 * @param {Tab=} options.referenceTab 103 * The reference tab after which the new tab will be added. If no 104 * reference tab is provided, the new tab will be added after all the 105 * other tabs. 106 * @param {string=} options.userContextId 107 * A user context id from UserContextManager. 108 * @param {window=} options.window 109 * The window where the new tab will open. Defaults to 110 * Services.wm.getMostRecentBrowserWindow if no window is provided. 111 * Will be ignored if referenceTab is provided. 112 */ 113 async addTab(options = {}) { 114 let { 115 focus = false, 116 referenceTab = null, 117 userContextId = null, 118 window = Services.wm.getMostRecentBrowserWindow(), 119 } = options; 120 121 let tabIndex; 122 if (referenceTab != null) { 123 // If a reference tab was specified, the window should be the window 124 // owning the reference tab. 125 window = this.getWindowForTab(referenceTab); 126 } 127 128 if (referenceTab != null) { 129 tabIndex = this.getTabsForWindow(window).indexOf(referenceTab) + 1; 130 } 131 132 const tabBrowser = this.getTabBrowser(window); 133 134 const tab = await tabBrowser.addTab("about:blank", { 135 tabIndex, 136 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), 137 userContextId: lazy.UserContextManager.getInternalIdById(userContextId), 138 }); 139 140 if (focus) { 141 await this.selectTab(tab); 142 } 143 144 return tab; 145 } 146 147 /** 148 * Retrieve the count of all the open tabs. 149 * 150 * @returns {number} Number of open tabs. 151 */ 152 getTabCount() { 153 return lazy.windowManager.windows.reduce((total, win) => { 154 // For browser windows count the tabs. Otherwise take the window itself. 155 const tabsLength = this.getTabsForWindow(win).length; 156 return total + (tabsLength ? tabsLength : 1); 157 }, 0); 158 } 159 160 /** 161 * Retrieve the tab owning a Browsing Context. 162 * 163 * @param {BrowsingContext=} browsingContext 164 * The browsing context to get the tab from. 165 * 166 * @returns {Tab|null} 167 * The tab owning the Browsing Context. 168 */ 169 getTabForBrowsingContext(browsingContext) { 170 const browser = browsingContext?.top.embedderElement; 171 if (!browser) { 172 return null; 173 } 174 175 const tabBrowser = this.getTabBrowser(browser.ownerGlobal); 176 return tabBrowser.getTabForBrowser(browser); 177 } 178 179 /** 180 * Retrieve the list of tabs for a given window. 181 * 182 * @param {ChromeWindow} win 183 * Window whose tabs need to be returned. 184 * 185 * @returns {Array<Tab>} 186 * The list of tabs. Will return an empty list if tab browser is not available 187 * or tabs are undefined. 188 */ 189 getTabsForWindow(win) { 190 const tabBrowser = this.getTabBrowser(win); 191 192 // For web-platform reftests a faked tabbrowser is used, 193 // which does not actually have tabs. 194 if (tabBrowser && tabBrowser.tabs) { 195 return tabBrowser.tabs; 196 } 197 198 return []; 199 } 200 201 getWindowForTab(tab) { 202 // `.linkedBrowser.ownerGlobal` works both with Firefox Desktop and Mobile. 203 // Other accessors (eg `.ownerGlobal` or `.browser.ownerGlobal`) fail on one 204 // of the platforms. 205 return tab.linkedBrowser.ownerGlobal; 206 } 207 208 /** 209 * Check if the given argument is a valid canonical browsing context and was not 210 * discarded. 211 * 212 * @param {BrowsingContext} browsingContext 213 * The browsing context to check. 214 * 215 * @returns {boolean} 216 * True if the browsing context is valid, false otherwise. 217 */ 218 isValidCanonicalBrowsingContext(browsingContext) { 219 return ( 220 CanonicalBrowsingContext.isInstance(browsingContext) && 221 !browsingContext.isDiscarded 222 ); 223 } 224 225 /** 226 * Remove the given tab. 227 * 228 * @param {Tab} tab 229 * Tab to remove. 230 * @param {object=} options 231 * @param {boolean=} options.skipPermitUnload 232 * Flag to indicate if a potential beforeunload prompt should be skipped 233 * when closing the tab. Defaults to false. 234 */ 235 async removeTab(tab, options = {}) { 236 const { skipPermitUnload = false } = options; 237 238 if (!tab) { 239 return; 240 } 241 242 const ownerWindow = this.getWindowForTab(tab); 243 const tabBrowser = this.getTabBrowser(ownerWindow); 244 await tabBrowser.removeTab(tab, { 245 skipPermitUnload, 246 }); 247 } 248 249 /** 250 * Select the given tab. 251 * 252 * @param {Tab} tab 253 * Tab to select. 254 * 255 * @returns {Promise} 256 * Promise that resolves when the given tab has been selected. 257 */ 258 async selectTab(tab) { 259 if (!tab) { 260 return Promise.resolve(); 261 } 262 263 const ownerWindow = this.getWindowForTab(tab); 264 const tabBrowser = this.getTabBrowser(ownerWindow); 265 266 if (tab === tabBrowser.selectedTab) { 267 return Promise.resolve(); 268 } 269 270 const selected = new lazy.EventPromise(ownerWindow, "TabSelect"); 271 tabBrowser.selectedTab = tab; 272 273 await selected; 274 275 // Sometimes at that point window is not focused. 276 if (Services.focus.activeWindow != ownerWindow) { 277 const activated = new lazy.EventPromise(ownerWindow, "activate"); 278 ownerWindow.focus(); 279 return activated; 280 } 281 282 return Promise.resolve(); 283 } 284 285 supportsTabs() { 286 return lazy.AppInfo.isAndroid || lazy.AppInfo.isFirefox; 287 } 288 } 289 290 // Expose a shared singleton. 291 export const TabManager = new TabManagerClass();