summaryrefslogtreecommitdiffstats
path: root/toolkit/components/prompts/content/tabprompts.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/prompts/content/tabprompts.sys.mjs')
-rw-r--r--toolkit/components/prompts/content/tabprompts.sys.mjs298
1 files changed, 298 insertions, 0 deletions
diff --git a/toolkit/components/prompts/content/tabprompts.sys.mjs b/toolkit/components/prompts/content/tabprompts.sys.mjs
new file mode 100644
index 0000000000..7edb024fa8
--- /dev/null
+++ b/toolkit/components/prompts/content/tabprompts.sys.mjs
@@ -0,0 +1,298 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+
+export var TabModalPrompt = class {
+ constructor(win) {
+ this.win = win;
+ let newPrompt = (this.element =
+ win.document.createElement("tabmodalprompt"));
+ win.MozXULElement.insertFTLIfNeeded("toolkit/global/tabprompts.ftl");
+ newPrompt.setAttribute("role", "dialog");
+ let randomIdSuffix = Math.random().toString(32).substring(2);
+ newPrompt.setAttribute("aria-describedby", `infoBody-${randomIdSuffix}`);
+ newPrompt.appendChild(
+ win.MozXULElement.parseXULToFragment(
+ `
+ <div class="tabmodalprompt-mainContainer" xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <div class="tabmodalprompt-topContainer">
+ <div class="tabmodalprompt-infoContainer">
+ <div class="tabmodalprompt-infoTitle infoTitle" hidden="hidden"/>
+ <div class="tabmodalprompt-infoBody infoBody" id="infoBody-${randomIdSuffix}" tabindex="-1"/>
+ </div>
+
+ <div class="tabmodalprompt-loginContainer" hidden="hidden">
+ <xul:label class="tabmodalprompt-loginLabel" data-l10n-id="tabmodalprompt-username" control="loginTextbox-${randomIdSuffix}"/>
+ <input class="tabmodalprompt-loginTextbox" id="loginTextbox-${randomIdSuffix}"/>
+ </div>
+
+ <div class="tabmodalprompt-password1Container" hidden="hidden">
+ <xul:label class="tabmodalprompt-password1Label" data-l10n-id="tabmodalprompt-password" control="password1Textbox-${randomIdSuffix}"/>
+ <input class="tabmodalprompt-password1Textbox" type="password" id="password1Textbox-${randomIdSuffix}"/>
+ </div>
+
+ <div class="tabmodalprompt-checkboxContainer" hidden="hidden">
+ <div/>
+ <xul:checkbox class="tabmodalprompt-checkbox"/>
+ </div>
+
+ <!-- content goes here -->
+ </div>
+ <div class="tabmodalprompt-buttonContainer">
+ <xul:button class="tabmodalprompt-button3" hidden="true"/>
+ <div class="tabmodalprompt-buttonSpacer"/>
+ <xul:button class="tabmodalprompt-button0" data-l10n-id="tabmodalprompt-ok-button"/>
+ <xul:button class="tabmodalprompt-button2" hidden="true"/>
+ <xul:button class="tabmodalprompt-button1" data-l10n-id="tabmodalprompt-cancel-button"/>
+ </div>
+ </div>`
+ )
+ );
+
+ this.ui = {
+ prompt: this,
+ promptContainer: this.element,
+ mainContainer: newPrompt.querySelector(".tabmodalprompt-mainContainer"),
+ loginContainer: newPrompt.querySelector(".tabmodalprompt-loginContainer"),
+ loginTextbox: newPrompt.querySelector(".tabmodalprompt-loginTextbox"),
+ loginLabel: newPrompt.querySelector(".tabmodalprompt-loginLabel"),
+ password1Container: newPrompt.querySelector(
+ ".tabmodalprompt-password1Container"
+ ),
+ password1Textbox: newPrompt.querySelector(
+ ".tabmodalprompt-password1Textbox"
+ ),
+ password1Label: newPrompt.querySelector(".tabmodalprompt-password1Label"),
+ infoContainer: newPrompt.querySelector(".tabmodalprompt-infoContainer"),
+ infoBody: newPrompt.querySelector(".tabmodalprompt-infoBody"),
+ infoTitle: newPrompt.querySelector(".tabmodalprompt-infoTitle"),
+ infoIcon: null,
+ rows: newPrompt.querySelector(".tabmodalprompt-topContainer"),
+ checkbox: newPrompt.querySelector(".tabmodalprompt-checkbox"),
+ checkboxContainer: newPrompt.querySelector(
+ ".tabmodalprompt-checkboxContainer"
+ ),
+ button3: newPrompt.querySelector(".tabmodalprompt-button3"),
+ button2: newPrompt.querySelector(".tabmodalprompt-button2"),
+ button1: newPrompt.querySelector(".tabmodalprompt-button1"),
+ button0: newPrompt.querySelector(".tabmodalprompt-button0"),
+ // focusTarget (for BUTTON_DELAY_ENABLE) not yet supported
+ };
+
+ if (AppConstants.XP_UNIX) {
+ // Reorder buttons on Linux
+ let buttonContainer = newPrompt.querySelector(
+ ".tabmodalprompt-buttonContainer"
+ );
+ buttonContainer.appendChild(this.ui.button3);
+ buttonContainer.appendChild(this.ui.button2);
+ buttonContainer.appendChild(
+ newPrompt.querySelector(".tabmodalprompt-buttonSpacer")
+ );
+ buttonContainer.appendChild(this.ui.button1);
+ buttonContainer.appendChild(this.ui.button0);
+ }
+
+ this.ui.button0.addEventListener(
+ "command",
+ this.onButtonClick.bind(this, 0)
+ );
+ this.ui.button1.addEventListener(
+ "command",
+ this.onButtonClick.bind(this, 1)
+ );
+ this.ui.button2.addEventListener(
+ "command",
+ this.onButtonClick.bind(this, 2)
+ );
+ this.ui.button3.addEventListener(
+ "command",
+ this.onButtonClick.bind(this, 3)
+ );
+ // Anonymous wrapper used here because |Dialog| doesn't exist until init() is called!
+ this.ui.checkbox.addEventListener("command", () => {
+ this.Dialog.onCheckbox();
+ });
+
+ /**
+ * Based on dialog.xml handlers
+ */
+ this.element.addEventListener(
+ "keypress",
+ event => {
+ switch (event.keyCode) {
+ case KeyEvent.DOM_VK_RETURN:
+ this.onKeyAction("default", event);
+ break;
+
+ case KeyEvent.DOM_VK_ESCAPE:
+ this.onKeyAction("cancel", event);
+ break;
+
+ default:
+ if (
+ AppConstants.platform == "macosx" &&
+ event.key == "." &&
+ event.metaKey
+ ) {
+ this.onKeyAction("cancel", event);
+ }
+ break;
+ }
+ },
+ { mozSystemGroup: true }
+ );
+
+ this.element.addEventListener(
+ "focus",
+ event => {
+ let bnum = this.args.defaultButtonNum || 0;
+ let defaultButton = this.ui["button" + bnum];
+
+ if (AppConstants.platform == "macosx") {
+ // On OS X, the default button always stays marked as such (until
+ // the entire prompt blurs).
+ defaultButton.setAttribute("default", "true");
+ } else {
+ // On other platforms, the default button is only marked as such
+ // when no other button has focus. XUL buttons on not-OSX will
+ // react to pressing enter as a command, so you can't trigger the
+ // default without tabbing to it or something that isn't a button.
+ let focusedDefault = event.originalTarget == defaultButton;
+ let someButtonFocused =
+ event.originalTarget.localName == "button" ||
+ event.originalTarget.localName == "toolbarbutton";
+ if (focusedDefault || !someButtonFocused) {
+ defaultButton.setAttribute("default", "true");
+ }
+ }
+ },
+ true
+ );
+
+ this.element.addEventListener("blur", () => {
+ // If focus shifted to somewhere else in the browser, don't make
+ // the default button look active.
+ let bnum = this.args.defaultButtonNum || 0;
+ let button = this.ui["button" + bnum];
+ button.removeAttribute("default");
+ });
+ }
+
+ init(args, linkedTab, onCloseCallback) {
+ this.args = args;
+ this.linkedTab = linkedTab;
+ this.onCloseCallback = onCloseCallback;
+
+ if (args.enableDelay) {
+ throw new Error(
+ "BUTTON_DELAY_ENABLE not yet supported for tab-modal prompts"
+ );
+ }
+
+ // Apply styling depending on modalType (content or tab prompt)
+ if (args.modalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
+ this.element.classList.add("tab-prompt");
+ } else {
+ this.element.classList.add("content-prompt");
+ }
+
+ // We need to remove the prompt when the tab or browser window is closed or
+ // the page navigates, else we never unwind the event loop and that's sad times.
+ // Remember to cleanup in shutdownPrompt()!
+ this.win.addEventListener("resize", this);
+ this.win.addEventListener("unload", this);
+ if (linkedTab) {
+ linkedTab.addEventListener("TabClose", this);
+ }
+ // Note:
+ // nsPrompter.js or in e10s mode browser-parent.js call abortPrompt,
+ // when the domWindow, for which the prompt was created, generates
+ // a "pagehide" event.
+
+ let { CommonDialog } = ChromeUtils.importESModule(
+ "resource://gre/modules/CommonDialog.sys.mjs"
+ );
+ this.Dialog = new CommonDialog(args, this.ui);
+ this.Dialog.onLoad(null);
+
+ // For content prompts display the tabprompt title that shows the prompt origin when
+ // the prompt origin is not the same as that of the top window.
+ if (
+ args.modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT &&
+ args.showCallerOrigin
+ ) {
+ this.ui.infoTitle.removeAttribute("hidden");
+ }
+
+ // TODO: should unhide buttonSpacer on Windows when there are 4 buttons.
+ // Better yet, just drop support for 4-button dialogs. (bug 609510)
+ }
+
+ shutdownPrompt() {
+ // remove our event listeners
+ try {
+ this.win.removeEventListener("resize", this);
+ this.win.removeEventListener("unload", this);
+ if (this.linkedTab) {
+ this.linkedTab.removeEventListener("TabClose", this);
+ }
+ } catch (e) {}
+ // invoke callback
+ this.onCloseCallback();
+ this.win = null;
+ this.ui = null;
+ // Intentionally not cleaning up |this.element| here --
+ // TabModalPromptBox.removePrompt() would need it and it might not
+ // be called yet -- see browser_double_close_tabs.js.
+ }
+
+ abortPrompt() {
+ // Called from other code when the page changes.
+ this.Dialog.abortPrompt();
+ this.shutdownPrompt();
+ }
+
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "unload":
+ case "TabClose":
+ this.abortPrompt();
+ break;
+ }
+ }
+
+ onButtonClick(buttonNum) {
+ // We want to do all the work her asynchronously off a Gecko
+ // runnable, because of situations like the one described in
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1167575#c35 : we
+ // get here off processing of an OS event and will also process
+ // one more Gecko runnable before we break out of the event loop
+ // spin whoever posted the prompt is doing. If we do all our
+ // work sync, we will exit modal state _before_ processing that
+ // runnable, and if exiting moral state posts a runnable we will
+ // incorrectly process that runnable before leaving our event
+ // loop spin.
+ Services.tm.dispatchToMainThread(() => {
+ this.Dialog["onButton" + buttonNum]();
+ this.shutdownPrompt();
+ });
+ }
+
+ onKeyAction(action, event) {
+ if (event.defaultPrevented) {
+ return;
+ }
+
+ event.stopPropagation();
+ if (action == "default") {
+ let bnum = this.args.defaultButtonNum || 0;
+ this.onButtonClick(bnum);
+ } else {
+ // action == "cancel"
+ this.onButtonClick(1); // Cancel button
+ }
+ }
+};