browser.sys.mjs (9402B)
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 file, 3 * 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 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", 10 EventPromise: "chrome://remote/content/shared/Sync.sys.mjs", 11 MessageManagerDestroyedPromise: 12 "chrome://remote/content/marionette/sync.sys.mjs", 13 NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", 14 TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", 15 windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs", 16 }); 17 18 /** @namespace */ 19 export const browser = {}; 20 21 /** 22 * Variations of Marionette contexts. 23 * 24 * Choosing a context through the <tt>Marionette:SetContext</tt> 25 * command directs all subsequent browsing context scoped commands 26 * to that context. 27 * 28 * @class Marionette.Context 29 */ 30 export class Context { 31 /** 32 * Gets the correct context from a string. 33 * 34 * @param {string} s 35 * Context string serialisation. 36 * 37 * @returns {Context} 38 * Context. 39 * 40 * @throws {TypeError} 41 * If <var>s</var> is not a context. 42 */ 43 static fromString(s) { 44 switch (s) { 45 case "chrome": 46 return Context.Chrome; 47 48 case "content": 49 return Context.Content; 50 51 default: 52 throw new TypeError(`Unknown context: ${s}`); 53 } 54 } 55 } 56 57 Context.Chrome = "chrome"; 58 Context.Content = "content"; 59 60 /** 61 * Creates a browsing context wrapper. 62 * 63 * Browsing contexts handle interactions with the browser, according to 64 * the current environment. 65 */ 66 browser.Context = class { 67 /** 68 * @param {ChromeWindow} window 69 * ChromeWindow that contains the top-level browsing context. 70 * @param {GeckoDriver} driver 71 * Reference to driver instance. 72 */ 73 constructor(window, driver) { 74 this.window = window; 75 this.driver = driver; 76 77 // In Firefox this is <xul:tabbrowser> (not <xul:browser>!) 78 // and MobileTabBrowser in GeckoView. 79 this.tabBrowser = lazy.TabManager.getTabBrowser(this.window); 80 81 // Used to set curFrameId upon new session 82 this.newSession = true; 83 84 // A reference to the tab corresponding to the current window handle, 85 // if any. Specifically, this.tab refers to the last tab that Marionette 86 // switched to in this browser window. Note that this may not equal the 87 // currently selected tab. For example, if Marionette switches to tab 88 // A, and then clicks on a button that opens a new tab B in the same 89 // browser window, this.tab will still point to tab A, despite tab B 90 // being the currently selected tab. 91 this.tab = null; 92 } 93 94 /** 95 * Returns the content browser for the currently selected tab. 96 * If there is no tab selected, null will be returned. 97 */ 98 get contentBrowser() { 99 if (this.tab) { 100 return lazy.TabManager.getBrowserForTab(this.tab); 101 } else if ( 102 this.tabBrowser && 103 this.driver.isReftestBrowser(this.tabBrowser) 104 ) { 105 return this.tabBrowser; 106 } 107 108 return null; 109 } 110 111 /** 112 * Return the unique id of the content browser. 113 */ 114 get contentBrowserId() { 115 return lazy.NavigableManager.getIdForBrowser(this.contentBrowser); 116 } 117 118 get messageManager() { 119 if (this.contentBrowser) { 120 return this.contentBrowser.messageManager; 121 } 122 123 return null; 124 } 125 126 /** 127 * Checks if the browsing context has been discarded. 128 * 129 * The browsing context will have been discarded if the content 130 * browser, represented by the <code><xul:browser></code>, 131 * has been detached. 132 * 133 * @returns {boolean} 134 * True if browsing context has been discarded, false otherwise. 135 */ 136 get closed() { 137 return this.contentBrowser === null; 138 } 139 140 /** 141 * Gets the position and dimensions of the top-level browsing context. 142 * 143 * @returns {Map.<string, number>} 144 * Object with |x|, |y|, |width|, and |height| properties. 145 */ 146 get rect() { 147 return { 148 x: this.window.screenX, 149 y: this.window.screenY, 150 width: this.window.outerWidth, 151 height: this.window.outerHeight, 152 }; 153 } 154 155 /** 156 * Close the current window. 157 * 158 * @returns {Promise} 159 * A promise which is resolved when the current window has been closed. 160 */ 161 async closeWindow() { 162 return lazy.windowManager.closeWindow(this.window); 163 } 164 165 /** 166 * Focus the current window. 167 * 168 * @returns {Promise} 169 * A promise which is resolved when the current window has been focused. 170 */ 171 async focusWindow() { 172 await lazy.windowManager.focusWindow(this.window); 173 174 // Also focus the currently selected tab if present. 175 this.contentBrowser?.focus(); 176 } 177 178 /** 179 * Open a new browser window. 180 * 181 * @returns {Promise} 182 * A promise resolving to the newly created chrome window. 183 */ 184 openBrowserWindow(focus = false, isPrivate = false) { 185 return lazy.windowManager.openBrowserWindow({ 186 openerWindow: this.window, 187 focus, 188 isPrivate, 189 }); 190 } 191 192 /** 193 * Close the current tab. 194 * 195 * @returns {Promise} 196 * A promise which is resolved when the current tab has been closed. 197 * 198 * @throws UnsupportedOperationError 199 * If tab handling for the current application isn't supported. 200 */ 201 async closeTab() { 202 // If the current window is not a browser then close it directly. Do the 203 // same if only one remaining tab is open, or no tab selected at all. 204 // 205 // Note: For GeckoView there will always be a single tab only. But for 206 // consistency with other platforms a specific condition has been added 207 // below as well even it's not really used. 208 if ( 209 !this.tabBrowser || 210 !this.tabBrowser.tabs || 211 this.tabBrowser.tabs.length === 1 || 212 !this.tab 213 ) { 214 return this.closeWindow(); 215 } 216 217 let destroyed = new lazy.MessageManagerDestroyedPromise( 218 this.messageManager 219 ); 220 let tabClosed; 221 222 if (lazy.AppInfo.isAndroid) { 223 await lazy.TabManager.removeTab(this.tab); 224 } else if (lazy.AppInfo.isFirefox) { 225 tabClosed = new lazy.EventPromise(this.tab, "TabClose"); 226 await this.tabBrowser.removeTab(this.tab); 227 } else { 228 throw new lazy.error.UnsupportedOperationError( 229 `closeTab() not supported for ${lazy.AppInfo.name}` 230 ); 231 } 232 233 return Promise.all([destroyed, tabClosed]); 234 } 235 236 /** 237 * Open a new tab in the currently selected chrome window. 238 */ 239 async openTab(focus = false) { 240 let tab = null; 241 242 // Bug 1795841 - For Firefox the TabManager cannot be used yet. As such 243 // handle opening a tab differently for Android. 244 if (lazy.AppInfo.isAndroid) { 245 tab = await lazy.TabManager.addTab({ focus, window: this.window }); 246 } else if (lazy.AppInfo.isFirefox) { 247 const opened = new lazy.EventPromise(this.window, "TabOpen"); 248 this.window.BrowserCommands.openTab({ url: "about:blank" }); 249 await opened; 250 251 tab = this.tabBrowser.selectedTab; 252 253 // The new tab is always selected by default. If focus is not wanted, 254 // the previously tab needs to be selected again. 255 if (!focus) { 256 await lazy.TabManager.selectTab(this.tab); 257 } 258 } else { 259 throw new lazy.error.UnsupportedOperationError( 260 `openTab() not supported for ${lazy.AppInfo.name}` 261 ); 262 } 263 264 return tab; 265 } 266 267 /** 268 * Set the current tab. 269 * 270 * @param {number=} index 271 * Tab index to switch to. If the parameter is undefined, 272 * the currently selected tab will be used. 273 * @param {ChromeWindow=} window 274 * Switch to this window before selecting the tab. 275 * @param {boolean=} focus 276 * A boolean value which determines whether to focus 277 * the window. Defaults to true. 278 * 279 * @returns {Tab} 280 * The selected tab. 281 * 282 * @throws UnsupportedOperationError 283 * If tab handling for the current application isn't supported. 284 */ 285 async switchToTab(index, window = undefined, focus = true) { 286 if (window) { 287 this.window = window; 288 this.tabBrowser = lazy.TabManager.getTabBrowser(this.window); 289 } 290 291 if (!this.tabBrowser || this.driver.isReftestBrowser(this.tabBrowser)) { 292 return null; 293 } 294 295 if (typeof index == "undefined") { 296 this.tab = this.tabBrowser.selectedTab; 297 } else { 298 this.tab = this.tabBrowser.tabs[index]; 299 } 300 301 if (focus) { 302 await lazy.TabManager.selectTab(this.tab); 303 } 304 305 // By accessing the content browser's message manager a new browsing 306 // context is created for browserless tabs, which is needed to successfully 307 // run the WebDriver's is browsing context open step. This is temporary 308 // until we find a better solution on bug 1812258. 309 this.messageManager; 310 311 return this.tab; 312 } 313 314 /** 315 * Registers a new frame, and sets its current frame id to this frame 316 * if it is not already assigned, and if a) we already have a session 317 * or b) we're starting a new session and it is the right start frame. 318 */ 319 register() { 320 if (!this.tabBrowser) { 321 return; 322 } 323 324 // If we're setting up a new session on Firefox, we only process the 325 // registration for this frame if it belongs to the current tab. 326 if (!this.tab) { 327 this.switchToTab(); 328 } 329 } 330 };