<?xml version="1.0"?> <!-- 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/. --> <!DOCTYPE bindings [ <!ENTITY % commonDialogDTD SYSTEM "chrome://global/locale/commonDialog.dtd"> <!ENTITY % dialogOverlayDTD SYSTEM "chrome://global/locale/dialogOverlay.dtd"> %commonDialogDTD; %dialogOverlayDTD; ]> <bindings id="tabPrompts" xmlns="http://www.mozilla.org/xbl" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl"> <binding id="tabmodalprompt"> <resources> <stylesheet src="chrome://global/content/tabprompts.css"/> <stylesheet src="chrome://global/skin/tabprompts.css"/> </resources> <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" role="dialog" aria-describedby="info.body"> <!-- This is based on the guts of commonDialog.xul --> <spacer flex="1"/> <hbox pack="center"> <vbox anonid="mainContainer" class="mainContainer"> <grid class="topContainer" flex="1"> <columns> <column/> <column flex="1"/> </columns> <rows> <vbox anonid="infoContainer" align="center" pack="center" flex="1"> <description anonid="info.title" class="info.title" hidden="true" /> <description anonid="info.body" class="info.body"/> </vbox> <row anonid="loginContainer" hidden="true" align="center"> <label anonid="loginLabel" value="&editfield0.label;" control="loginTextbox"/> <textbox anonid="loginTextbox"/> </row> <row anonid="password1Container" hidden="true" align="center"> <label anonid="password1Label" value="&editfield1.label;" control="password1Textbox"/> <textbox anonid="password1Textbox" type="password"/> </row> <row anonid="checkboxContainer" hidden="true"> <spacer/> <checkbox anonid="checkbox"/> </row> <xbl:children includes="row"/> </rows> </grid> <xbl:children/> <hbox class="buttonContainer"> #ifdef XP_UNIX <button anonid="button3" hidden="true"/> <button anonid="button2" hidden="true"/> <spacer anonid="buttonSpacer" flex="1"/> <button anonid="button1" label="&cancelButton.label;"/> <button anonid="button0" label="&okButton.label;"/> #else <button anonid="button3" hidden="true"/> <spacer anonid="buttonSpacer" flex="1"/> <button anonid="button0" label="&okButton.label;"/> <button anonid="button2" hidden="true"/> <button anonid="button1" label="&cancelButton.label;"/> #endif </hbox> </vbox> </hbox> <spacer flex="2"/> </xbl:content> <implementation implements="nsIDOMEventListener"> <constructor> <![CDATA[ let self = this; function getElement(anonid) { return document.getAnonymousElementByAttribute(self, "anonid", anonid); } this.ui = { prompt : this, loginContainer : getElement("loginContainer"), loginTextbox : getElement("loginTextbox"), loginLabel : getElement("loginLabel"), password1Container : getElement("password1Container"), password1Textbox : getElement("password1Textbox"), password1Label : getElement("password1Label"), infoBody : getElement("info.body"), infoTitle : getElement("info.title"), infoIcon : null, checkbox : getElement("checkbox"), checkboxContainer : getElement("checkboxContainer"), button3 : getElement("button3"), button2 : getElement("button2"), button1 : getElement("button1"), button0 : getElement("button0"), // focusTarget (for BUTTON_DELAY_ENABLE) not yet supported }; this.ui.button0.addEventListener("command", this.onButtonClick.bind(this, 0), false); this.ui.button1.addEventListener("command", this.onButtonClick.bind(this, 1), false); this.ui.button2.addEventListener("command", this.onButtonClick.bind(this, 2), false); this.ui.button3.addEventListener("command", this.onButtonClick.bind(this, 3), false); // Anonymous wrapper used here because |Dialog| doesn't exist until init() is called! this.ui.checkbox.addEventListener("command", function() { self.Dialog.onCheckbox(); }, false); this.isLive = false; ]]> </constructor> <destructor> <![CDATA[ if (this.isLive) { this.abortPrompt(); } ]]> </destructor> <field name="ui"/> <field name="args"/> <field name="linkedTab"/> <field name="onCloseCallback"/> <field name="Dialog"/> <field name="isLive"/> <field name="availWidth"/> <field name="availHeight"/> <field name="minWidth"/> <field name="minHeight"/> <method name="init"> <parameter name="args"/> <parameter name="linkedTab"/> <parameter name="onCloseCallback"/> <body> <![CDATA[ this.args = args; this.linkedTab = linkedTab; this.onCloseCallback = onCloseCallback; if (args.enableDelay) throw "BUTTON_DELAY_ENABLE not yet supported for tab-modal prompts"; // 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.isLive = true; window.addEventListener("resize", this, false); window.addEventListener("unload", this, false); linkedTab.addEventListener("TabClose", this, false); // 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 tmp = {}; Components.utils.import("resource://gre/modules/CommonDialog.jsm", tmp); this.Dialog = new tmp.CommonDialog(args, this.ui); this.Dialog.onLoad(null); // 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.showAlertOrigin) 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) this.onResize(); ]]> </body> </method> <method name="shutdownPrompt"> <body> <![CDATA[ // remove our event listeners try { window.removeEventListener("resize", this, false); window.removeEventListener("unload", this, false); this.linkedTab.removeEventListener("TabClose", this, false); } catch (e) { } this.isLive = false; // invoke callback this.onCloseCallback(); ]]> </body> </method> <method name="abortPrompt"> <body> <![CDATA[ // Called from other code when the page changes. this.Dialog.abortPrompt(); this.shutdownPrompt(); ]]> </body> </method> <method name="handleEvent"> <parameter name="aEvent"/> <body> <![CDATA[ switch (aEvent.type) { case "resize": this.onResize(); break; case "unload": case "TabClose": this.abortPrompt(); break; } ]]> </body> </method> <method name="onResize"> <body> <![CDATA[ let availWidth = this.clientWidth; let availHeight = this.clientHeight; if (availWidth == this.availWidth && availHeight == this.availHeight) return; this.availWidth = availWidth; this.availHeight = availHeight; let self = this; function getElement(anonid) { return document.getAnonymousElementByAttribute(self, "anonid", anonid); } let main = getElement("mainContainer"); let info = getElement("infoContainer"); let body = this.ui.infoBody; // cap prompt dimensions at 60% width and 60% height of content area if (!this.minWidth) this.minWidth = parseInt(window.getComputedStyle(main).minWidth); if (!this.minHeight) this.minHeight = parseInt(window.getComputedStyle(main).minHeight); let maxWidth = Math.max(Math.floor(availWidth * 0.6), this.minWidth) + info.clientWidth - main.clientWidth; let maxHeight = Math.max(Math.floor(availHeight * 0.6), this.minHeight) + info.clientHeight - main.clientHeight; body.style.maxWidth = maxWidth + "px"; info.style.overflow = info.style.width = info.style.height = ""; // when prompt text is too long, use scrollbars if (info.clientWidth > maxWidth) { info.style.overflow = "auto"; info.style.width = maxWidth + "px"; } if (info.clientHeight > maxHeight) { info.style.overflow = "auto"; info.style.height = maxHeight + "px"; } ]]> </body> </method> <method name="onButtonClick"> <parameter name="buttonNum"/> <body> <![CDATA[ // 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.mainThread.dispatch(() => { this.Dialog["onButton" + buttonNum](); this.shutdownPrompt(); }, Ci.nsIThread.DISPATCH_NORMAL); ]]> </body> </method> <method name="onKeyAction"> <parameter name="action"/> <parameter name="event"/> <body> <![CDATA[ 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 } ]]> </body> </method> </implementation> <handlers> <!-- Based on dialog.xml handlers --> <handler event="keypress" keycode="VK_RETURN" group="system" action="this.onKeyAction('default', event);"/> <handler event="keypress" keycode="VK_ESCAPE" group="system" action="this.onKeyAction('cancel', event);"/> #ifdef XP_MACOSX <handler event="keypress" key="." modifiers="meta" group="system" action="this.onKeyAction('cancel', event);"/> #endif <handler event="focus" phase="capturing"> let bnum = this.args.defaultButtonNum || 0; let defaultButton = this.ui["button" + bnum]; let { AppConstants } = Components.utils.import("resource://gre/modules/AppConstants.jsm", {}); 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 instanceof Ci.nsIDOMXULButtonElement; defaultButton.setAttribute("default", focusedDefault || !someButtonFocused); } </handler> <handler event="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.setAttribute("default", false); </handler> </handlers> </binding> </bindings>