1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/prompts/src/CommonDialog.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,345 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +this.EXPORTED_SYMBOLS = ["CommonDialog"]; 1.9 + 1.10 +const Ci = Components.interfaces; 1.11 +const Cr = Components.results; 1.12 +const Cc = Components.classes; 1.13 +const Cu = Components.utils; 1.14 + 1.15 +Cu.import("resource://gre/modules/Services.jsm"); 1.16 + 1.17 + 1.18 +this.CommonDialog = function CommonDialog(args, ui) { 1.19 + this.args = args; 1.20 + this.ui = ui; 1.21 +} 1.22 + 1.23 +CommonDialog.prototype = { 1.24 + args : null, 1.25 + ui : null, 1.26 + 1.27 + hasInputField : true, 1.28 + numButtons : undefined, 1.29 + iconClass : undefined, 1.30 + soundID : undefined, 1.31 + focusTimer : null, 1.32 + 1.33 + onLoad : function(xulDialog) { 1.34 + switch (this.args.promptType) { 1.35 + case "alert": 1.36 + case "alertCheck": 1.37 + this.hasInputField = false; 1.38 + this.numButtons = 1; 1.39 + this.iconClass = ["alert-icon"]; 1.40 + this.soundID = Ci.nsISound.EVENT_ALERT_DIALOG_OPEN; 1.41 + break; 1.42 + case "confirmCheck": 1.43 + case "confirm": 1.44 + this.hasInputField = false; 1.45 + this.numButtons = 2; 1.46 + this.iconClass = ["question-icon"]; 1.47 + this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN; 1.48 + break; 1.49 + case "confirmEx": 1.50 + var numButtons = 0; 1.51 + if (this.args.button0Label) 1.52 + numButtons++; 1.53 + if (this.args.button1Label) 1.54 + numButtons++; 1.55 + if (this.args.button2Label) 1.56 + numButtons++; 1.57 + if (this.args.button3Label) 1.58 + numButtons++; 1.59 + if (numButtons == 0) 1.60 + throw "A dialog with no buttons? Can not haz."; 1.61 + this.numButtons = numButtons; 1.62 + this.hasInputField = false; 1.63 + this.iconClass = ["question-icon"]; 1.64 + this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN; 1.65 + break; 1.66 + case "prompt": 1.67 + this.numButtons = 2; 1.68 + this.iconClass = ["question-icon"]; 1.69 + this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN; 1.70 + this.initTextbox("login", this.args.value); 1.71 + // Clear the label, since this isn't really a username prompt. 1.72 + this.ui.loginLabel.setAttribute("value", ""); 1.73 + break; 1.74 + case "promptUserAndPass": 1.75 + this.numButtons = 2; 1.76 + this.iconClass = ["authentication-icon", "question-icon"]; 1.77 + this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN; 1.78 + this.initTextbox("login", this.args.user); 1.79 + this.initTextbox("password1", this.args.pass); 1.80 + break; 1.81 + case "promptPassword": 1.82 + this.numButtons = 2; 1.83 + this.iconClass = ["authentication-icon", "question-icon"]; 1.84 + this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN; 1.85 + this.initTextbox("password1", this.args.pass); 1.86 + // Clear the label, since the message presumably indicates its purpose. 1.87 + this.ui.password1Label.setAttribute("value", ""); 1.88 + break; 1.89 + default: 1.90 + Cu.reportError("commonDialog opened for unknown type: " + this.args.promptType); 1.91 + throw "unknown dialog type"; 1.92 + } 1.93 + 1.94 + // set the document title 1.95 + let title = this.args.title; 1.96 + // OS X doesn't have a title on modal dialogs, this is hidden on other platforms. 1.97 + let infoTitle = this.ui.infoTitle; 1.98 + infoTitle.appendChild(infoTitle.ownerDocument.createTextNode(title)); 1.99 + if (xulDialog) 1.100 + xulDialog.ownerDocument.title = title; 1.101 + 1.102 + // Set button labels and visibility 1.103 + // 1.104 + // This assumes that button0 defaults to a visible "ok" button, and 1.105 + // button1 defaults to a visible "cancel" button. The other 2 buttons 1.106 + // have no default labels (and are hidden). 1.107 + switch (this.numButtons) { 1.108 + case 4: 1.109 + this.setLabelForNode(this.ui.button3, this.args.button3Label); 1.110 + this.ui.button3.hidden = false; 1.111 + // fall through 1.112 + case 3: 1.113 + this.setLabelForNode(this.ui.button2, this.args.button2Label); 1.114 + this.ui.button2.hidden = false; 1.115 + // fall through 1.116 + case 2: 1.117 + // Defaults to a visible "cancel" button 1.118 + if (this.args.button1Label) 1.119 + this.setLabelForNode(this.ui.button1, this.args.button1Label); 1.120 + break; 1.121 + 1.122 + case 1: 1.123 + this.ui.button1.hidden = true; 1.124 + break; 1.125 + } 1.126 + // Defaults to a visible "ok" button 1.127 + if (this.args.button0Label) 1.128 + this.setLabelForNode(this.ui.button0, this.args.button0Label); 1.129 + 1.130 + // display the main text 1.131 + // Bug 317334 - crop string length as a workaround. 1.132 + let croppedMessage = this.args.text.substr(0, 10000); 1.133 + let infoBody = this.ui.infoBody; 1.134 + infoBody.appendChild(infoBody.ownerDocument.createTextNode(croppedMessage)); 1.135 + 1.136 + let label = this.args.checkLabel; 1.137 + if (label) { 1.138 + // Only show the checkbox if label has a value. 1.139 + this.ui.checkboxContainer.hidden = false; 1.140 + this.setLabelForNode(this.ui.checkbox, label); 1.141 + this.ui.checkbox.checked = this.args.checked; 1.142 + } 1.143 + 1.144 + // set the icon 1.145 + let icon = this.ui.infoIcon; 1.146 + if (icon) 1.147 + this.iconClass.forEach(function(el,idx,arr) icon.classList.add(el)); 1.148 + 1.149 + // set default result to cancelled 1.150 + this.args.ok = false; 1.151 + this.args.buttonNumClicked = 1; 1.152 + 1.153 + 1.154 + // Set the default button 1.155 + let b = (this.args.defaultButtonNum || 0); 1.156 + let button = this.ui["button" + b]; 1.157 + 1.158 + if (xulDialog) 1.159 + xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b]; 1.160 + else 1.161 + button.setAttribute("default", "true"); 1.162 + 1.163 + // Set default focus / selection. 1.164 + this.setDefaultFocus(true); 1.165 + 1.166 + if (this.args.enableDelay) { 1.167 + this.setButtonsEnabledState(false); 1.168 + // Use a longer, pref-controlled delay when the dialog is first opened. 1.169 + let delayTime = Services.prefs.getIntPref("security.dialog_enable_delay"); 1.170 + this.startOnFocusDelay(delayTime); 1.171 + let self = this; 1.172 + this.ui.focusTarget.addEventListener("blur", function(e) { self.onBlur(e); }, false); 1.173 + this.ui.focusTarget.addEventListener("focus", function(e) { self.onFocus(e); }, false); 1.174 + } 1.175 + 1.176 + // Play a sound (unless we're tab-modal -- don't want those to feel like OS prompts). 1.177 + try { 1.178 + if (xulDialog && this.soundID) { 1.179 + Cc["@mozilla.org/sound;1"]. 1.180 + createInstance(Ci.nsISound). 1.181 + playEventSound(this.soundID); 1.182 + } 1.183 + } catch (e) { 1.184 + Cu.reportError("Couldn't play common dialog event sound: " + e); 1.185 + } 1.186 + 1.187 + let topic = "common-dialog-loaded"; 1.188 + if (!xulDialog) 1.189 + topic = "tabmodal-dialog-loaded"; 1.190 + Services.obs.notifyObservers(this.ui.prompt, topic, null); 1.191 + }, 1.192 + 1.193 + setLabelForNode: function(aNode, aLabel) { 1.194 + // This is for labels which may contain embedded access keys. 1.195 + // If we end in (&X) where X represents the access key, optionally preceded 1.196 + // by spaces and/or followed by the ':' character, store the access key and 1.197 + // remove the access key placeholder + leading spaces from the label. 1.198 + // Otherwise a character preceded by one but not two &s is the access key. 1.199 + // Store it and remove the &. 1.200 + 1.201 + // Note that if you change the following code, see the comment of 1.202 + // nsTextBoxFrame::UpdateAccessTitle. 1.203 + var accessKey = null; 1.204 + if (/ *\(\&([^&])\)(:)?$/.test(aLabel)) { 1.205 + aLabel = RegExp.leftContext + RegExp.$2; 1.206 + accessKey = RegExp.$1; 1.207 + } else if (/^(.*[^&])?\&(([^&]).*$)/.test(aLabel)) { 1.208 + aLabel = RegExp.$1 + RegExp.$2; 1.209 + accessKey = RegExp.$3; 1.210 + } 1.211 + 1.212 + // && is the magic sequence to embed an & in your label. 1.213 + aLabel = aLabel.replace(/\&\&/g, "&"); 1.214 + aNode.label = aLabel; 1.215 + 1.216 + // XXXjag bug 325251 1.217 + // Need to set this after aNode.setAttribute("value", aLabel); 1.218 + if (accessKey) 1.219 + aNode.accessKey = accessKey; 1.220 + }, 1.221 + 1.222 + 1.223 + initTextbox : function (aName, aValue) { 1.224 + this.ui[aName + "Container"].hidden = false; 1.225 + this.ui[aName + "Textbox"].setAttribute("value", 1.226 + aValue !== null ? aValue : ""); 1.227 + }, 1.228 + 1.229 + setButtonsEnabledState : function(enabled) { 1.230 + this.ui.button0.disabled = !enabled; 1.231 + // button1 (cancel) remains enabled. 1.232 + this.ui.button2.disabled = !enabled; 1.233 + this.ui.button3.disabled = !enabled; 1.234 + }, 1.235 + 1.236 + onBlur : function (aEvent) { 1.237 + if (aEvent.target != this.ui.focusTarget) 1.238 + return; 1.239 + this.setButtonsEnabledState(false); 1.240 + 1.241 + // If we blur while waiting to enable the buttons, just cancel the 1.242 + // timer to ensure the delay doesn't fire while not focused. 1.243 + if (this.focusTimer) { 1.244 + this.focusTimer.cancel(); 1.245 + this.focusTimer = null; 1.246 + } 1.247 + }, 1.248 + 1.249 + onFocus : function (aEvent) { 1.250 + if (aEvent.target != this.ui.focusTarget) 1.251 + return; 1.252 + this.startOnFocusDelay(); 1.253 + }, 1.254 + 1.255 + startOnFocusDelay : function(delayTime) { 1.256 + // Shouldn't already have a timer, but just in case... 1.257 + if (this.focusTimer) 1.258 + return; 1.259 + // If no delay specified, use 250ms. (This is the normal case for when 1.260 + // after the dialog has been opened and focus shifts.) 1.261 + if (!delayTime) 1.262 + delayTime = 250; 1.263 + let self = this; 1.264 + this.focusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.265 + this.focusTimer.initWithCallback(function() { self.onFocusTimeout(); }, 1.266 + delayTime, Ci.nsITimer.TYPE_ONE_SHOT); 1.267 + }, 1.268 + 1.269 + onFocusTimeout : function() { 1.270 + this.focusTimer = null; 1.271 + this.setButtonsEnabledState(true); 1.272 + }, 1.273 + 1.274 + setDefaultFocus : function(isInitialLoad) { 1.275 + let b = (this.args.defaultButtonNum || 0); 1.276 + let button = this.ui["button" + b]; 1.277 + 1.278 + if (!this.hasInputField) { 1.279 + let isOSX = ("nsILocalFileMac" in Components.interfaces); 1.280 + if (isOSX) 1.281 + this.ui.infoBody.focus(); 1.282 + else 1.283 + button.focus(); 1.284 + } else { 1.285 + // When the prompt is initialized, focus and select the textbox 1.286 + // contents. Afterwards, only focus the textbox. 1.287 + if (this.args.promptType == "promptPassword") { 1.288 + if (isInitialLoad) 1.289 + this.ui.password1Textbox.select(); 1.290 + else 1.291 + this.ui.password1Textbox.focus(); 1.292 + } else { 1.293 + if (isInitialLoad) 1.294 + this.ui.loginTextbox.select(); 1.295 + else 1.296 + this.ui.loginTextbox.focus(); 1.297 + } 1.298 + } 1.299 + }, 1.300 + 1.301 + onCheckbox : function() { 1.302 + this.args.checked = this.ui.checkbox.checked; 1.303 + }, 1.304 + 1.305 + onButton0 : function() { 1.306 + this.args.promptActive = false; 1.307 + this.args.ok = true; 1.308 + this.args.buttonNumClicked = 0; 1.309 + 1.310 + let username = this.ui.loginTextbox.value; 1.311 + let password = this.ui.password1Textbox.value; 1.312 + 1.313 + // Return textfield values 1.314 + switch (this.args.promptType) { 1.315 + case "prompt": 1.316 + this.args.value = username; 1.317 + break; 1.318 + case "promptUserAndPass": 1.319 + this.args.user = username; 1.320 + this.args.pass = password; 1.321 + break; 1.322 + case "promptPassword": 1.323 + this.args.pass = password; 1.324 + break; 1.325 + } 1.326 + }, 1.327 + 1.328 + onButton1 : function() { 1.329 + this.args.promptActive = false; 1.330 + this.args.buttonNumClicked = 1; 1.331 + }, 1.332 + 1.333 + onButton2 : function() { 1.334 + this.args.promptActive = false; 1.335 + this.args.buttonNumClicked = 2; 1.336 + }, 1.337 + 1.338 + onButton3 : function() { 1.339 + this.args.promptActive = false; 1.340 + this.args.buttonNumClicked = 3; 1.341 + }, 1.342 + 1.343 + abortPrompt : function() { 1.344 + this.args.promptActive = false; 1.345 + this.args.promptAborted = true; 1.346 + }, 1.347 + 1.348 +};