Source code

Revision control

Copy as Markdown

Other Tools

/* Any copyright is dedicated to the Public Domain.
/*
* Utility module for tests to interact with prompts spawned by nsIPrompt or
* nsIPromptService.
*/
import { BrowserTestUtils } from "resource://testing-common/BrowserTestUtils.sys.mjs";
import { TestUtils } from "resource://testing-common/TestUtils.sys.mjs";
export let PromptTestUtils = {
/**
* Wait for a prompt from nsIPrompt or nsIPromptsService, interact with it and
* click the specified button to close it.
* @param {Browser|Window} [parent] - Parent of the prompt. This can be
* either the parent window or the browser. For tab prompts, if given a
* window, the currently selected browser in that window will be used.
* @param {Object} promptOptions - @see waitForPrompt
* @param {Object} promptActions - @see handlePrompt
* @returns {Promise} - A promise which resolves once the prompt has been
* closed.
*/
async handleNextPrompt(parent, promptOptions, promptActions) {
let dialog = await this.waitForPrompt(parent, promptOptions);
return this.handlePrompt(dialog, promptActions);
},
/**
* Interact with an existing prompt and close it.
* @param {Dialog} dialog - The dialog instance associated with the prompt.
* @param {Object} [actions] - Options on how to interact with the
* prompt and how to close it.
* @param {Boolean} [actions.checkboxState] - Set the checkbox state.
* true = checked, false = unchecked.
* @param {Number} [actions.buttonNumClick] - Which button to click to close
* the prompt.
* @param {String} [actions.loginInput] - Input text for the login text field.
* This field is also used for text input for the "prompt" type.
* @param {String} [actions.passwordInput] - Input text for the password text
* field.
* @returns {Promise} - A promise which resolves once the prompt has been
* closed.
*/
handlePrompt(
dialog,
{
checkboxState = null,
buttonNumClick = 0,
loginInput = null,
passwordInput = null,
} = {}
) {
let promptClosePromise;
// Get parent window to listen for prompt close event
let win = dialog.ui.prompt?.opener;
if (win) {
promptClosePromise = BrowserTestUtils.waitForEvent(
win,
"DOMModalDialogClosed"
);
} else {
// We don't have a parent, wait for window close instead
promptClosePromise = BrowserTestUtils.windowClosed(dialog.ui.prompt);
}
if (typeof checkboxState == "boolean") {
dialog.ui.checkbox.checked = checkboxState;
}
if (loginInput != null) {
dialog.ui.loginTextbox.value = loginInput;
}
if (passwordInput != null) {
dialog.ui.password1Textbox.value = passwordInput;
}
let button = dialog.ui["button" + buttonNumClick];
if (!button) {
throw new Error("Could not find button with index " + buttonNumClick);
}
button.click();
return promptClosePromise;
},
/**
* Wait for a prompt from nsIPrompt or nsIPromptsService to open.
* @param {Browser|Window} [parent] - Parent of the prompt. This can be either
* the parent window or the browser. For tab prompts, if given a window, the
* currently selected browser in that window will be used.
* If not given a parent, the method will return on prompts of any window.
* @param {Object} attrs - The prompt attributes to filter for.
* @param {Number} attrs.modalType - Whether the expected prompt is a content, tab or window prompt.
* nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
* @param {String} [attrs.promptType] - Common dialog type of the prompt to filter for.
* @see {@link CommonDialog} for possible prompt types.
* @returns {Promise<CommonDialog>} - A Promise which resolves with a dialog
* object once the prompt has loaded.
*/
async waitForPrompt(parent, { modalType, promptType = null } = {}) {
if (!modalType) {
throw new Error("modalType is mandatory");
}
// Get window by browser or browser by window, depending on what is passed
// via the parent arg. If the caller passes parent=null, both will be null.
let parentWindow;
let parentBrowser;
if (parent) {
if (Element.isInstance(parent)) {
// Parent is browser
parentBrowser = parent;
parentWindow = parentBrowser.ownerGlobal;
} else if (parent.isChromeWindow) {
// Parent is window
parentWindow = parent;
parentBrowser = parentWindow.gBrowser?.selectedBrowser;
} else {
throw new Error("Invalid parent. Expected browser or dom window");
}
}
let topic = "common-dialog-loaded";
let dialog;
await TestUtils.topicObserved(topic, subject => {
// If we are not given a browser, use the currently selected browser of the window
let browser =
parentBrowser || subject.ownerGlobal.gBrowser?.selectedBrowser;
// Is not associated with given parent window, skip
if (parentWindow && subject.opener !== parentWindow) {
return false;
}
// For tab prompts, ensure that the associated browser matches.
if (browser && modalType == Services.prompt.MODAL_TYPE_TAB) {
let dialogBox = parentWindow.gBrowser.getTabDialogBox(browser);
let hasMatchingDialog = dialogBox
.getTabDialogManager()
._dialogs.some(
d => d._frame?.browsingContext == subject.browsingContext
);
if (!hasMatchingDialog) {
return false;
}
}
if (browser && modalType == Services.prompt.MODAL_TYPE_CONTENT) {
let dialogBox = parentWindow.gBrowser.getTabDialogBox(browser);
let hasMatchingDialog = dialogBox
.getContentDialogManager()
._dialogs.some(
d => d._frame?.browsingContext == subject.browsingContext
);
if (!hasMatchingDialog) {
return false;
}
}
// subject is the window object of the prompt which has a Dialog object
// attached.
dialog = subject.Dialog;
// Not the modalType we're looking for.
// For window prompts dialog.args.modalType is undefined.
if (dialog.args.modalType !== modalType) {
return false;
}
// Not the promptType we're looking for.
if (promptType && dialog.args.promptType !== promptType) {
return false;
}
// Prompt found
return true;
});
return dialog;
},
};