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 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 generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
12 TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
13 TimedPromise: "chrome://remote/content/marionette/sync.sys.mjs",
14 waitForObserverTopic: "chrome://remote/content/marionette/sync.sys.mjs",
18 * Provides helpers to interact with Window objects.
20 * @class WindowManager
24 // Maps ChromeWindow to uuid: WeakMap.<Object, string>
25 this._chromeWindowHandles = new WeakMap();
28 get chromeWindowHandles() {
29 const chromeWindowHandles = [];
31 for (const win of this.windows) {
32 chromeWindowHandles.push(this.getIdForWindow(win));
35 return chromeWindowHandles;
39 return Services.wm.getEnumerator(null);
43 * Find a specific window matching the provided window handle.
45 * @param {string} handle
46 * The unique handle of either a chrome window or a content browser, as
47 * returned by :js:func:`#getIdForBrowser` or :js:func:`#getIdForWindow`.
49 * @returns {object} A window properties object,
50 * @see :js:func:`GeckoDriver#getWindowProperties`
52 findWindowByHandle(handle) {
53 for (const win of this.windows) {
54 // In case the wanted window is a chrome window, we are done.
55 const chromeWindowId = this.getIdForWindow(win);
56 if (chromeWindowId == handle) {
57 return this.getWindowProperties(win);
60 // Otherwise check if the chrome window has a tab browser, and that it
61 // contains a tab with the wanted window handle.
62 const tabBrowser = lazy.TabManager.getTabBrowser(win);
63 if (tabBrowser && tabBrowser.tabs) {
64 for (let i = 0; i < tabBrowser.tabs.length; ++i) {
65 let contentBrowser = lazy.TabManager.getBrowserForTab(
68 let contentWindowId = lazy.TabManager.getIdForBrowser(contentBrowser);
70 if (contentWindowId == handle) {
71 return this.getWindowProperties(win, { tabIndex: i });
81 * A set of properties describing a window and that should allow to uniquely
82 * identify it. The described window can either be a Chrome Window or a
85 * @typedef {object} WindowProperties
86 * @property {Window} win - The Chrome Window containing the window.
87 * When describing a Chrome Window, this is the window itself.
88 * @property {string} id - The unique id of the containing Chrome Window.
89 * @property {boolean} hasTabBrowser - `true` if the Chrome Window has a
91 * @property {number} tabIndex - Optional, the index of the specific tab
96 * Returns a WindowProperties object, that can be used with :js:func:`GeckoDriver#setWindowHandle`.
99 * The Chrome Window for which we want to create a properties object.
100 * @param {object} options
101 * @param {number} options.tabIndex
102 * Tab index of a specific Content Window in the specified Chrome Window.
103 * @returns {WindowProperties} A window properties object.
105 getWindowProperties(win, options = {}) {
106 if (!Window.isInstance(win)) {
107 throw new TypeError("Invalid argument, expected a Window object");
112 id: this.getIdForWindow(win),
113 hasTabBrowser: !!lazy.TabManager.getTabBrowser(win),
114 tabIndex: options.tabIndex,
119 * Retrieves an id for the given chrome window. The id is a dynamically
120 * generated uuid associated with the window object.
122 * @param {window} win
123 * The window object for which we want to retrieve the id.
124 * @returns {string} The unique id for this chrome window.
126 getIdForWindow(win) {
127 if (!this._chromeWindowHandles.has(win)) {
128 this._chromeWindowHandles.set(win, lazy.generateUUID());
130 return this._chromeWindowHandles.get(win);
134 * Close the specified window.
136 * @param {window} win
137 * The window to close.
139 * A promise which is resolved when the current window has been closed.
141 async closeWindow(win) {
142 const destroyed = lazy.waitForObserverTopic("xul-window-destroyed", {
143 checkFn: () => win && win.closed,
152 * Focus the specified window.
154 * @param {window} win
155 * The window to focus.
157 * A promise which is resolved when the window has been focused.
159 async focusWindow(win) {
160 if (Services.focus.activeWindow != win) {
161 let activated = new lazy.EventPromise(win, "activate");
162 let focused = new lazy.EventPromise(win, "focus", { capture: true });
166 await Promise.all([activated, focused]);
171 * Open a new browser window.
173 * @param {object=} options
174 * @param {boolean=} options.focus
175 * If true, the opened window will receive the focus. Defaults to false.
176 * @param {boolean=} options.isPrivate
177 * If true, the opened window will be a private window. Defaults to false.
178 * @param {ChromeWindow=} options.openerWindow
179 * Use this window as the opener of the new window. Defaults to the
182 * A promise resolving to the newly created chrome window.
184 async openBrowserWindow(options = {}) {
185 let { focus = false, isPrivate = false, openerWindow = null } = options;
187 switch (lazy.AppInfo.name) {
189 if (openerWindow === null) {
190 // If no opener was provided, fallback to the topmost window.
191 openerWindow = Services.wm.getMostRecentBrowserWindow();
195 throw new lazy.error.UnsupportedOperationError(
196 `openWindow() could not find a valid opener window`
200 // Open new browser window, and wait until it is fully loaded.
201 // Also wait for the window to be focused and activated to prevent a
202 // race condition when promptly focusing to the original window again.
203 const win = openerWindow.OpenBrowserWindow({ private: isPrivate });
205 const activated = new lazy.EventPromise(win, "activate");
206 const focused = new lazy.EventPromise(win, "focus", { capture: true });
207 const startup = lazy.waitForObserverTopic(
208 "browser-delayed-startup-finished",
210 checkFn: subject => subject == win,
214 // TODO: Both for WebDriver BiDi and classic, opening a new window
215 // should not run the focus steps. When focus is false we should avoid
216 // focusing the new window completely. See Bug 1766329
219 await Promise.all([activated, focused, startup]);
222 // Focus the currently selected tab.
223 const browser = lazy.TabManager.getTabBrowser(win).selectedBrowser;
226 // If the new window shouldn't get focused, set the
227 // focus back to the opening window.
228 await this.focusWindow(openerWindow);
234 throw new lazy.error.UnsupportedOperationError(
235 `openWindow() not supported in ${lazy.AppInfo.name}`
241 * Wait until the initial application window has been opened and loaded.
243 * @returns {Promise<WindowProxy>}
244 * A promise that resolved to the application window.
246 waitForInitialApplicationWindowLoaded() {
247 return new lazy.TimedPromise(
249 // This call includes a fallback to "mail3:pane" as well.
250 const win = Services.wm.getMostRecentBrowserWindow();
252 const windowLoaded = lazy.waitForObserverTopic(
253 "browser-delayed-startup-finished",
255 checkFn: subject => (win !== null ? subject == win : true),
259 // The current window has already been finished loading.
260 if (win && win.document.readyState == "complete") {
265 // Wait for the next browser/mail window to open and finished loading.
266 const { subject } = await windowLoaded;
270 errorMessage: "No applicable application window found",
276 // Expose a shared singleton.
277 export const windowManager = new WindowManager();