summaryrefslogtreecommitdiffstats
path: root/toolkit/components/prompts/test/chromeScript.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/prompts/test/chromeScript.js')
-rw-r--r--toolkit/components/prompts/test/chromeScript.js353
1 files changed, 353 insertions, 0 deletions
diff --git a/toolkit/components/prompts/test/chromeScript.js b/toolkit/components/prompts/test/chromeScript.js
new file mode 100644
index 0000000000..5bdf3606d4
--- /dev/null
+++ b/toolkit/components/prompts/test/chromeScript.js
@@ -0,0 +1,353 @@
+/* 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 => {
+ info("Received handlePrompt message");
+ handlePromptWhenItAppears(msg.action, msg.modalType, msg.isSelect);
+});
+
+async function handlePromptWhenItAppears(action, modalType, isSelect) {
+ try {
+ if (!(await handlePrompt(action, modalType, isSelect))) {
+ setTimeout(
+ () => this.handlePromptWhenItAppears(action, modalType, isSelect),
+ 100
+ );
+ }
+ } catch (e) {
+ info(`handlePromptWhenItAppears: exception: ${e}`);
+ }
+}
+
+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) {
+ info(`handlePrompt: modalType=${modalType}`);
+
+ 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) {
+ info("handlePrompt: no prompt found. retrying...");
+ return false; // try again in a bit
+ }
+
+ ui = prompts[0].Dialog.ui;
+ checkTabModal(prompts[0], gBrowser.selectedBrowser);
+ } else {
+ let doc = getDialogDoc();
+ if (!doc) {
+ info("handlePrompt: no document found. retrying...");
+ 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") {
+ info(`handlePrompt: wait for dialogClosed`);
+ await dialogClosed;
+ }
+
+ info(`handlePrompt: send promptHandled`);
+ 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 : "<null>");
+ }
+
+ 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) {
+ info(`dismissPrompt: action=${JSON.stringify(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.contentViewer.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;
+}