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/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs",
10 AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
11 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
12 EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
13 generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
14 TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
15 TimedPromise: "chrome://remote/content/marionette/sync.sys.mjs",
17 "chrome://remote/content/shared/UserContextManager.sys.mjs",
18 waitForObserverTopic: "chrome://remote/content/marionette/sync.sys.mjs",
22 * Provides helpers to interact with Window objects.
24 * @class WindowManager
28 // Maps ChromeWindow to uuid: WeakMap.<Object, string>
29 this._chromeWindowHandles = new WeakMap();
32 get chromeWindowHandles() {
33 const chromeWindowHandles = [];
35 for (const win of this.windows) {
36 chromeWindowHandles.push(this.getIdForWindow(win));
39 return chromeWindowHandles;
43 return Services.wm.getEnumerator(null);
47 * Find a specific window matching the provided window handle.
49 * @param {string} handle
50 * The unique handle of either a chrome window or a content browser, as
51 * returned by :js:func:`#getIdForBrowser` or :js:func:`#getIdForWindow`.
53 * @returns {object} A window properties object,
54 * @see :js:func:`GeckoDriver#getWindowProperties`
56 findWindowByHandle(handle) {
57 for (const win of this.windows) {
58 // In case the wanted window is a chrome window, we are done.
59 const chromeWindowId = this.getIdForWindow(win);
60 if (chromeWindowId == handle) {
61 return this.getWindowProperties(win);
64 // Otherwise check if the chrome window has a tab browser, and that it
65 // contains a tab with the wanted window handle.
66 const tabBrowser = lazy.TabManager.getTabBrowser(win);
67 if (tabBrowser && tabBrowser.tabs) {
68 for (let i = 0; i < tabBrowser.tabs.length; ++i) {
69 let contentBrowser = lazy.TabManager.getBrowserForTab(
72 let contentWindowId = lazy.TabManager.getIdForBrowser(contentBrowser);
74 if (contentWindowId == handle) {
75 return this.getWindowProperties(win, { tabIndex: i });
85 * A set of properties describing a window and that should allow to uniquely
86 * identify it. The described window can either be a Chrome Window or a
89 * @typedef {object} WindowProperties
90 * @property {Window} win - The Chrome Window containing the window.
91 * When describing a Chrome Window, this is the window itself.
92 * @property {string} id - The unique id of the containing Chrome Window.
93 * @property {boolean} hasTabBrowser - `true` if the Chrome Window has a
95 * @property {number} tabIndex - Optional, the index of the specific tab
100 * Returns a WindowProperties object, that can be used with :js:func:`GeckoDriver#setWindowHandle`.
102 * @param {Window} win
103 * The Chrome Window for which we want to create a properties object.
104 * @param {object} options
105 * @param {number} options.tabIndex
106 * Tab index of a specific Content Window in the specified Chrome Window.
107 * @returns {WindowProperties} A window properties object.
109 getWindowProperties(win, options = {}) {
110 if (!Window.isInstance(win)) {
111 throw new TypeError("Invalid argument, expected a Window object");
116 id: this.getIdForWindow(win),
117 hasTabBrowser: !!lazy.TabManager.getTabBrowser(win),
118 tabIndex: options.tabIndex,
123 * Retrieves an id for the given chrome window. The id is a dynamically
124 * generated uuid associated with the window object.
126 * @param {window} win
127 * The window object for which we want to retrieve the id.
128 * @returns {string} The unique id for this chrome window.
130 getIdForWindow(win) {
131 if (!this._chromeWindowHandles.has(win)) {
132 this._chromeWindowHandles.set(win, lazy.generateUUID());
134 return this._chromeWindowHandles.get(win);
138 * Close the specified window.
140 * @param {window} win
141 * The window to close.
143 * A promise which is resolved when the current window has been closed.
145 async closeWindow(win) {
146 const destroyed = lazy.waitForObserverTopic("xul-window-destroyed", {
147 checkFn: () => win && win.closed,
156 * Focus the specified window.
158 * @param {window} win
159 * The window to focus.
161 * A promise which is resolved when the window has been focused.
163 async focusWindow(win) {
164 if (Services.focus.activeWindow != win) {
165 let activated = new lazy.EventPromise(win, "activate");
166 let focused = new lazy.EventPromise(win, "focus", { capture: true });
170 await Promise.all([activated, focused]);
175 * Open a new browser window.
177 * @param {object=} options
178 * @param {boolean=} options.focus
179 * If true, the opened window will receive the focus. Defaults to false.
180 * @param {boolean=} options.isPrivate
181 * If true, the opened window will be a private window. Defaults to false.
182 * @param {ChromeWindow=} options.openerWindow
183 * Use this window as the opener of the new window. Defaults to the
185 * @param {string=} options.userContextId
186 * The id of the user context which should own the initial tab of the new
189 * A promise resolving to the newly created chrome window.
191 async openBrowserWindow(options = {}) {
196 userContextId = null,
199 switch (lazy.AppInfo.name) {
201 if (openerWindow === null) {
202 // If no opener was provided, fallback to the topmost window.
203 openerWindow = Services.wm.getMostRecentBrowserWindow();
207 throw new lazy.error.UnsupportedOperationError(
208 `openWindow() could not find a valid opener window`
212 // Open new browser window, and wait until it is fully loaded.
213 // Also wait for the window to be focused and activated to prevent a
214 // race condition when promptly focusing to the original window again.
215 const browser = await new Promise(resolveOnContentBrowserCreated =>
216 lazy.URILoadingHelper.openTrustedLinkIn(
222 resolveOnContentBrowserCreated,
224 lazy.UserContextManager.getInternalIdById(userContextId),
229 // TODO: Both for WebDriver BiDi and classic, opening a new window
230 // should not run the focus steps. When focus is false we should avoid
231 // focusing the new window completely. See Bug 1766329
234 // Focus the currently selected tab.
237 // If the new window shouldn't get focused, set the
238 // focus back to the opening window.
239 await this.focusWindow(openerWindow);
242 return browser.ownerGlobal;
245 throw new lazy.error.UnsupportedOperationError(
246 `openWindow() not supported in ${lazy.AppInfo.name}`
252 * Wait until the initial application window has been opened and loaded.
254 * @returns {Promise<WindowProxy>}
255 * A promise that resolved to the application window.
257 waitForInitialApplicationWindowLoaded() {
258 return new lazy.TimedPromise(
260 // This call includes a fallback to "mail3:pane" as well.
261 const win = Services.wm.getMostRecentBrowserWindow();
263 const windowLoaded = lazy.waitForObserverTopic(
264 "browser-delayed-startup-finished",
266 checkFn: subject => (win !== null ? subject == win : true),
270 // The current window has already been finished loading.
271 if (win && win.document.readyState == "complete") {
276 // Wait for the next browser/mail window to open and finished loading.
277 const { subject } = await windowLoaded;
281 errorMessage: "No applicable application window found",
287 // Expose a shared singleton.
288 export const windowManager = new WindowManager();