Bug 1869043 add a main thread record of track audio outputs r=padenot
[gecko.git] / remote / shared / Prompt.sys.mjs
blobdcdc254ebc0485a07b9502d6558eb74318b59896
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   Log: "chrome://remote/content/shared/Log.sys.mjs",
10 });
12 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
14 const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml";
16 /** @namespace */
17 export const modal = {
18   ACTION_CLOSED: "closed",
19   ACTION_OPENED: "opened",
22 /**
23  * Check for already existing modal or tab modal dialogs and
24  * return the first one.
25  *
26  * @param {browser.Context} context
27  *     Reference to the browser context to check for existent dialogs.
28  *
29  * @returns {modal.Dialog}
30  *     Returns instance of the Dialog class, or `null` if no modal dialog
31  *     is present.
32  */
33 modal.findPrompt = function (context) {
34   // First check if there is a modal dialog already present for the
35   // current browser window.
36   for (let win of Services.wm.getEnumerator(null)) {
37     // TODO: Use BrowserWindowTracker.getTopWindow for modal dialogs without
38     // an opener.
39     if (
40       win.document.documentURI === COMMON_DIALOG &&
41       win.opener &&
42       win.opener === context.window
43     ) {
44       lazy.logger.trace("Found open window modal prompt");
45       return new modal.Dialog(() => context, win);
46     }
47   }
49   if (lazy.AppInfo.isAndroid) {
50     const geckoViewPrompts = context.window.prompts();
51     if (geckoViewPrompts.length) {
52       lazy.logger.trace("Found open GeckoView prompt");
53       const prompt = geckoViewPrompts[0];
54       return new modal.Dialog(() => context, prompt);
55     }
56   }
58   const contentBrowser = context.contentBrowser;
60   // If no modal dialog has been found yet, also check for tab and content modal
61   // dialogs for the current tab.
62   //
63   // TODO: Find an adequate implementation for Firefox on Android (bug 1708105)
64   if (contentBrowser?.tabDialogBox) {
65     let dialogs = contentBrowser.tabDialogBox.getTabDialogManager().dialogs;
66     if (dialogs.length) {
67       lazy.logger.trace("Found open tab modal prompt");
68       return new modal.Dialog(() => context, dialogs[0].frameContentWindow);
69     }
71     dialogs = contentBrowser.tabDialogBox.getContentDialogManager().dialogs;
73     // Even with the dialog manager handing back a dialog, the `Dialog` property
74     // gets lazily added. If it's not set yet, ignore the dialog for now.
75     if (dialogs.length && dialogs[0].frameContentWindow.Dialog) {
76       lazy.logger.trace("Found open content prompt");
77       return new modal.Dialog(() => context, dialogs[0].frameContentWindow);
78     }
79   }
81   // If no modal dialog has been found yet, check for old non SubDialog based
82   // content modal dialogs. Even with those deprecated in Firefox 89 we should
83   // keep supporting applications that don't have them implemented yet.
84   if (contentBrowser?.tabModalPromptBox) {
85     const prompts = contentBrowser.tabModalPromptBox.listPrompts();
86     if (prompts.length) {
87       lazy.logger.trace("Found open old-style content prompt");
88       return new modal.Dialog(() => context, null);
89     }
90   }
92   return null;
95 /**
96  * Represents a modal dialog.
97  *
98  * @param {function(): browser.Context} curBrowserFn
99  *     Function that returns the current |browser.Context|.
100  * @param {DOMWindow} dialog
101  *     DOMWindow of the dialog.
102  */
103 modal.Dialog = class {
104   constructor(curBrowserFn, dialog) {
105     this.curBrowserFn_ = curBrowserFn;
106     this.win_ = Cu.getWeakReference(dialog);
107   }
109   get args() {
110     if (lazy.AppInfo.isAndroid) {
111       return this.window.args;
112     }
113     let tm = this.tabModal;
114     return tm ? tm.args : null;
115   }
117   get curBrowser_() {
118     return this.curBrowserFn_();
119   }
121   get isOpen() {
122     if (lazy.AppInfo.isAndroid) {
123       return this.window !== null;
124     }
125     if (!this.ui) {
126       return false;
127     }
128     return true;
129   }
131   get isWindowModal() {
132     return [
133       Services.prompt.MODAL_TYPE_WINDOW,
134       Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
135     ].includes(this.args.modalType);
136   }
138   get tabModal() {
139     let win = this.window;
140     if (win) {
141       return win.Dialog;
142     }
143     return this.curBrowser_.getTabModal();
144   }
146   get promptType() {
147     return this.args.promptType;
148   }
150   get ui() {
151     let tm = this.tabModal;
152     return tm ? tm.ui : null;
153   }
155   /**
156    * For Android, this returns a GeckoViewPrompter, which can be used to control prompts.
157    * Otherwise, this returns the ChromeWindow associated with an open dialog window if
158    * it is currently attached to the DOM.
159    */
160   get window() {
161     if (this.win_) {
162       let win = this.win_.get();
163       if (win && (lazy.AppInfo.isAndroid || win.parent)) {
164         return win;
165       }
166     }
167     return null;
168   }
170   set text(inputText) {
171     if (lazy.AppInfo.isAndroid) {
172       this.window.setInputText(inputText);
173     } else {
174       // see toolkit/components/prompts/content/commonDialog.js
175       let { loginTextbox } = this.ui;
176       loginTextbox.value = inputText;
177     }
178   }
180   accept() {
181     if (lazy.AppInfo.isAndroid) {
182       // GeckoView does not have a UI, so the methods are called directly
183       this.window.acceptPrompt();
184     } else {
185       const { button0 } = this.ui;
186       button0.click();
187     }
188   }
190   dismiss() {
191     if (lazy.AppInfo.isAndroid) {
192       // GeckoView does not have a UI, so the methods are called directly
193       this.window.dismissPrompt();
194     } else {
195       const { button0, button1 } = this.ui;
196       (button1 ? button1 : button0).click();
197     }
198   }
200   /**
201    * Returns text of the prompt.
202    *
203    * @returns {string | Promise}
204    *     Returns string on desktop and Promise on Android.
205    */
206   async getText() {
207     if (lazy.AppInfo.isAndroid) {
208       const textPromise = await this.window.getPromptText();
209       return textPromise;
210     }
211     return this.ui.infoBody.textContent;
212   }
214   /**
215    * Returns text of the prompt input.
216    *
217    * @returns {string}
218    *     Returns string on desktop and Promise on Android.
219    */
220   async getInputText() {
221     if (lazy.AppInfo.isAndroid) {
222       const textPromise = await this.window.getInputText();
223       return textPromise;
224     }
225     return this.ui.loginTextbox.value;
226   }