michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = ["CommonDialog"]; michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: const Cc = Components.classes; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: michael@0: this.CommonDialog = function CommonDialog(args, ui) { michael@0: this.args = args; michael@0: this.ui = ui; michael@0: } michael@0: michael@0: CommonDialog.prototype = { michael@0: args : null, michael@0: ui : null, michael@0: michael@0: hasInputField : true, michael@0: numButtons : undefined, michael@0: iconClass : undefined, michael@0: soundID : undefined, michael@0: focusTimer : null, michael@0: michael@0: onLoad : function(xulDialog) { michael@0: switch (this.args.promptType) { michael@0: case "alert": michael@0: case "alertCheck": michael@0: this.hasInputField = false; michael@0: this.numButtons = 1; michael@0: this.iconClass = ["alert-icon"]; michael@0: this.soundID = Ci.nsISound.EVENT_ALERT_DIALOG_OPEN; michael@0: break; michael@0: case "confirmCheck": michael@0: case "confirm": michael@0: this.hasInputField = false; michael@0: this.numButtons = 2; michael@0: this.iconClass = ["question-icon"]; michael@0: this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN; michael@0: break; michael@0: case "confirmEx": michael@0: var numButtons = 0; michael@0: if (this.args.button0Label) michael@0: numButtons++; michael@0: if (this.args.button1Label) michael@0: numButtons++; michael@0: if (this.args.button2Label) michael@0: numButtons++; michael@0: if (this.args.button3Label) michael@0: numButtons++; michael@0: if (numButtons == 0) michael@0: throw "A dialog with no buttons? Can not haz."; michael@0: this.numButtons = numButtons; michael@0: this.hasInputField = false; michael@0: this.iconClass = ["question-icon"]; michael@0: this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN; michael@0: break; michael@0: case "prompt": michael@0: this.numButtons = 2; michael@0: this.iconClass = ["question-icon"]; michael@0: this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN; michael@0: this.initTextbox("login", this.args.value); michael@0: // Clear the label, since this isn't really a username prompt. michael@0: this.ui.loginLabel.setAttribute("value", ""); michael@0: break; michael@0: case "promptUserAndPass": michael@0: this.numButtons = 2; michael@0: this.iconClass = ["authentication-icon", "question-icon"]; michael@0: this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN; michael@0: this.initTextbox("login", this.args.user); michael@0: this.initTextbox("password1", this.args.pass); michael@0: break; michael@0: case "promptPassword": michael@0: this.numButtons = 2; michael@0: this.iconClass = ["authentication-icon", "question-icon"]; michael@0: this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN; michael@0: this.initTextbox("password1", this.args.pass); michael@0: // Clear the label, since the message presumably indicates its purpose. michael@0: this.ui.password1Label.setAttribute("value", ""); michael@0: break; michael@0: default: michael@0: Cu.reportError("commonDialog opened for unknown type: " + this.args.promptType); michael@0: throw "unknown dialog type"; michael@0: } michael@0: michael@0: // set the document title michael@0: let title = this.args.title; michael@0: // OS X doesn't have a title on modal dialogs, this is hidden on other platforms. michael@0: let infoTitle = this.ui.infoTitle; michael@0: infoTitle.appendChild(infoTitle.ownerDocument.createTextNode(title)); michael@0: if (xulDialog) michael@0: xulDialog.ownerDocument.title = title; michael@0: michael@0: // Set button labels and visibility michael@0: // michael@0: // This assumes that button0 defaults to a visible "ok" button, and michael@0: // button1 defaults to a visible "cancel" button. The other 2 buttons michael@0: // have no default labels (and are hidden). michael@0: switch (this.numButtons) { michael@0: case 4: michael@0: this.setLabelForNode(this.ui.button3, this.args.button3Label); michael@0: this.ui.button3.hidden = false; michael@0: // fall through michael@0: case 3: michael@0: this.setLabelForNode(this.ui.button2, this.args.button2Label); michael@0: this.ui.button2.hidden = false; michael@0: // fall through michael@0: case 2: michael@0: // Defaults to a visible "cancel" button michael@0: if (this.args.button1Label) michael@0: this.setLabelForNode(this.ui.button1, this.args.button1Label); michael@0: break; michael@0: michael@0: case 1: michael@0: this.ui.button1.hidden = true; michael@0: break; michael@0: } michael@0: // Defaults to a visible "ok" button michael@0: if (this.args.button0Label) michael@0: this.setLabelForNode(this.ui.button0, this.args.button0Label); michael@0: michael@0: // display the main text michael@0: // Bug 317334 - crop string length as a workaround. michael@0: let croppedMessage = this.args.text.substr(0, 10000); michael@0: let infoBody = this.ui.infoBody; michael@0: infoBody.appendChild(infoBody.ownerDocument.createTextNode(croppedMessage)); michael@0: michael@0: let label = this.args.checkLabel; michael@0: if (label) { michael@0: // Only show the checkbox if label has a value. michael@0: this.ui.checkboxContainer.hidden = false; michael@0: this.setLabelForNode(this.ui.checkbox, label); michael@0: this.ui.checkbox.checked = this.args.checked; michael@0: } michael@0: michael@0: // set the icon michael@0: let icon = this.ui.infoIcon; michael@0: if (icon) michael@0: this.iconClass.forEach(function(el,idx,arr) icon.classList.add(el)); michael@0: michael@0: // set default result to cancelled michael@0: this.args.ok = false; michael@0: this.args.buttonNumClicked = 1; michael@0: michael@0: michael@0: // Set the default button michael@0: let b = (this.args.defaultButtonNum || 0); michael@0: let button = this.ui["button" + b]; michael@0: michael@0: if (xulDialog) michael@0: xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b]; michael@0: else michael@0: button.setAttribute("default", "true"); michael@0: michael@0: // Set default focus / selection. michael@0: this.setDefaultFocus(true); michael@0: michael@0: if (this.args.enableDelay) { michael@0: this.setButtonsEnabledState(false); michael@0: // Use a longer, pref-controlled delay when the dialog is first opened. michael@0: let delayTime = Services.prefs.getIntPref("security.dialog_enable_delay"); michael@0: this.startOnFocusDelay(delayTime); michael@0: let self = this; michael@0: this.ui.focusTarget.addEventListener("blur", function(e) { self.onBlur(e); }, false); michael@0: this.ui.focusTarget.addEventListener("focus", function(e) { self.onFocus(e); }, false); michael@0: } michael@0: michael@0: // Play a sound (unless we're tab-modal -- don't want those to feel like OS prompts). michael@0: try { michael@0: if (xulDialog && this.soundID) { michael@0: Cc["@mozilla.org/sound;1"]. michael@0: createInstance(Ci.nsISound). michael@0: playEventSound(this.soundID); michael@0: } michael@0: } catch (e) { michael@0: Cu.reportError("Couldn't play common dialog event sound: " + e); michael@0: } michael@0: michael@0: let topic = "common-dialog-loaded"; michael@0: if (!xulDialog) michael@0: topic = "tabmodal-dialog-loaded"; michael@0: Services.obs.notifyObservers(this.ui.prompt, topic, null); michael@0: }, michael@0: michael@0: setLabelForNode: function(aNode, aLabel) { michael@0: // This is for labels which may contain embedded access keys. michael@0: // If we end in (&X) where X represents the access key, optionally preceded michael@0: // by spaces and/or followed by the ':' character, store the access key and michael@0: // remove the access key placeholder + leading spaces from the label. michael@0: // Otherwise a character preceded by one but not two &s is the access key. michael@0: // Store it and remove the &. michael@0: michael@0: // Note that if you change the following code, see the comment of michael@0: // nsTextBoxFrame::UpdateAccessTitle. michael@0: var accessKey = null; michael@0: if (/ *\(\&([^&])\)(:)?$/.test(aLabel)) { michael@0: aLabel = RegExp.leftContext + RegExp.$2; michael@0: accessKey = RegExp.$1; michael@0: } else if (/^(.*[^&])?\&(([^&]).*$)/.test(aLabel)) { michael@0: aLabel = RegExp.$1 + RegExp.$2; michael@0: accessKey = RegExp.$3; michael@0: } michael@0: michael@0: // && is the magic sequence to embed an & in your label. michael@0: aLabel = aLabel.replace(/\&\&/g, "&"); michael@0: aNode.label = aLabel; michael@0: michael@0: // XXXjag bug 325251 michael@0: // Need to set this after aNode.setAttribute("value", aLabel); michael@0: if (accessKey) michael@0: aNode.accessKey = accessKey; michael@0: }, michael@0: michael@0: michael@0: initTextbox : function (aName, aValue) { michael@0: this.ui[aName + "Container"].hidden = false; michael@0: this.ui[aName + "Textbox"].setAttribute("value", michael@0: aValue !== null ? aValue : ""); michael@0: }, michael@0: michael@0: setButtonsEnabledState : function(enabled) { michael@0: this.ui.button0.disabled = !enabled; michael@0: // button1 (cancel) remains enabled. michael@0: this.ui.button2.disabled = !enabled; michael@0: this.ui.button3.disabled = !enabled; michael@0: }, michael@0: michael@0: onBlur : function (aEvent) { michael@0: if (aEvent.target != this.ui.focusTarget) michael@0: return; michael@0: this.setButtonsEnabledState(false); michael@0: michael@0: // If we blur while waiting to enable the buttons, just cancel the michael@0: // timer to ensure the delay doesn't fire while not focused. michael@0: if (this.focusTimer) { michael@0: this.focusTimer.cancel(); michael@0: this.focusTimer = null; michael@0: } michael@0: }, michael@0: michael@0: onFocus : function (aEvent) { michael@0: if (aEvent.target != this.ui.focusTarget) michael@0: return; michael@0: this.startOnFocusDelay(); michael@0: }, michael@0: michael@0: startOnFocusDelay : function(delayTime) { michael@0: // Shouldn't already have a timer, but just in case... michael@0: if (this.focusTimer) michael@0: return; michael@0: // If no delay specified, use 250ms. (This is the normal case for when michael@0: // after the dialog has been opened and focus shifts.) michael@0: if (!delayTime) michael@0: delayTime = 250; michael@0: let self = this; michael@0: this.focusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: this.focusTimer.initWithCallback(function() { self.onFocusTimeout(); }, michael@0: delayTime, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: }, michael@0: michael@0: onFocusTimeout : function() { michael@0: this.focusTimer = null; michael@0: this.setButtonsEnabledState(true); michael@0: }, michael@0: michael@0: setDefaultFocus : function(isInitialLoad) { michael@0: let b = (this.args.defaultButtonNum || 0); michael@0: let button = this.ui["button" + b]; michael@0: michael@0: if (!this.hasInputField) { michael@0: let isOSX = ("nsILocalFileMac" in Components.interfaces); michael@0: if (isOSX) michael@0: this.ui.infoBody.focus(); michael@0: else michael@0: button.focus(); michael@0: } else { michael@0: // When the prompt is initialized, focus and select the textbox michael@0: // contents. Afterwards, only focus the textbox. michael@0: if (this.args.promptType == "promptPassword") { michael@0: if (isInitialLoad) michael@0: this.ui.password1Textbox.select(); michael@0: else michael@0: this.ui.password1Textbox.focus(); michael@0: } else { michael@0: if (isInitialLoad) michael@0: this.ui.loginTextbox.select(); michael@0: else michael@0: this.ui.loginTextbox.focus(); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: onCheckbox : function() { michael@0: this.args.checked = this.ui.checkbox.checked; michael@0: }, michael@0: michael@0: onButton0 : function() { michael@0: this.args.promptActive = false; michael@0: this.args.ok = true; michael@0: this.args.buttonNumClicked = 0; michael@0: michael@0: let username = this.ui.loginTextbox.value; michael@0: let password = this.ui.password1Textbox.value; michael@0: michael@0: // Return textfield values michael@0: switch (this.args.promptType) { michael@0: case "prompt": michael@0: this.args.value = username; michael@0: break; michael@0: case "promptUserAndPass": michael@0: this.args.user = username; michael@0: this.args.pass = password; michael@0: break; michael@0: case "promptPassword": michael@0: this.args.pass = password; michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: onButton1 : function() { michael@0: this.args.promptActive = false; michael@0: this.args.buttonNumClicked = 1; michael@0: }, michael@0: michael@0: onButton2 : function() { michael@0: this.args.promptActive = false; michael@0: this.args.buttonNumClicked = 2; michael@0: }, michael@0: michael@0: onButton3 : function() { michael@0: this.args.promptActive = false; michael@0: this.args.buttonNumClicked = 3; michael@0: }, michael@0: michael@0: abortPrompt : function() { michael@0: this.args.promptActive = false; michael@0: this.args.promptAborted = true; michael@0: }, michael@0: michael@0: };