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/prompt_common.js | 445 +++++++++++++++++++++++ 1 file changed, 445 insertions(+) create mode 100644 toolkit/components/prompts/test/prompt_common.js (limited to 'toolkit/components/prompts/test/prompt_common.js') 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; + }; + }, + } + ); +} -- cgit v1.2.3