From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- toolkit/components/prompts/test/.eslintrc.js | 8 + .../prompts/test/PromptTestUtils.sys.mjs | 237 ++++ .../components/prompts/test/bug619644_inner.html | 7 + .../components/prompts/test/bug625187_iframe.html | 16 + toolkit/components/prompts/test/chrome.toml | 14 + toolkit/components/prompts/test/chromeScript.js | 341 +++++ toolkit/components/prompts/test/mochitest.toml | 26 + toolkit/components/prompts/test/prompt_common.js | 445 +++++++ .../components/prompts/test/test_bug619644.html | 74 ++ .../components/prompts/test/test_bug620145.html | 96 ++ .../components/prompts/test/test_dom_prompts.html | 207 ++++ .../prompts/test/test_modal_prompts.html | 1305 ++++++++++++++++++++ .../components/prompts/test/test_modal_select.html | 138 +++ .../prompts/test/test_subresources_prompts.html | 200 +++ 14 files changed, 3114 insertions(+) create mode 100644 toolkit/components/prompts/test/.eslintrc.js create mode 100644 toolkit/components/prompts/test/PromptTestUtils.sys.mjs create mode 100644 toolkit/components/prompts/test/bug619644_inner.html create mode 100644 toolkit/components/prompts/test/bug625187_iframe.html create mode 100644 toolkit/components/prompts/test/chrome.toml create mode 100644 toolkit/components/prompts/test/chromeScript.js create mode 100644 toolkit/components/prompts/test/mochitest.toml create mode 100644 toolkit/components/prompts/test/prompt_common.js create mode 100644 toolkit/components/prompts/test/test_bug619644.html create mode 100644 toolkit/components/prompts/test/test_bug620145.html create mode 100644 toolkit/components/prompts/test/test_dom_prompts.html create mode 100644 toolkit/components/prompts/test/test_modal_prompts.html create mode 100644 toolkit/components/prompts/test/test_modal_select.html create mode 100644 toolkit/components/prompts/test/test_subresources_prompts.html (limited to 'toolkit/components/prompts/test') diff --git a/toolkit/components/prompts/test/.eslintrc.js b/toolkit/components/prompts/test/.eslintrc.js new file mode 100644 index 0000000000..af973e82fe --- /dev/null +++ b/toolkit/components/prompts/test/.eslintrc.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + rules: { + // ownerGlobal doesn't exist in content privileged windows. + "mozilla/use-ownerGlobal": "off", + }, +}; diff --git a/toolkit/components/prompts/test/PromptTestUtils.sys.mjs b/toolkit/components/prompts/test/PromptTestUtils.sys.mjs new file mode 100644 index 0000000000..138c61b189 --- /dev/null +++ b/toolkit/components/prompts/test/PromptTestUtils.sys.mjs @@ -0,0 +1,237 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Utility module for tests to interact with prompts spawned by nsIPrompt or + * nsIPromptService. + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { BrowserTestUtils } from "resource://testing-common/BrowserTestUtils.sys.mjs"; +import { TestUtils } from "resource://testing-common/TestUtils.sys.mjs"; + +const kPrefs = {}; + +// Whether prompts with modal type TAB are shown as SubDialog (true) or +// TabModalPrompt (false). +XPCOMUtils.defineLazyPreferenceGetter( + kPrefs, + "tabPromptSubDialogEnabled", + "prompts.tabChromePromptSubDialog", + false +); + +// Whether web content prompts (alert etc.) are shown as SubDialog (true) +// or TabModalPrompt (false) +XPCOMUtils.defineLazyPreferenceGetter( + kPrefs, + "contentPromptSubDialogEnabled", + "prompts.contentPromptSubDialog", + false +); + +function isCommonDialog(modalType) { + return ( + modalType === Services.prompt.MODAL_TYPE_WINDOW || + (kPrefs.tabPromptSubDialogEnabled && + modalType === Services.prompt.MODAL_TYPE_TAB) || + (kPrefs.contentPromptSubDialogEnabled && + modalType === Services.prompt.MODAL_TYPE_CONTENT) + ); +} + +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; + if (isCommonDialog(dialog.args.modalType)) { + win = dialog.ui.prompt?.opener; + } else { + // Tab prompts should always have a parent window + win = dialog.ui.prompt.win; + } + + 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. + * @param {String} [attrs.promptType] - Common dialog type of the prompt to filter for. + * @see {@link CommonDialog} for possible prompt types. + * @returns {Promise} - 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 = isCommonDialog(modalType) + ? "common-dialog-loaded" + : "tabmodal-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; + if (isCommonDialog(modalType)) { + // 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; + } else { + // subject is the tabprompt dom node + // Get the full prompt object which has the dialog object + let prompt = browser.tabModalPromptBox.getPrompt(subject); + + // Is not associated with given parent browser, skip. + if (!prompt) { + return false; + } + + dialog = prompt.Dialog; + } + + // Not the modalType we're looking for. + // For window prompts dialog.args.modalType is undefined. + if (isCommonDialog(modalType) && 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; + }, +}; diff --git a/toolkit/components/prompts/test/bug619644_inner.html b/toolkit/components/prompts/test/bug619644_inner.html new file mode 100644 index 0000000000..cecfa78bad --- /dev/null +++ b/toolkit/components/prompts/test/bug619644_inner.html @@ -0,0 +1,7 @@ +

Original content

+ diff --git a/toolkit/components/prompts/test/bug625187_iframe.html b/toolkit/components/prompts/test/bug625187_iframe.html new file mode 100644 index 0000000000..740d59a617 --- /dev/null +++ b/toolkit/components/prompts/test/bug625187_iframe.html @@ -0,0 +1,16 @@ + + + Test for Bug 625187 - the iframe + + + +

+

+ + diff --git a/toolkit/components/prompts/test/chrome.toml b/toolkit/components/prompts/test/chrome.toml new file mode 100644 index 0000000000..55f2617c0d --- /dev/null +++ b/toolkit/components/prompts/test/chrome.toml @@ -0,0 +1,14 @@ +[DEFAULT] +support-files = [ + "prompt_common.js", + "chromeScript.js", +] + +["test_modal_prompts.html"] +skip-if = [ + "os == 'android'", #android: TIMED_OUT + "os == 'linux' && (debug || asan || tsan)", +] + +["test_modal_select.html"] +skip-if = ["os == 'android'"] #android: TIMED_OUT diff --git a/toolkit/components/prompts/test/chromeScript.js b/toolkit/components/prompts/test/chromeScript.js new file mode 100644 index 0000000000..1e71224f6b --- /dev/null +++ b/toolkit/components/prompts/test/chromeScript.js @@ -0,0 +1,341 @@ +/* eslint-env mozilla/chrome-script */ + +const { clearInterval, setInterval, setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +const { BrowserTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); + +var tabSubDialogsEnabled = Services.prefs.getBoolPref( + "prompts.tabChromePromptSubDialog", + false +); + +var contentPromptSubdialogsEnabled = Services.prefs.getBoolPref( + "prompts.contentPromptSubDialog", + false +); + +// Define these to make EventUtils happy. +let window = this; +let parent = {}; + +let EventUtils = {}; +Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", + EventUtils +); + +addMessageListener("handlePrompt", msg => { + handlePromptWhenItAppears(msg.action, msg.modalType, msg.isSelect); +}); + +async function handlePromptWhenItAppears(action, modalType, isSelect) { + if (!(await handlePrompt(action, modalType, isSelect))) { + setTimeout( + () => this.handlePromptWhenItAppears(action, modalType, isSelect), + 100 + ); + } +} + +function checkTabModal(prompt, browser) { + let doc = browser.ownerDocument; + + let { bottom: toolboxBottom } = doc + .getElementById("navigator-toolbox") + .getBoundingClientRect(); + + let { mainContainer } = prompt.ui; + + let { x, y } = mainContainer.getBoundingClientRect(); + ok(y > 0, "Container should have y > 0"); + // Inset by 1px since the corner point doesn't return the frame due to the + // border-radius. + is( + doc.elementFromPoint(x + 1, y + 1).parentNode, + mainContainer, + "Check tabmodalprompt is visible" + ); + + info("Click to the left of the dialog over the content area"); + isnot( + doc.elementFromPoint(x - 10, y + 50), + browser, + "Check clicks on the content area don't go to the browser" + ); + is( + doc.elementFromPoint(x - 10, y + 50), + prompt.element, + "Check clicks on the content area go to the prompt dialog background" + ); + + if (prompt.args.modalType == Ci.nsIPrompt.MODAL_TYPE_TAB) { + ok( + y <= toolboxBottom - 5, + "Dialog should overlap the toolbox by at least 5px" + ); + } else { + ok(y >= toolboxBottom, "Dialog must not overlap with toolbox."); + } + + ok( + browser.hasAttribute("tabmodalPromptShowing"), + "Check browser has @tabmodalPromptShowing" + ); +} + +async function handlePrompt(action, modalType, isSelect) { + let ui; + let browserWin = Services.wm.getMostRecentWindow("navigator:browser"); + + if ( + (!contentPromptSubdialogsEnabled && + modalType === Services.prompt.MODAL_TYPE_CONTENT) || + (!tabSubDialogsEnabled && modalType === Services.prompt.MODAL_TYPE_TAB) + ) { + let gBrowser = browserWin.gBrowser; + let promptManager = gBrowser.getTabModalPromptBox(gBrowser.selectedBrowser); + let prompts = promptManager.listPrompts(); + if (!prompts.length) { + return false; // try again in a bit + } + + ui = prompts[0].Dialog.ui; + checkTabModal(prompts[0], gBrowser.selectedBrowser); + } else { + let doc = getDialogDoc(); + if (!doc) { + return false; // try again in a bit + } + + if (isSelect) { + ui = doc; + } else { + ui = doc.defaultView.Dialog.ui; + } + } + + let dialogClosed = BrowserTestUtils.waitForEvent( + browserWin, + "DOMModalDialogClosed" + ); + + let promptState; + if (isSelect) { + promptState = getSelectState(ui); + dismissSelect(ui, action); + } else { + promptState = getPromptState(ui); + dismissPrompt(ui, action); + } + + // Wait until the prompt has been closed before sending callback msg. + // Unless the test explicitly doesn't request a button click. + if (action.buttonClick !== "none") { + await dialogClosed; + } + + sendAsyncMessage("promptHandled", { promptState }); + return true; +} + +function getSelectState(ui) { + let listbox = ui.getElementById("list"); + + let state = {}; + state.msg = ui.getElementById("info.txt").value; + state.selectedIndex = listbox.selectedIndex; + state.items = []; + + for (let i = 0; i < listbox.itemCount; i++) { + let item = listbox.getItemAtIndex(i).label; + state.items.push(item); + } + + return state; +} + +function getPromptState(ui) { + let state = {}; + state.msg = ui.infoBody.textContent; + state.infoRowHidden = ui.infoRow?.hidden || false; + state.titleHidden = ui.infoTitle.hidden; + state.textHidden = ui.loginContainer.hidden; + state.passHidden = ui.password1Container.hidden; + state.checkHidden = ui.checkboxContainer.hidden; + state.checkMsg = state.checkHidden ? "" : ui.checkbox.label; + state.checked = state.checkHidden ? false : ui.checkbox.checked; + // TabModalPrompts don't have an infoIcon + state.iconClass = ui.infoIcon ? ui.infoIcon.className : null; + state.textValue = ui.loginTextbox.value; + state.passValue = ui.password1Textbox.value; + + state.butt0Label = ui.button0.label; + state.butt1Label = ui.button1.label; + state.butt2Label = ui.button2.label; + + state.butt0Disabled = ui.button0.disabled; + state.butt1Disabled = ui.button1.disabled; + state.butt2Disabled = ui.button2.disabled; + + function isDefaultButton(b) { + return b.hasAttribute("default") && b.getAttribute("default") == "true"; + } + state.defButton0 = isDefaultButton(ui.button0); + state.defButton1 = isDefaultButton(ui.button1); + state.defButton2 = isDefaultButton(ui.button2); + + let e = Services.focus.focusedElement; + + if (e == null) { + state.focused = null; + } else if (ui.button0.isSameNode(e)) { + state.focused = "button0"; + } else if (ui.button1.isSameNode(e)) { + state.focused = "button1"; + } else if (ui.button2.isSameNode(e)) { + state.focused = "button2"; + } else if (e.isSameNode(ui.loginTextbox)) { + state.focused = "textField"; + } else if (e.isSameNode(ui.password1Textbox)) { + state.focused = "passField"; + } else if (ui.infoBody.isSameNode(e)) { + state.focused = "infoBody"; + } else { + state.focused = + "ERROR: unexpected element focused: " + (e ? e.localName : ""); + } + + let treeOwner = + ui.prompt && ui.prompt.docShell && ui.prompt.docShell.treeOwner; + if (treeOwner && treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)) { + // Check that the dialog is modal, chrome and dependent; + // We can't just check window.opener because that'll be + // a content window, which therefore isn't exposed (it'll lie and + // be null). + let flags = treeOwner.getInterface(Ci.nsIAppWindow).chromeFlags; + state.chrome = (flags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_CHROME) != 0; + state.dialog = (flags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) != 0; + state.chromeDependent = + (flags & Ci.nsIWebBrowserChrome.CHROME_DEPENDENT) != 0; + let wbc = treeOwner.getInterface(Ci.nsIWebBrowserChrome); + state.isWindowModal = wbc.isWindowModal(); + } + + // Check the dialog is a common dialog document and has been embedded. + let isEmbedded = !!ui.prompt?.docShell?.chromeEventHandler; + let isCommonDialogDoc = + getDialogDoc()?.location.href.includes("commonDialog.xhtml"); + state.isSubDialogPrompt = isCommonDialogDoc && isEmbedded; + state.showCallerOrigin = ui.prompt.args.showCallerOrigin; + + return state; +} + +function dismissSelect(ui, action) { + let dialog = ui.getElementsByTagName("dialog")[0]; + let listbox = ui.getElementById("list"); + + if (action.selectItem) { + listbox.selectedIndex = 1; + } + + if (action.buttonClick == "ok") { + dialog.acceptDialog(); + } else if (action.buttonClick == "cancel") { + dialog.cancelDialog(); + } +} + +function dismissPrompt(ui, action) { + if (action.setCheckbox) { + // Annoyingly, the prompt code is driven by oncommand. + ui.checkbox.checked = true; + ui.checkbox.doCommand(); + } + + if ("textField" in action) { + ui.loginTextbox.setAttribute("value", action.textField); + } + + if ("passField" in action) { + ui.password1Textbox.setAttribute("value", action.passField); + } + + switch (action.buttonClick) { + case "ok": + case 0: + ui.button0.click(); + break; + case "cancel": + case 1: + ui.button1.click(); + break; + case 2: + ui.button2.click(); + break; + case "ESC": + // XXX This is assuming tab-modal. + let browserWin = Services.wm.getMostRecentWindow("navigator:browser"); + EventUtils.synthesizeKey("KEY_Escape", {}, browserWin); + break; + case "pollOK": + // Buttons are disabled at the moment, poll until they're reenabled. + // Can't use setInterval here, because the window's in a modal state + // and thus DOM events are suppressed. + let interval = setInterval(() => { + if (ui.button0.disabled) { + return; + } + ui.button0.click(); + clearInterval(interval); + }, 100); + break; + case "none": + break; + + default: + throw new Error("dismissPrompt action listed unknown button."); + } +} + +function getDialogDoc() { + // Trudge through all the open windows, until we find the one + // that has either commonDialog.xhtml or selectDialog.xhtml loaded. + // var enumerator = Services.wm.getEnumerator("navigator:browser"); + for (let { docShell } of Services.wm.getEnumerator(null)) { + var containedDocShells = docShell.getAllDocShellsInSubtree( + docShell.typeChrome, + docShell.ENUMERATE_FORWARDS + ); + for (let childDocShell of containedDocShells) { + // Get the corresponding document for this docshell + // We don't want it if it's not done loading. + if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) { + continue; + } + var childDoc = childDocShell.docViewer.DOMDocument; + + if ( + childDoc.location.href != + "chrome://global/content/commonDialog.xhtml" && + childDoc.location.href != "chrome://global/content/selectDialog.xhtml" + ) { + continue; + } + + // We're expecting the dialog to be focused. If it's not yet, try later. + // (In particular, this is needed on Linux to reliably check focused elements.) + if (Services.focus.focusedWindow != childDoc.defaultView) { + continue; + } + + return childDoc; + } + } + + return null; +} diff --git a/toolkit/components/prompts/test/mochitest.toml b/toolkit/components/prompts/test/mochitest.toml new file mode 100644 index 0000000000..5c18b889c6 --- /dev/null +++ b/toolkit/components/prompts/test/mochitest.toml @@ -0,0 +1,26 @@ +[DEFAULT] +support-files = [ + "../../passwordmgr/test/authenticate.sjs", + "bug619644_inner.html", + "bug625187_iframe.html", + "prompt_common.js", + "chromeScript.js", +] + +["test_bug619644.html"] +skip-if = ["os == 'android'"] # No tab prompts on android + +["test_bug620145.html"] +skip-if = ["os == 'android'"] #TIMED_OUT + +["test_dom_prompts.html"] +skip-if = ["os == 'android'"] #android: bug 1267092 + +["test_subresources_prompts.html"] +skip-if = [ + "os == 'android'", + "verify", + "http3", + "http2", +] +fail-if = ["xorigin"] diff --git a/toolkit/components/prompts/test/prompt_common.js b/toolkit/components/prompts/test/prompt_common.js new file mode 100644 index 0000000000..4b3a2262aa --- /dev/null +++ b/toolkit/components/prompts/test/prompt_common.js @@ -0,0 +1,445 @@ +const { Cc, Ci, Cu: ChromeUtils } = SpecialPowers; + +/** + * Converts a property bag to object. + * @param {nsIPropertyBag} bag - The property bag to convert + * @returns {Object} - The object representation of the nsIPropertyBag + */ +function propBagToObject(bag) { + if (!(bag instanceof Ci.nsIPropertyBag)) { + throw new TypeError("Not a property bag"); + } + let result = {}; + for (let { name, value } of bag.enumerator) { + result[name] = value; + } + return result; +} + +var modalType; +var tabSubDialogsEnabled = SpecialPowers.Services.prefs.getBoolPref( + "prompts.tabChromePromptSubDialog", + false +); +var contentSubDialogsEnabled = SpecialPowers.Services.prefs.getBoolPref( + "prompts.contentPromptSubDialog", + false +); +var isSelectDialog = false; +var isOSX = "nsILocalFileMac" in SpecialPowers.Ci; +var isE10S = SpecialPowers.Services.appinfo.processType == 2; + +var gChromeScript = SpecialPowers.loadChromeScript( + SimpleTest.getTestFileURL("chromeScript.js") +); +SimpleTest.registerCleanupFunction(() => gChromeScript.destroy()); + +async function runPromptCombinations(window, testFunc) { + let util = new PromptTestUtil(window); + let run = () => { + info( + `Running tests (modalType=${modalType}, usePromptService=${util.usePromptService}, useBrowsingContext=${util.useBrowsingContext}, useAsync=${util.useAsync})` + ); + return testFunc(util); + }; + + // Prompt service with dom window parent only supports window prompts + util.usePromptService = true; + util.useBrowsingContext = false; + util.modalType = Ci.nsIPrompt.MODAL_TYPE_WINDOW; + modalType = util.modalType; + util.useAsync = false; + await run(); + + let modalTypes = [ + Ci.nsIPrompt.MODAL_TYPE_WINDOW, + Ci.nsIPrompt.MODAL_TYPE_TAB, + Ci.nsIPrompt.MODAL_TYPE_CONTENT, + ]; + + for (let type of modalTypes) { + util.modalType = type; + modalType = type; + + // Prompt service with browsing context sync + util.usePromptService = true; + util.useBrowsingContext = true; + util.useAsync = false; + await run(); + + // Prompt service with browsing context async + util.usePromptService = true; + util.useBrowsingContext = true; + util.useAsync = true; + await run(); + + // nsIPrompt + // modalType is set via nsIWritablePropertyBag (legacy) + util.usePromptService = false; + util.useBrowsingContext = false; + util.useAsync = false; + await run(); + } +} + +class PromptTestUtil { + constructor(window) { + this.window = window; + this.browsingContext = + SpecialPowers.wrap(window).windowGlobalChild.browsingContext; + this.promptService = SpecialPowers.Services.prompt; + this.nsPrompt = Cc["@mozilla.org/prompter;1"] + .getService(Ci.nsIPromptFactory) + .getPrompt(window, Ci.nsIPrompt); + + this.usePromptService = null; + this.useBrowsingContext = null; + this.useAsync = null; + this.modalType = null; + } + + get _prompter() { + if (this.usePromptService) { + return this.promptService; + } + return this.nsPrompt; + } + + async prompt(funcName, promptArgs) { + if ( + this.useBrowsingContext == null || + this.usePromptService == null || + this.useAsync == null || + this.modalType == null + ) { + throw new Error("Not initialized"); + } + let args = []; + if (this.usePromptService) { + if (this.useBrowsingContext) { + if (this.useAsync) { + funcName = `async${funcName[0].toUpperCase()}${funcName.substring( + 1 + )}`; + } else { + funcName += "BC"; + } + args = [this.browsingContext, this.modalType]; + } else { + args = [this.window]; + } + } else { + let bag = this.nsPrompt.QueryInterface(Ci.nsIWritablePropertyBag2); + bag.setPropertyAsUint32("modalType", this.modalType); + } + // Append the prompt arguments + args = args.concat(promptArgs); + + let interfaceName = this.usePromptService ? "Services.prompt" : "prompt"; + ok( + this._prompter[funcName], + `${interfaceName} should have method ${funcName}.` + ); + + info(`Calling ${interfaceName}.${funcName}(${args})`); + let result = this._prompter[funcName](...args); + is( + this.useAsync, + result != null && + result.constructor != null && + result.constructor.name === "Promise", + "If method is async it should return a promise." + ); + + if (this.useAsync) { + let propBag = await result; + return propBag && propBagToObject(propBag); + } + return result; + } +} + +function onloadPromiseFor(id) { + var iframe = document.getElementById(id); + return new Promise(resolve => { + iframe.addEventListener( + "load", + function (e) { + resolve(true); + }, + { once: true } + ); + }); +} + +/** + * Take an action on the next prompt that appears without checking the state in advance. + * This is useful when the action doesn't depend on which prompt is shown and you + * are expecting multiple prompts at once in an indeterminate order. + * If you know the state of the prompt you expect you should use `handlePrompt` instead. + * @param {object} action defining how to handle the prompt + * @returns {Promise} resolving with the prompt state. + */ +function handlePromptWithoutChecks(action) { + return new Promise(resolve => { + gChromeScript.addMessageListener("promptHandled", function handled(msg) { + gChromeScript.removeMessageListener("promptHandled", handled); + resolve(msg.promptState); + }); + gChromeScript.sendAsyncMessage("handlePrompt", { action, modalType }); + }); +} + +async function handlePrompt(state, action) { + let actualState = await handlePromptWithoutChecks(action); + checkPromptState(actualState, state); +} + +function checkPromptState(promptState, expectedState) { + info(`checkPromptState: Expected: ${expectedState.msg}`); + // XXX check title? OS X has title in content + is(promptState.msg, expectedState.msg, "Checking expected message"); + + let isOldContentPrompt = + !promptState.isSubDialogPrompt && + modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT; + + if (isOldContentPrompt && !promptState.showCallerOrigin) { + ok( + promptState.titleHidden, + "The title should be hidden for content prompts opened with tab modal prompt." + ); + } else if ( + isOSX || + promptState.isSubDialogPrompt || + promptState.showCallerOrigin + ) { + ok( + !promptState.titleHidden, + "Checking title always visible on OS X or when opened with common dialog" + ); + } else { + is( + promptState.titleHidden, + expectedState.titleHidden, + "Checking title visibility" + ); + } + is( + promptState.textHidden, + expectedState.textHidden, + "Checking textbox visibility" + ); + is( + promptState.passHidden, + expectedState.passHidden, + "Checking passbox visibility" + ); + is( + promptState.checkHidden, + expectedState.checkHidden, + "Checking checkbox visibility" + ); + is(promptState.checkMsg, expectedState.checkMsg, "Checking checkbox label"); + is(promptState.checked, expectedState.checked, "Checking checkbox checked"); + if ( + modalType === Ci.nsIPrompt.MODAL_TYPE_WINDOW || + (modalType === Ci.nsIPrompt.MODAL_TYPE_TAB && tabSubDialogsEnabled) + ) { + is( + promptState.iconClass, + expectedState.iconClass, + "Checking expected icon CSS class" + ); + } + is(promptState.textValue, expectedState.textValue, "Checking textbox value"); + is(promptState.passValue, expectedState.passValue, "Checking passbox value"); + + if (expectedState.butt0Label) { + is( + promptState.butt0Label, + expectedState.butt0Label, + "Checking accept-button label" + ); + } + if (expectedState.butt1Label) { + is( + promptState.butt1Label, + expectedState.butt1Label, + "Checking cancel-button label" + ); + } + if (expectedState.butt2Label) { + is( + promptState.butt2Label, + expectedState.butt2Label, + "Checking extra1-button label" + ); + } + + // For prompts with a time-delay button. + if (expectedState.butt0Disabled) { + is(promptState.butt0Disabled, true, "Checking accept-button is disabled"); + is( + promptState.butt1Disabled, + false, + "Checking cancel-button isn't disabled" + ); + } + + is( + promptState.defButton0, + expectedState.defButton == "button0", + "checking button0 default" + ); + is( + promptState.defButton1, + expectedState.defButton == "button1", + "checking button1 default" + ); + is( + promptState.defButton2, + expectedState.defButton == "button2", + "checking button2 default" + ); + + if ( + isOSX && + expectedState.focused && + expectedState.focused.startsWith("button") && + !promptState.infoRowHidden + ) { + is( + promptState.focused, + "infoBody", + "buttons don't focus on OS X, but infoBody does instead" + ); + } else { + is(promptState.focused, expectedState.focused, "Checking focused element"); + } + + if (expectedState.hasOwnProperty("chrome")) { + is( + promptState.chrome, + expectedState.chrome, + "Dialog should be opened as chrome" + ); + } + if (expectedState.hasOwnProperty("dialog")) { + is( + promptState.dialog, + expectedState.dialog, + "Dialog should be opened as a dialog" + ); + } + if (expectedState.hasOwnProperty("chromeDependent")) { + is( + promptState.chromeDependent, + expectedState.chromeDependent, + "Dialog should be opened as dependent" + ); + } + if (expectedState.hasOwnProperty("isWindowModal")) { + is( + promptState.isWindowModal, + expectedState.isWindowModal, + "Dialog should be modal" + ); + } +} + +function checkEchoedAuthInfo(expectedState, browsingContext) { + return SpecialPowers.spawn( + browsingContext, + [expectedState.user, expectedState.pass], + (expectedUser, expectedPass) => { + let doc = this.content.document; + + // The server echos back the HTTP auth info it received. + let username = doc.getElementById("user").textContent; + let password = doc.getElementById("pass").textContent; + let authok = doc.getElementById("ok").textContent; + + Assert.equal(authok, "PASS", "Checking for successful authentication"); + Assert.equal(username, expectedUser, "Checking for echoed username"); + Assert.equal(password, expectedPass, "Checking for echoed password"); + } + ); +} + +/** + * Create a Proxy to relay method calls on an nsIAuthPrompt[2] prompter to a chrome script which can + * perform the calls in the parent. Out and inout params will be copied back from the parent to + * content. + * + * @param chromeScript The reference to the chrome script that will listen to `proxyPrompter` + * messages in the parent and call the `methodName` method. + * The return value from the message handler should be an object with properties: + * `rv` - containing the return value of the method call. + * `args` - containing the array of arguments passed to the method since out or inout ones could have + * been modified. + */ +function PrompterProxy(chromeScript) { + return new Proxy( + {}, + { + get(target, prop, receiver) { + return (...args) => { + // Array of indices of out/inout params to copy from the parent back to the caller. + let outParams = []; + + switch (prop) { + case "prompt": { + outParams = [/* result */ 5]; + break; + } + case "promptAuth": { + outParams = []; + break; + } + case "promptPassword": + case "asyncPromptPassword": { + outParams = [/* pwd */ 4]; + break; + } + case "promptUsernameAndPassword": + case "asyncPromptUsernameAndPassword": { + outParams = [/* user */ 4, /* pwd */ 5]; + break; + } + default: { + throw new Error("Unknown nsIAuthPrompt method"); + } + } + + let result; + chromeScript + .sendQuery("proxyPrompter", { + args, + methodName: prop, + }) + .then(val => { + result = val; + }); + SpecialPowers.Services.tm.spinEventLoopUntil( + "Test(prompt_common.js:get)", + () => result + ); + + for (let outParam of outParams) { + // Copy the out or inout param value over the original + args[outParam].value = result.args[outParam].value; + } + + if (prop == "promptAuth") { + args[2].username = result.args[2].username; + args[2].password = result.args[2].password; + args[2].domain = result.args[2].domain; + } + + return result.rv; + }; + }, + } + ); +} diff --git a/toolkit/components/prompts/test/test_bug619644.html b/toolkit/components/prompts/test/test_bug619644.html new file mode 100644 index 0000000000..2b424c71a6 --- /dev/null +++ b/toolkit/components/prompts/test/test_bug619644.html @@ -0,0 +1,74 @@ + + + + + Test for Bug 619644 + + + + + + + +Mozilla Bug 619644 +
+
+
+ + diff --git a/toolkit/components/prompts/test/test_bug620145.html b/toolkit/components/prompts/test/test_bug620145.html new file mode 100644 index 0000000000..3894a528ae --- /dev/null +++ b/toolkit/components/prompts/test/test_bug620145.html @@ -0,0 +1,96 @@ + + + Test for Bug 620145 + + + + + + + +Mozilla Bug 620145 +
+
+ +
+ This is a short piece of text used for testing that mouse selecting is + stopped when an alert appears. +
+
+ This is another short piece of text used for testing that mouse selecting is + stopped when an alert appears. +
+ + + + + + diff --git a/toolkit/components/prompts/test/test_dom_prompts.html b/toolkit/components/prompts/test/test_dom_prompts.html new file mode 100644 index 0000000000..95595b8df2 --- /dev/null +++ b/toolkit/components/prompts/test/test_dom_prompts.html @@ -0,0 +1,207 @@ + + + Test for DOM prompts + + + + + + + +
+
+ + + + + diff --git a/toolkit/components/prompts/test/test_modal_prompts.html b/toolkit/components/prompts/test/test_modal_prompts.html new file mode 100644 index 0000000000..7e1aafc8c9 --- /dev/null +++ b/toolkit/components/prompts/test/test_modal_prompts.html @@ -0,0 +1,1305 @@ + + + + + Modal Prompts Test + + + + +Prompter tests: modal prompts +

+ + + +
+
+
+ + diff --git a/toolkit/components/prompts/test/test_modal_select.html b/toolkit/components/prompts/test/test_modal_select.html new file mode 100644 index 0000000000..27688cf329 --- /dev/null +++ b/toolkit/components/prompts/test/test_modal_select.html @@ -0,0 +1,138 @@ + + + + Modal Prompts Test + + + + +Prompter tests: modal prompts +

+ + + +
+
+
+ + diff --git a/toolkit/components/prompts/test/test_subresources_prompts.html b/toolkit/components/prompts/test/test_subresources_prompts.html new file mode 100644 index 0000000000..b71ad0694e --- /dev/null +++ b/toolkit/components/prompts/test/test_subresources_prompts.html @@ -0,0 +1,200 @@ + + + Test subresources prompts (Bug 625187 and bug 1230462) + + + + + + + + +Mozilla Bug 625187 +Mozilla Bug 1230462 + +

+ + + + + + + +

+
+
+
+
-- 
cgit v1.2.3