Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 this.EXPORTED_SYMBOLS = ["CommonDialog"];
7 const Ci = Components.interfaces;
8 const Cr = Components.results;
9 const Cc = Components.classes;
10 const Cu = Components.utils;
12 Cu.import("resource://gre/modules/Services.jsm");
15 this.CommonDialog = function CommonDialog(args, ui) {
16 this.args = args;
17 this.ui = ui;
18 }
20 CommonDialog.prototype = {
21 args : null,
22 ui : null,
24 hasInputField : true,
25 numButtons : undefined,
26 iconClass : undefined,
27 soundID : undefined,
28 focusTimer : null,
30 onLoad : function(xulDialog) {
31 switch (this.args.promptType) {
32 case "alert":
33 case "alertCheck":
34 this.hasInputField = false;
35 this.numButtons = 1;
36 this.iconClass = ["alert-icon"];
37 this.soundID = Ci.nsISound.EVENT_ALERT_DIALOG_OPEN;
38 break;
39 case "confirmCheck":
40 case "confirm":
41 this.hasInputField = false;
42 this.numButtons = 2;
43 this.iconClass = ["question-icon"];
44 this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
45 break;
46 case "confirmEx":
47 var numButtons = 0;
48 if (this.args.button0Label)
49 numButtons++;
50 if (this.args.button1Label)
51 numButtons++;
52 if (this.args.button2Label)
53 numButtons++;
54 if (this.args.button3Label)
55 numButtons++;
56 if (numButtons == 0)
57 throw "A dialog with no buttons? Can not haz.";
58 this.numButtons = numButtons;
59 this.hasInputField = false;
60 this.iconClass = ["question-icon"];
61 this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
62 break;
63 case "prompt":
64 this.numButtons = 2;
65 this.iconClass = ["question-icon"];
66 this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
67 this.initTextbox("login", this.args.value);
68 // Clear the label, since this isn't really a username prompt.
69 this.ui.loginLabel.setAttribute("value", "");
70 break;
71 case "promptUserAndPass":
72 this.numButtons = 2;
73 this.iconClass = ["authentication-icon", "question-icon"];
74 this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
75 this.initTextbox("login", this.args.user);
76 this.initTextbox("password1", this.args.pass);
77 break;
78 case "promptPassword":
79 this.numButtons = 2;
80 this.iconClass = ["authentication-icon", "question-icon"];
81 this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
82 this.initTextbox("password1", this.args.pass);
83 // Clear the label, since the message presumably indicates its purpose.
84 this.ui.password1Label.setAttribute("value", "");
85 break;
86 default:
87 Cu.reportError("commonDialog opened for unknown type: " + this.args.promptType);
88 throw "unknown dialog type";
89 }
91 // set the document title
92 let title = this.args.title;
93 // OS X doesn't have a title on modal dialogs, this is hidden on other platforms.
94 let infoTitle = this.ui.infoTitle;
95 infoTitle.appendChild(infoTitle.ownerDocument.createTextNode(title));
96 if (xulDialog)
97 xulDialog.ownerDocument.title = title;
99 // Set button labels and visibility
100 //
101 // This assumes that button0 defaults to a visible "ok" button, and
102 // button1 defaults to a visible "cancel" button. The other 2 buttons
103 // have no default labels (and are hidden).
104 switch (this.numButtons) {
105 case 4:
106 this.setLabelForNode(this.ui.button3, this.args.button3Label);
107 this.ui.button3.hidden = false;
108 // fall through
109 case 3:
110 this.setLabelForNode(this.ui.button2, this.args.button2Label);
111 this.ui.button2.hidden = false;
112 // fall through
113 case 2:
114 // Defaults to a visible "cancel" button
115 if (this.args.button1Label)
116 this.setLabelForNode(this.ui.button1, this.args.button1Label);
117 break;
119 case 1:
120 this.ui.button1.hidden = true;
121 break;
122 }
123 // Defaults to a visible "ok" button
124 if (this.args.button0Label)
125 this.setLabelForNode(this.ui.button0, this.args.button0Label);
127 // display the main text
128 // Bug 317334 - crop string length as a workaround.
129 let croppedMessage = this.args.text.substr(0, 10000);
130 let infoBody = this.ui.infoBody;
131 infoBody.appendChild(infoBody.ownerDocument.createTextNode(croppedMessage));
133 let label = this.args.checkLabel;
134 if (label) {
135 // Only show the checkbox if label has a value.
136 this.ui.checkboxContainer.hidden = false;
137 this.setLabelForNode(this.ui.checkbox, label);
138 this.ui.checkbox.checked = this.args.checked;
139 }
141 // set the icon
142 let icon = this.ui.infoIcon;
143 if (icon)
144 this.iconClass.forEach(function(el,idx,arr) icon.classList.add(el));
146 // set default result to cancelled
147 this.args.ok = false;
148 this.args.buttonNumClicked = 1;
151 // Set the default button
152 let b = (this.args.defaultButtonNum || 0);
153 let button = this.ui["button" + b];
155 if (xulDialog)
156 xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b];
157 else
158 button.setAttribute("default", "true");
160 // Set default focus / selection.
161 this.setDefaultFocus(true);
163 if (this.args.enableDelay) {
164 this.setButtonsEnabledState(false);
165 // Use a longer, pref-controlled delay when the dialog is first opened.
166 let delayTime = Services.prefs.getIntPref("security.dialog_enable_delay");
167 this.startOnFocusDelay(delayTime);
168 let self = this;
169 this.ui.focusTarget.addEventListener("blur", function(e) { self.onBlur(e); }, false);
170 this.ui.focusTarget.addEventListener("focus", function(e) { self.onFocus(e); }, false);
171 }
173 // Play a sound (unless we're tab-modal -- don't want those to feel like OS prompts).
174 try {
175 if (xulDialog && this.soundID) {
176 Cc["@mozilla.org/sound;1"].
177 createInstance(Ci.nsISound).
178 playEventSound(this.soundID);
179 }
180 } catch (e) {
181 Cu.reportError("Couldn't play common dialog event sound: " + e);
182 }
184 let topic = "common-dialog-loaded";
185 if (!xulDialog)
186 topic = "tabmodal-dialog-loaded";
187 Services.obs.notifyObservers(this.ui.prompt, topic, null);
188 },
190 setLabelForNode: function(aNode, aLabel) {
191 // This is for labels which may contain embedded access keys.
192 // If we end in (&X) where X represents the access key, optionally preceded
193 // by spaces and/or followed by the ':' character, store the access key and
194 // remove the access key placeholder + leading spaces from the label.
195 // Otherwise a character preceded by one but not two &s is the access key.
196 // Store it and remove the &.
198 // Note that if you change the following code, see the comment of
199 // nsTextBoxFrame::UpdateAccessTitle.
200 var accessKey = null;
201 if (/ *\(\&([^&])\)(:)?$/.test(aLabel)) {
202 aLabel = RegExp.leftContext + RegExp.$2;
203 accessKey = RegExp.$1;
204 } else if (/^(.*[^&])?\&(([^&]).*$)/.test(aLabel)) {
205 aLabel = RegExp.$1 + RegExp.$2;
206 accessKey = RegExp.$3;
207 }
209 // && is the magic sequence to embed an & in your label.
210 aLabel = aLabel.replace(/\&\&/g, "&");
211 aNode.label = aLabel;
213 // XXXjag bug 325251
214 // Need to set this after aNode.setAttribute("value", aLabel);
215 if (accessKey)
216 aNode.accessKey = accessKey;
217 },
220 initTextbox : function (aName, aValue) {
221 this.ui[aName + "Container"].hidden = false;
222 this.ui[aName + "Textbox"].setAttribute("value",
223 aValue !== null ? aValue : "");
224 },
226 setButtonsEnabledState : function(enabled) {
227 this.ui.button0.disabled = !enabled;
228 // button1 (cancel) remains enabled.
229 this.ui.button2.disabled = !enabled;
230 this.ui.button3.disabled = !enabled;
231 },
233 onBlur : function (aEvent) {
234 if (aEvent.target != this.ui.focusTarget)
235 return;
236 this.setButtonsEnabledState(false);
238 // If we blur while waiting to enable the buttons, just cancel the
239 // timer to ensure the delay doesn't fire while not focused.
240 if (this.focusTimer) {
241 this.focusTimer.cancel();
242 this.focusTimer = null;
243 }
244 },
246 onFocus : function (aEvent) {
247 if (aEvent.target != this.ui.focusTarget)
248 return;
249 this.startOnFocusDelay();
250 },
252 startOnFocusDelay : function(delayTime) {
253 // Shouldn't already have a timer, but just in case...
254 if (this.focusTimer)
255 return;
256 // If no delay specified, use 250ms. (This is the normal case for when
257 // after the dialog has been opened and focus shifts.)
258 if (!delayTime)
259 delayTime = 250;
260 let self = this;
261 this.focusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
262 this.focusTimer.initWithCallback(function() { self.onFocusTimeout(); },
263 delayTime, Ci.nsITimer.TYPE_ONE_SHOT);
264 },
266 onFocusTimeout : function() {
267 this.focusTimer = null;
268 this.setButtonsEnabledState(true);
269 },
271 setDefaultFocus : function(isInitialLoad) {
272 let b = (this.args.defaultButtonNum || 0);
273 let button = this.ui["button" + b];
275 if (!this.hasInputField) {
276 let isOSX = ("nsILocalFileMac" in Components.interfaces);
277 if (isOSX)
278 this.ui.infoBody.focus();
279 else
280 button.focus();
281 } else {
282 // When the prompt is initialized, focus and select the textbox
283 // contents. Afterwards, only focus the textbox.
284 if (this.args.promptType == "promptPassword") {
285 if (isInitialLoad)
286 this.ui.password1Textbox.select();
287 else
288 this.ui.password1Textbox.focus();
289 } else {
290 if (isInitialLoad)
291 this.ui.loginTextbox.select();
292 else
293 this.ui.loginTextbox.focus();
294 }
295 }
296 },
298 onCheckbox : function() {
299 this.args.checked = this.ui.checkbox.checked;
300 },
302 onButton0 : function() {
303 this.args.promptActive = false;
304 this.args.ok = true;
305 this.args.buttonNumClicked = 0;
307 let username = this.ui.loginTextbox.value;
308 let password = this.ui.password1Textbox.value;
310 // Return textfield values
311 switch (this.args.promptType) {
312 case "prompt":
313 this.args.value = username;
314 break;
315 case "promptUserAndPass":
316 this.args.user = username;
317 this.args.pass = password;
318 break;
319 case "promptPassword":
320 this.args.pass = password;
321 break;
322 }
323 },
325 onButton1 : function() {
326 this.args.promptActive = false;
327 this.args.buttonNumClicked = 1;
328 },
330 onButton2 : function() {
331 this.args.promptActive = false;
332 this.args.buttonNumClicked = 2;
333 },
335 onButton3 : function() {
336 this.args.promptActive = false;
337 this.args.buttonNumClicked = 3;
338 },
340 abortPrompt : function() {
341 this.args.promptActive = false;
342 this.args.promptAborted = true;
343 },
345 };