summaryrefslogtreecommitdiffstats
path: root/toolkit/components/prompts/test/prompt_common.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/prompts/test/prompt_common.js
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/prompts/test/prompt_common.js')
-rw-r--r--toolkit/components/prompts/test/prompt_common.js445
1 files changed, 445 insertions, 0 deletions
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;
+ };
+ },
+ }
+ );
+}