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