Prompt.sys.mjs (5640B)
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/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs", 9 Log: "chrome://remote/content/shared/Log.sys.mjs", 10 }); 11 12 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get()); 13 14 const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml"; 15 16 /** @namespace */ 17 export const modal = { 18 ACTION_CLOSED: "closed", 19 ACTION_OPENED: "opened", 20 }; 21 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(win); 46 } 47 } 48 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(prompt); 55 } 56 } 57 58 const contentBrowser = context.contentBrowser; 59 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(dialogs[0].frameContentWindow); 69 } 70 71 dialogs = contentBrowser.tabDialogBox.getContentDialogManager().dialogs; 72 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(dialogs[0].frameContentWindow); 78 } 79 } 80 return null; 81 }; 82 83 /** 84 * Represents a modal dialog. 85 * 86 * @param {DOMWindow} dialog 87 * DOMWindow of the dialog. 88 */ 89 modal.Dialog = class { 90 #win; 91 92 constructor(dialog) { 93 this.#win = Cu.getWeakReference(dialog); 94 } 95 96 get args() { 97 if (lazy.AppInfo.isAndroid) { 98 return this.window.args; 99 } 100 let tm = this.tabModal; 101 return tm ? tm.args : null; 102 } 103 104 get isOpen() { 105 if (lazy.AppInfo.isAndroid) { 106 return this.window !== null; 107 } 108 if (!this.ui) { 109 return false; 110 } 111 return true; 112 } 113 114 get isWindowModal() { 115 return [ 116 Services.prompt.MODAL_TYPE_WINDOW, 117 Services.prompt.MODAL_TYPE_INTERNAL_WINDOW, 118 ].includes(this.args.modalType); 119 } 120 121 get tabModal() { 122 return this.window?.Dialog; 123 } 124 125 get promptType() { 126 return this.args.inPermitUnload ? "beforeunload" : this.args.promptType; 127 } 128 129 get ui() { 130 let tm = this.tabModal; 131 return tm ? tm.ui : null; 132 } 133 134 /** 135 * For Android, this returns a GeckoViewPrompter, which can be used to control prompts. 136 * Otherwise, this returns the ChromeWindow associated with an open dialog window if 137 * it is currently attached to the DOM. 138 */ 139 get window() { 140 if (this.#win) { 141 let win = this.#win.get(); 142 if (win && (lazy.AppInfo.isAndroid || win.parent)) { 143 return win; 144 } 145 } 146 return null; 147 } 148 149 /** 150 * Sets the text of a prompt's input field. 151 * 152 * @param {string} inputText 153 * The text to set for the input field. 154 */ 155 set text(inputText) { 156 if (lazy.AppInfo.isAndroid) { 157 this.window.setInputText(inputText); 158 } else { 159 // see toolkit/components/prompts/content/commonDialog.js 160 let { loginTextbox } = this.ui; 161 loginTextbox.value = inputText; 162 } 163 } 164 165 /** 166 * Accept the user prompt. 167 */ 168 accept() { 169 if (lazy.AppInfo.isAndroid) { 170 // GeckoView does not have a UI, so the methods are called directly 171 this.window.acceptPrompt(); 172 } else { 173 const { button0 } = this.ui; 174 button0.click(); 175 } 176 } 177 178 /** 179 * Dismiss the user prompt. 180 */ 181 dismiss() { 182 if (lazy.AppInfo.isAndroid) { 183 // GeckoView does not have a UI, so the methods are called directly 184 this.window.dismissPrompt(); 185 } else { 186 const { button0, button1 } = this.ui; 187 (button1 ? button1 : button0).click(); 188 } 189 } 190 191 /** 192 * Returns text of the prompt. 193 * 194 * @returns {Promise<string>} 195 * Returns a Promise resolving to the prompt text. 196 */ 197 getText() { 198 if (lazy.AppInfo.isAndroid) { 199 return this.window.getPromptText(); 200 } 201 202 return Promise.resolve(this.ui.infoBody.textContent); 203 } 204 205 /** 206 * Returns text of the prompt input. 207 * 208 * @returns {Promise<string>} 209 * Returns a Promise resolving to the input's text. 210 */ 211 getInputText() { 212 if (lazy.AppInfo.isAndroid) { 213 return this.window.getInputText(); 214 } 215 216 return Promise.resolve(this.ui.loginTextbox.value); 217 } 218 };