/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs", }); const DIALOG_TYPES = { ALERT: "alert", BEFOREUNLOAD: "beforeunload", CONFIRM: "confirm", PROMPT: "prompt", }; /** * Helper dedicated to detect and interact with browser dialogs such as `alert`, * `confirm` etc. The current implementation only supports tabmodal dialogs, * not full window dialogs. * * Emits "dialog-loaded" when a javascript dialog is opened for the current * browser. * * @param {BrowserElement} browser */ export class DialogHandler { constructor(browser) { lazy.EventEmitter.decorate(this); this._dialog = null; this._browser = browser; this._onCommonDialogLoaded = this._onCommonDialogLoaded.bind(this); this._onTabDialogLoaded = this._onTabDialogLoaded.bind(this); Services.obs.addObserver( this._onCommonDialogLoaded, "common-dialog-loaded" ); Services.obs.addObserver(this._onTabDialogLoaded, "tabmodal-dialog-loaded"); } destructor() { this._dialog = null; this._pageTarget = null; Services.obs.removeObserver( this._onCommonDialogLoaded, "common-dialog-loaded" ); Services.obs.removeObserver( this._onTabDialogLoaded, "tabmodal-dialog-loaded" ); } async handleJavaScriptDialog({ accept, promptText }) { if (!this._dialog) { throw new Error("No dialog available for handleJavaScriptDialog"); } const type = this._getDialogType(); if (promptText && type === "prompt") { this._dialog.ui.loginTextbox.value = promptText; } const onDialogClosed = new Promise(r => { this._browser.addEventListener("DOMModalDialogClosed", r, { once: true, }); }); // 0 corresponds to the OK callback, 1 to the CANCEL callback. if (accept) { this._dialog.ui.button0.click(); } else { this._dialog.ui.button1.click(); } await onDialogClosed; // Resetting dialog to null here might be racy and lead to errors if the // content page is triggering several prompts in a row. // See Bug 1569578. this._dialog = null; } _getDialogType() { const { inPermitUnload, promptType } = this._dialog.args; if (inPermitUnload) { return DIALOG_TYPES.BEFOREUNLOAD; } switch (promptType) { case "alert": return DIALOG_TYPES.ALERT; case "confirm": return DIALOG_TYPES.CONFIRM; case "prompt": return DIALOG_TYPES.PROMPT; default: throw new Error("Unsupported dialog type: " + promptType); } } _onCommonDialogLoaded(dialogWindow) { const dialogs = this._browser.tabDialogBox.getContentDialogManager() .dialogs; const dialog = dialogs.find(d => d.frameContentWindow === dialogWindow); if (!dialog) { // The dialog is not for the current tab. return; } this._dialog = dialogWindow.Dialog; const message = this._dialog.args.text; const type = this._getDialogType(); this.emit("dialog-loaded", { message, type }); } _onTabDialogLoaded(promptContainer) { const prompts = this._browser.tabModalPromptBox.listPrompts(); const prompt = prompts.find(p => p.ui.promptContainer === promptContainer); if (!prompt) { // The dialog is not for the current tab. return; } this._dialog = prompt; const message = this._dialog.args.text; const type = this._getDialogType(); this.emit("dialog-loaded", { message, type }); } }