|
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/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = ["CommonDialog"]; |
|
6 |
|
7 const Ci = Components.interfaces; |
|
8 const Cr = Components.results; |
|
9 const Cc = Components.classes; |
|
10 const Cu = Components.utils; |
|
11 |
|
12 Cu.import("resource://gre/modules/Services.jsm"); |
|
13 |
|
14 |
|
15 this.CommonDialog = function CommonDialog(args, ui) { |
|
16 this.args = args; |
|
17 this.ui = ui; |
|
18 } |
|
19 |
|
20 CommonDialog.prototype = { |
|
21 args : null, |
|
22 ui : null, |
|
23 |
|
24 hasInputField : true, |
|
25 numButtons : undefined, |
|
26 iconClass : undefined, |
|
27 soundID : undefined, |
|
28 focusTimer : null, |
|
29 |
|
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 } |
|
90 |
|
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; |
|
98 |
|
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; |
|
118 |
|
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); |
|
126 |
|
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)); |
|
132 |
|
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 } |
|
140 |
|
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)); |
|
145 |
|
146 // set default result to cancelled |
|
147 this.args.ok = false; |
|
148 this.args.buttonNumClicked = 1; |
|
149 |
|
150 |
|
151 // Set the default button |
|
152 let b = (this.args.defaultButtonNum || 0); |
|
153 let button = this.ui["button" + b]; |
|
154 |
|
155 if (xulDialog) |
|
156 xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b]; |
|
157 else |
|
158 button.setAttribute("default", "true"); |
|
159 |
|
160 // Set default focus / selection. |
|
161 this.setDefaultFocus(true); |
|
162 |
|
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 } |
|
172 |
|
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 } |
|
183 |
|
184 let topic = "common-dialog-loaded"; |
|
185 if (!xulDialog) |
|
186 topic = "tabmodal-dialog-loaded"; |
|
187 Services.obs.notifyObservers(this.ui.prompt, topic, null); |
|
188 }, |
|
189 |
|
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 &. |
|
197 |
|
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 } |
|
208 |
|
209 // && is the magic sequence to embed an & in your label. |
|
210 aLabel = aLabel.replace(/\&\&/g, "&"); |
|
211 aNode.label = aLabel; |
|
212 |
|
213 // XXXjag bug 325251 |
|
214 // Need to set this after aNode.setAttribute("value", aLabel); |
|
215 if (accessKey) |
|
216 aNode.accessKey = accessKey; |
|
217 }, |
|
218 |
|
219 |
|
220 initTextbox : function (aName, aValue) { |
|
221 this.ui[aName + "Container"].hidden = false; |
|
222 this.ui[aName + "Textbox"].setAttribute("value", |
|
223 aValue !== null ? aValue : ""); |
|
224 }, |
|
225 |
|
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 }, |
|
232 |
|
233 onBlur : function (aEvent) { |
|
234 if (aEvent.target != this.ui.focusTarget) |
|
235 return; |
|
236 this.setButtonsEnabledState(false); |
|
237 |
|
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 }, |
|
245 |
|
246 onFocus : function (aEvent) { |
|
247 if (aEvent.target != this.ui.focusTarget) |
|
248 return; |
|
249 this.startOnFocusDelay(); |
|
250 }, |
|
251 |
|
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 }, |
|
265 |
|
266 onFocusTimeout : function() { |
|
267 this.focusTimer = null; |
|
268 this.setButtonsEnabledState(true); |
|
269 }, |
|
270 |
|
271 setDefaultFocus : function(isInitialLoad) { |
|
272 let b = (this.args.defaultButtonNum || 0); |
|
273 let button = this.ui["button" + b]; |
|
274 |
|
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 }, |
|
297 |
|
298 onCheckbox : function() { |
|
299 this.args.checked = this.ui.checkbox.checked; |
|
300 }, |
|
301 |
|
302 onButton0 : function() { |
|
303 this.args.promptActive = false; |
|
304 this.args.ok = true; |
|
305 this.args.buttonNumClicked = 0; |
|
306 |
|
307 let username = this.ui.loginTextbox.value; |
|
308 let password = this.ui.password1Textbox.value; |
|
309 |
|
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 }, |
|
324 |
|
325 onButton1 : function() { |
|
326 this.args.promptActive = false; |
|
327 this.args.buttonNumClicked = 1; |
|
328 }, |
|
329 |
|
330 onButton2 : function() { |
|
331 this.args.promptActive = false; |
|
332 this.args.buttonNumClicked = 2; |
|
333 }, |
|
334 |
|
335 onButton3 : function() { |
|
336 this.args.promptActive = false; |
|
337 this.args.buttonNumClicked = 3; |
|
338 }, |
|
339 |
|
340 abortPrompt : function() { |
|
341 this.args.promptActive = false; |
|
342 this.args.promptAborted = true; |
|
343 }, |
|
344 |
|
345 }; |