Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / remote / shared / WindowManager.sys.mjs
blob94b1ed13c1cca853bdc2b47cc8ecf5d6727682d6
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/. */
5 const lazy = {};
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",
16   UserContextManager:
17     "chrome://remote/content/shared/UserContextManager.sys.mjs",
18   waitForObserverTopic: "chrome://remote/content/marionette/sync.sys.mjs",
19 });
21 /**
22  * Provides helpers to interact with Window objects.
23  *
24  * @class WindowManager
25  */
26 class WindowManager {
27   constructor() {
28     // Maps ChromeWindow to uuid: WeakMap.<Object, string>
29     this._chromeWindowHandles = new WeakMap();
30   }
32   get chromeWindowHandles() {
33     const chromeWindowHandles = [];
35     for (const win of this.windows) {
36       chromeWindowHandles.push(this.getIdForWindow(win));
37     }
39     return chromeWindowHandles;
40   }
42   get windows() {
43     return Services.wm.getEnumerator(null);
44   }
46   /**
47    * Find a specific window matching the provided window handle.
48    *
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`.
52    *
53    * @returns {object} A window properties object,
54    *     @see :js:func:`GeckoDriver#getWindowProperties`
55    */
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);
62       }
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(
70             tabBrowser.tabs[i]
71           );
72           let contentWindowId = lazy.TabManager.getIdForBrowser(contentBrowser);
74           if (contentWindowId == handle) {
75             return this.getWindowProperties(win, { tabIndex: i });
76           }
77         }
78       }
79     }
81     return null;
82   }
84   /**
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
87    * Content Window.
88    *
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
94    *     tabBrowser.
95    * @property {number} tabIndex - Optional, the index of the specific tab
96    *     within the window.
97    */
99   /**
100    * Returns a WindowProperties object, that can be used with :js:func:`GeckoDriver#setWindowHandle`.
101    *
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.
108    */
109   getWindowProperties(win, options = {}) {
110     if (!Window.isInstance(win)) {
111       throw new TypeError("Invalid argument, expected a Window object");
112     }
114     return {
115       win,
116       id: this.getIdForWindow(win),
117       hasTabBrowser: !!lazy.TabManager.getTabBrowser(win),
118       tabIndex: options.tabIndex,
119     };
120   }
122   /**
123    * Retrieves an id for the given chrome window. The id is a dynamically
124    * generated uuid associated with the window object.
125    *
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.
129    */
130   getIdForWindow(win) {
131     if (!this._chromeWindowHandles.has(win)) {
132       this._chromeWindowHandles.set(win, lazy.generateUUID());
133     }
134     return this._chromeWindowHandles.get(win);
135   }
137   /**
138    * Close the specified window.
139    *
140    * @param {window} win
141    *     The window to close.
142    * @returns {Promise}
143    *     A promise which is resolved when the current window has been closed.
144    */
145   async closeWindow(win) {
146     const destroyed = lazy.waitForObserverTopic("xul-window-destroyed", {
147       checkFn: () => win && win.closed,
148     });
150     win.close();
152     return destroyed;
153   }
155   /**
156    * Focus the specified window.
157    *
158    * @param {window} win
159    *     The window to focus.
160    * @returns {Promise}
161    *     A promise which is resolved when the window has been focused.
162    */
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 });
168       win.focus();
170       await Promise.all([activated, focused]);
171     }
172   }
174   /**
175    * Open a new browser window.
176    *
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
184    *     topmost window.
185    * @param {string=} options.userContextId
186    *     The id of the user context which should own the initial tab of the new
187    *     window.
188    * @returns {Promise}
189    *     A promise resolving to the newly created chrome window.
190    */
191   async openBrowserWindow(options = {}) {
192     let {
193       focus = false,
194       isPrivate = false,
195       openerWindow = null,
196       userContextId = null,
197     } = options;
199     switch (lazy.AppInfo.name) {
200       case "Firefox":
201         if (openerWindow === null) {
202           // If no opener was provided, fallback to the topmost window.
203           openerWindow = Services.wm.getMostRecentBrowserWindow();
204         }
206         if (!openerWindow) {
207           throw new lazy.error.UnsupportedOperationError(
208             `openWindow() could not find a valid opener window`
209           );
210         }
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(
217             openerWindow,
218             "about:blank",
219             "window",
220             {
221               private: isPrivate,
222               resolveOnContentBrowserCreated,
223               userContextId:
224                 lazy.UserContextManager.getInternalIdById(userContextId),
225             }
226           )
227         );
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
233         if (focus) {
234           // Focus the currently selected tab.
235           browser.focus();
236         } else {
237           // If the new window shouldn't get focused, set the
238           // focus back to the opening window.
239           await this.focusWindow(openerWindow);
240         }
242         return browser.ownerGlobal;
244       default:
245         throw new lazy.error.UnsupportedOperationError(
246           `openWindow() not supported in ${lazy.AppInfo.name}`
247         );
248     }
249   }
251   /**
252    * Wait until the initial application window has been opened and loaded.
253    *
254    * @returns {Promise<WindowProxy>}
255    *     A promise that resolved to the application window.
256    */
257   waitForInitialApplicationWindowLoaded() {
258     return new lazy.TimedPromise(
259       async resolve => {
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",
265           {
266             checkFn: subject => (win !== null ? subject == win : true),
267           }
268         );
270         // The current window has already been finished loading.
271         if (win && win.document.readyState == "complete") {
272           resolve(win);
273           return;
274         }
276         // Wait for the next browser/mail window to open and finished loading.
277         const { subject } = await windowLoaded;
278         resolve(subject);
279       },
280       {
281         errorMessage: "No applicable application window found",
282       }
283     );
284   }
287 // Expose a shared singleton.
288 export const windowManager = new WindowManager();