Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 const Ci = Components.interfaces;
5 const Cc = Components.classes;
6 const Cr = Components.results;
7 const Cu = Components.utils;
9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
10 Cu.import("resource://gre/modules/Services.jsm");
11 Cu.import("resource://gre/modules/Prompt.jsm");
13 var gPromptService = null;
15 function PromptService() {
16 gPromptService = this;
17 }
19 PromptService.prototype = {
20 classID: Components.ID("{9a61149b-2276-4a0a-b79c-be994ad106cf}"),
22 QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
24 /* ---------- nsIPromptFactory ---------- */
25 // XXX Copied from nsPrompter.js.
26 getPrompt: function getPrompt(domWin, iid) {
27 let doc = this.getDocument();
28 if (!doc) {
29 let fallback = this._getFallbackService();
30 return fallback.QueryInterface(Ci.nsIPromptFactory).getPrompt(domWin, iid);
31 }
33 let p = new InternalPrompt(domWin, doc);
34 p.QueryInterface(iid);
35 return p;
36 },
38 /* ---------- private memebers ---------- */
40 _getFallbackService: function _getFallbackService() {
41 return Components.classesByID["{7ad1b327-6dfa-46ec-9234-f2a620ea7e00}"]
42 .getService(Ci.nsIPromptService);
43 },
45 getDocument: function getDocument() {
46 let win = Services.wm.getMostRecentWindow("navigator:browser");
47 return win ? win.document : null;
48 },
50 // nsIPromptService and nsIPromptService2 methods proxy to our Prompt class
51 // if we can show in-document popups, or to the fallback service otherwise.
52 callProxy: function(aMethod, aArguments) {
53 let prompt;
54 let doc = this.getDocument();
55 if (!doc) {
56 let fallback = this._getFallbackService();
57 return fallback[aMethod].apply(fallback, aArguments);
58 }
59 let domWin = aArguments[0];
60 prompt = new InternalPrompt(domWin, doc);
61 return prompt[aMethod].apply(prompt, Array.prototype.slice.call(aArguments, 1));
62 },
64 /* ---------- nsIPromptService ---------- */
66 alert: function() {
67 return this.callProxy("alert", arguments);
68 },
69 alertCheck: function() {
70 return this.callProxy("alertCheck", arguments);
71 },
72 confirm: function() {
73 return this.callProxy("confirm", arguments);
74 },
75 confirmCheck: function() {
76 return this.callProxy("confirmCheck", arguments);
77 },
78 confirmEx: function() {
79 return this.callProxy("confirmEx", arguments);
80 },
81 prompt: function() {
82 return this.callProxy("prompt", arguments);
83 },
84 promptUsernameAndPassword: function() {
85 return this.callProxy("promptUsernameAndPassword", arguments);
86 },
87 promptPassword: function() {
88 return this.callProxy("promptPassword", arguments);
89 },
90 select: function() {
91 return this.callProxy("select", arguments);
92 },
94 /* ---------- nsIPromptService2 ---------- */
95 promptAuth: function() {
96 return this.callProxy("promptAuth", arguments);
97 },
98 asyncPromptAuth: function() {
99 return this.callProxy("asyncPromptAuth", arguments);
100 }
101 };
103 function InternalPrompt(aDomWin, aDocument) {
104 this._domWin = aDomWin;
105 this._doc = aDocument;
106 }
108 InternalPrompt.prototype = {
109 _domWin: null,
110 _doc: null,
112 QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt, Ci.nsIAuthPrompt, Ci.nsIAuthPrompt2]),
114 /* ---------- internal methods ---------- */
115 _getPrompt: function _getPrompt(aTitle, aText, aButtons, aCheckMsg, aCheckState) {
116 let p = new Prompt({
117 window: this._domWin,
118 title: aTitle,
119 message: aText,
120 buttons: aButtons || [
121 PromptUtils.getLocaleString("OK"),
122 PromptUtils.getLocaleString("Cancel")
123 ]
124 });
125 return p;
126 },
128 addCheckbox: function addCheckbox(aPrompt, aCheckMsg, aCheckState) {
129 // Don't bother to check for aCheckSate. For nsIPomptService interfaces, aCheckState is an
130 // out param and is required to be defined. If we've gotten here without it, something
131 // has probably gone wrong and we should fail
132 if (aCheckMsg) {
133 aPrompt.addCheckbox({
134 label: PromptUtils.cleanUpLabel(aCheckMsg),
135 checked: aCheckState.value
136 });
137 }
139 return aPrompt;
140 },
142 addTextbox: function(prompt, value, autofocus, hint) {
143 prompt.addTextbox({
144 value: (value !== null) ? value : "",
145 autofocus: autofocus,
146 hint: hint
147 });
148 },
150 addPassword: function(prompt, value, autofocus, hint) {
151 prompt.addPassword({
152 value: (value !== null) ? value : "",
153 autofocus: autofocus,
154 hint: hint
155 });
156 },
158 /* Shows a native prompt, and then spins the event loop for this thread while we wait
159 * for a response
160 */
161 showPrompt: function showPrompt(aPrompt) {
162 if (this._domWin) {
163 PromptUtils.fireDialogEvent(this._domWin, "DOMWillOpenModalDialog");
164 let winUtils = this._domWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
165 winUtils.enterModalState();
166 }
168 let retval = null;
169 aPrompt.show(function(data) {
170 retval = data;
171 });
173 // Spin this thread while we wait for a result
174 let thread = Services.tm.currentThread;
175 while (retval == null)
176 thread.processNextEvent(true);
178 if (this._domWin) {
179 let winUtils = this._domWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
180 winUtils.leaveModalState();
181 PromptUtils.fireDialogEvent(this._domWin, "DOMModalDialogClosed");
182 }
184 return retval;
185 },
187 /*
188 * ---------- interface disambiguation ----------
189 *
190 * XXX Copied from nsPrompter.js.
191 *
192 * nsIPrompt and nsIAuthPrompt share 3 method names with slightly
193 * different arguments. All but prompt() have the same number of
194 * arguments, so look at the arg types to figure out how we're being
195 * called. :-(
196 */
197 prompt: function prompt() {
198 if (gPromptService.inContentProcess)
199 return gPromptService.callProxy("prompt", [null].concat(Array.prototype.slice.call(arguments)));
201 // also, the nsIPrompt flavor has 5 args instead of 6.
202 if (typeof arguments[2] == "object")
203 return this.nsIPrompt_prompt.apply(this, arguments);
204 else
205 return this.nsIAuthPrompt_prompt.apply(this, arguments);
206 },
208 promptUsernameAndPassword: function promptUsernameAndPassword() {
209 // Both have 6 args, so use types.
210 if (typeof arguments[2] == "object")
211 return this.nsIPrompt_promptUsernameAndPassword.apply(this, arguments);
212 else
213 return this.nsIAuthPrompt_promptUsernameAndPassword.apply(this, arguments);
214 },
216 promptPassword: function promptPassword() {
217 // Both have 5 args, so use types.
218 if (typeof arguments[2] == "object")
219 return this.nsIPrompt_promptPassword.apply(this, arguments);
220 else
221 return this.nsIAuthPrompt_promptPassword.apply(this, arguments);
222 },
224 /* ---------- nsIPrompt ---------- */
226 alert: function alert(aTitle, aText) {
227 let p = this._getPrompt(aTitle, aText, [ PromptUtils.getLocaleString("OK") ]);
228 p.setHint("alert");
229 this.showPrompt(p);
230 },
232 alertCheck: function alertCheck(aTitle, aText, aCheckMsg, aCheckState) {
233 let p = this._getPrompt(aTitle, aText, [ PromptUtils.getLocaleString("OK") ]);
234 this.addCheckbox(p, aCheckMsg, aCheckState);
235 let data = this.showPrompt(p);
236 if (aCheckState && data.button > -1)
237 aCheckState.value = data.checkbox0;
238 },
240 confirm: function confirm(aTitle, aText) {
241 let p = this._getPrompt(aTitle, aText);
242 p.setHint("confirm");
243 let data = this.showPrompt(p);
244 return (data.button == 0);
245 },
247 confirmCheck: function confirmCheck(aTitle, aText, aCheckMsg, aCheckState) {
248 let p = this._getPrompt(aTitle, aText, null);
249 this.addCheckbox(p, aCheckMsg, aCheckState);
250 let data = this.showPrompt(p);
251 let ok = data.button == 0;
252 if (aCheckState && data.button > -1)
253 aCheckState.value = data.checkbox0;
254 return ok;
255 },
257 confirmEx: function confirmEx(aTitle, aText, aButtonFlags, aButton0,
258 aButton1, aButton2, aCheckMsg, aCheckState) {
259 let buttons = [];
260 let titles = [aButton0, aButton1, aButton2];
261 for (let i = 0; i < 3; i++) {
262 let bTitle = null;
263 switch (aButtonFlags & 0xff) {
264 case Ci.nsIPromptService.BUTTON_TITLE_OK :
265 bTitle = PromptUtils.getLocaleString("OK");
266 break;
267 case Ci.nsIPromptService.BUTTON_TITLE_CANCEL :
268 bTitle = PromptUtils.getLocaleString("Cancel");
269 break;
270 case Ci.nsIPromptService.BUTTON_TITLE_YES :
271 bTitle = PromptUtils.getLocaleString("Yes");
272 break;
273 case Ci.nsIPromptService.BUTTON_TITLE_NO :
274 bTitle = PromptUtils.getLocaleString("No");
275 break;
276 case Ci.nsIPromptService.BUTTON_TITLE_SAVE :
277 bTitle = PromptUtils.getLocaleString("Save");
278 break;
279 case Ci.nsIPromptService.BUTTON_TITLE_DONT_SAVE :
280 bTitle = PromptUtils.getLocaleString("DontSave");
281 break;
282 case Ci.nsIPromptService.BUTTON_TITLE_REVERT :
283 bTitle = PromptUtils.getLocaleString("Revert");
284 break;
285 case Ci.nsIPromptService.BUTTON_TITLE_IS_STRING :
286 bTitle = PromptUtils.cleanUpLabel(titles[i]);
287 break;
288 }
290 if (bTitle)
291 buttons.push(bTitle);
293 aButtonFlags >>= 8;
294 }
296 let p = this._getPrompt(aTitle, aText, buttons);
297 this.addCheckbox(p, aCheckMsg, aCheckState);
298 let data = this.showPrompt(p);
299 if (aCheckState && data.button > -1)
300 aCheckState.value = data.checkbox0;
301 return data.button;
302 },
304 nsIPrompt_prompt: function nsIPrompt_prompt(aTitle, aText, aValue, aCheckMsg, aCheckState) {
305 let p = this._getPrompt(aTitle, aText, null, aCheckMsg, aCheckState);
306 p.setHint("prompt");
307 this.addTextbox(p, aValue.value, true);
308 this.addCheckbox(p, aCheckMsg, aCheckState);
309 let data = this.showPrompt(p);
311 let ok = data.button == 0;
312 if (aCheckState && data.button > -1)
313 aCheckState.value = data.checkbox0;
314 if (ok)
315 aValue.value = data.textbox0;
316 return ok;
317 },
319 nsIPrompt_promptPassword: function nsIPrompt_promptPassword(
320 aTitle, aText, aPassword, aCheckMsg, aCheckState) {
321 let p = this._getPrompt(aTitle, aText, null);
322 this.addPassword(p, aPassword.value, true, PromptUtils.getLocaleString("password", "passwdmgr"));
323 this.addCheckbox(p, aCheckMsg, aCheckState);
324 let data = this.showPrompt(p);
326 let ok = data.button == 0;
327 if (aCheckState && data.button > -1)
328 aCheckState.value = data.checkbox0;
329 if (ok)
330 aPassword.value = data.password0;
331 return ok;
332 },
334 nsIPrompt_promptUsernameAndPassword: function nsIPrompt_promptUsernameAndPassword(
335 aTitle, aText, aUsername, aPassword, aCheckMsg, aCheckState) {
336 let p = this._getPrompt(aTitle, aText, null);
337 this.addTextbox(p, aUsername.value, true, PromptUtils.getLocaleString("username", "passwdmgr"));
338 this.addPassword(p, aPassword.value, false, PromptUtils.getLocaleString("password", "passwdmgr"));
339 this.addCheckbox(p, aCheckMsg, aCheckState);
340 let data = this.showPrompt(p);
342 let ok = data.button == 0;
343 if (aCheckState && data.button > -1)
344 aCheckState.value = data.checkbox0;
346 if (ok) {
347 aUsername.value = data.textbox0;
348 aPassword.value = data.password0;
349 }
350 return ok;
351 },
353 select: function select(aTitle, aText, aCount, aSelectList, aOutSelection) {
354 let p = this._getPrompt(aTitle, aText, [ PromptUtils.getLocaleString("OK") ]);
355 p.addMenulist({ values: aSelectList });
356 let data = this.showPrompt(p);
358 let ok = data.button == 0;
359 if (ok)
360 aOutSelection.value = data.menulist0;
362 return ok;
363 },
365 /* ---------- nsIAuthPrompt ---------- */
367 nsIAuthPrompt_prompt : function (title, text, passwordRealm, savePassword, defaultText, result) {
368 // TODO: Port functions from nsLoginManagerPrompter.js to here
369 if (defaultText)
370 result.value = defaultText;
371 return this.nsIPrompt_prompt(title, text, result, null, {});
372 },
374 nsIAuthPrompt_promptUsernameAndPassword : function (aTitle, aText, aPasswordRealm, aSavePassword, aUser, aPass) {
375 return nsIAuthPrompt_loginPrompt(aTitle, aText, aPasswordRealm, aSavePassword, aUser, aPass);
376 },
378 nsIAuthPrompt_promptPassword : function (aTitle, aText, aPasswordRealm, aSavePassword, aPass) {
379 return nsIAuthPrompt_loginPrompt(aTitle, aText, aPasswordRealm, aSavePassword, null, aPass);
380 },
382 nsIAuthPrompt_loginPrompt: function(aTitle, aPasswordRealm, aSavePassword, aUser, aPass) {
383 let checkMsg = null;
384 let check = { value: false };
385 let [hostname, realm, aUser] = PromptUtils.getHostnameAndRealm(aPasswordRealm);
387 let canSave = PromptUtils.canSaveLogin(hostname, aSavePassword);
388 if (canSave) {
389 // Look for existing logins.
390 let foundLogins = PromptUtils.pwmgr.findLogins({}, hostname, null, realm);
391 [checkMsg, check] = PromptUtils.getUsernameAndPassword(foundLogins, aUser, aPass);
392 }
394 let ok = false;
395 if (aUser)
396 ok = this.nsIPrompt_promptUsernameAndPassword(aTitle, aText, aUser, aPass, checkMsg, check);
397 else
398 ok = this.nsIPrompt_promptPassword(aTitle, aText, aPass, checkMsg, check);
400 if (ok && canSave && check.value)
401 PromptUtils.savePassword(hostname, realm, aUser, aPass);
403 return ok;
404 },
406 /* ---------- nsIAuthPrompt2 ---------- */
408 promptAuth: function promptAuth(aChannel, aLevel, aAuthInfo) {
409 let checkMsg = null;
410 let check = { value: false };
411 let message = PromptUtils.makeDialogText(aChannel, aAuthInfo);
412 let [username, password] = PromptUtils.getAuthInfo(aAuthInfo);
413 let [hostname, httpRealm] = PromptUtils.getAuthTarget(aChannel, aAuthInfo);
414 let foundLogins = PromptUtils.pwmgr.findLogins({}, hostname, null, httpRealm);
416 let canSave = PromptUtils.canSaveLogin(hostname, null);
417 if (canSave)
418 [checkMsg, check] = PromptUtils.getUsernameAndPassword(foundLogins, username, password);
420 if (username.value && password.value) {
421 PromptUtils.setAuthInfo(aAuthInfo, username.value, password.value);
422 }
424 let canAutologin = false;
425 if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY &&
426 !(aAuthInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) &&
427 Services.prefs.getBoolPref("signon.autologin.proxy"))
428 canAutologin = true;
430 let ok = canAutologin;
431 if (!ok && aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
432 ok = this.nsIPrompt_promptPassword(null, message, password, checkMsg, check);
433 else if (!ok)
434 ok = this.nsIPrompt_promptUsernameAndPassword(null, message, username, password, checkMsg, check);
436 PromptUtils.setAuthInfo(aAuthInfo, username.value, password.value);
438 if (ok && canSave && check.value)
439 PromptUtils.savePassword(foundLogins, username, password, hostname, httpRealm);
441 return ok;
442 },
444 _asyncPrompts: {},
445 _asyncPromptInProgress: false,
447 _doAsyncPrompt : function() {
448 if (this._asyncPromptInProgress)
449 return;
451 // Find the first prompt key we have in the queue
452 let hashKey = null;
453 for (hashKey in this._asyncPrompts)
454 break;
456 if (!hashKey)
457 return;
459 // If login manger has logins for this host, defer prompting if we're
460 // already waiting on a master password entry.
461 let prompt = this._asyncPrompts[hashKey];
462 let prompter = prompt.prompter;
463 let [hostname, httpRealm] = PromptUtils.getAuthTarget(prompt.channel, prompt.authInfo);
464 let foundLogins = PromptUtils.pwmgr.findLogins({}, hostname, null, httpRealm);
465 if (foundLogins.length > 0 && PromptUtils.pwmgr.uiBusy)
466 return;
468 this._asyncPromptInProgress = true;
469 prompt.inProgress = true;
471 let self = this;
473 let runnable = {
474 run: function() {
475 let ok = false;
476 try {
477 ok = prompter.promptAuth(prompt.channel, prompt.level, prompt.authInfo);
478 } catch (e) {
479 Cu.reportError("_doAsyncPrompt:run: " + e + "\n");
480 }
482 delete self._asyncPrompts[hashKey];
483 prompt.inProgress = false;
484 self._asyncPromptInProgress = false;
486 for (let consumer of prompt.consumers) {
487 if (!consumer.callback)
488 // Not having a callback means that consumer didn't provide it
489 // or canceled the notification
490 continue;
492 try {
493 if (ok)
494 consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
495 else
496 consumer.callback.onAuthCancelled(consumer.context, true);
497 } catch (e) { /* Throw away exceptions caused by callback */ }
498 }
499 self._doAsyncPrompt();
500 }
501 }
503 Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
504 },
506 asyncPromptAuth: function asyncPromptAuth(aChannel, aCallback, aContext, aLevel, aAuthInfo) {
507 let cancelable = null;
508 try {
509 // If the user submits a login but it fails, we need to remove the
510 // notification bar that was displayed. Conveniently, the user will
511 // be prompted for authentication again, which brings us here.
512 //this._removeLoginNotifications();
514 cancelable = {
515 QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
516 callback: aCallback,
517 context: aContext,
518 cancel: function() {
519 this.callback.onAuthCancelled(this.context, false);
520 this.callback = null;
521 this.context = null;
522 }
523 };
524 let [hostname, httpRealm] = PromptUtils.getAuthTarget(aChannel, aAuthInfo);
525 let hashKey = aLevel + "|" + hostname + "|" + httpRealm;
526 let asyncPrompt = this._asyncPrompts[hashKey];
527 if (asyncPrompt) {
528 asyncPrompt.consumers.push(cancelable);
529 return cancelable;
530 }
532 asyncPrompt = {
533 consumers: [cancelable],
534 channel: aChannel,
535 authInfo: aAuthInfo,
536 level: aLevel,
537 inProgress : false,
538 prompter: this
539 }
541 this._asyncPrompts[hashKey] = asyncPrompt;
542 this._doAsyncPrompt();
543 } catch (e) {
544 Cu.reportError("PromptService: " + e + "\n");
545 throw e;
546 }
547 return cancelable;
548 }
549 };
551 let PromptUtils = {
552 getLocaleString: function pu_getLocaleString(aKey, aService) {
553 if (aService == "passwdmgr")
554 return this.cleanUpLabel(this.passwdBundle.GetStringFromName(aKey));
556 return this.cleanUpLabel(this.bundle.GetStringFromName(aKey));
557 },
559 //
560 // Copied from chrome://global/content/commonDialog.js
561 //
562 cleanUpLabel: function cleanUpLabel(aLabel) {
563 // This is for labels which may contain embedded access keys.
564 // If we end in (&X) where X represents the access key, optionally preceded
565 // by spaces and/or followed by the ':' character,
566 // remove the access key placeholder + leading spaces from the label.
567 // Otherwise a character preceded by one but not two &s is the access key.
569 // Note that if you change the following code, see the comment of
570 // nsTextBoxFrame::UpdateAccessTitle.
571 if (!aLabel)
572 return "";
574 if (/ *\(\&([^&])\)(:)?$/.test(aLabel)) {
575 aLabel = RegExp.leftContext + RegExp.$2;
576 } else if (/^(.*[^&])?\&(([^&]).*$)/.test(aLabel)) {
577 aLabel = RegExp.$1 + RegExp.$2;
578 }
580 // Special code for using that & symbol
581 aLabel = aLabel.replace(/\&\&/g, "&");
583 return aLabel;
584 },
586 get pwmgr() {
587 delete this.pwmgr;
588 return this.pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
589 },
591 getHostnameAndRealm: function pu_getHostnameAndRealm(aRealmString) {
592 let httpRealm = /^.+ \(.+\)$/;
593 if (httpRealm.test(aRealmString))
594 return [null, null, null];
596 let uri = Services.io.newURI(aRealmString, null, null);
597 let pathname = "";
599 if (uri.path != "/")
600 pathname = uri.path;
602 let formattedHostname = this._getFormattedHostname(uri);
603 return [formattedHostname, formattedHostname + pathname, uri.username];
604 },
606 canSaveLogin: function pu_canSaveLogin(aHostname, aSavePassword) {
607 let canSave = !this._inPrivateBrowsing && this.pwmgr.getLoginSavingEnabled(aHostname)
608 if (aSavePassword)
609 canSave = canSave && (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY)
610 return canSave;
611 },
613 getUsernameAndPassword: function pu_getUsernameAndPassword(aFoundLogins, aUser, aPass) {
614 let checkLabel = null;
615 let check = { value: false };
616 let selectedLogin;
618 checkLabel = this.getLocaleString("saveButton", "passwdmgr");
620 // XXX Like the original code, we can't deal with multiple
621 // account selection. (bug 227632)
622 if (aFoundLogins.length > 0) {
623 selectedLogin = aFoundLogins[0];
625 // If the caller provided a username, try to use it. If they
626 // provided only a password, this will try to find a password-only
627 // login (or return null if none exists).
628 if (aUser.value)
629 selectedLogin = this.findLogin(aFoundLogins, "username", aUser.value);
631 if (selectedLogin) {
632 check.value = true;
633 aUser.value = selectedLogin.username;
634 // If the caller provided a password, prefer it.
635 if (!aPass.value)
636 aPass.value = selectedLogin.password;
637 }
638 }
640 return [checkLabel, check];
641 },
643 findLogin: function pu_findLogin(aLogins, aName, aValue) {
644 for (let i = 0; i < aLogins.length; i++)
645 if (aLogins[i][aName] == aValue)
646 return aLogins[i];
647 return null;
648 },
650 savePassword: function pu_savePassword(aLogins, aUser, aPass, aHostname, aRealm) {
651 let selectedLogin = this.findLogin(aLogins, "username", aUser.value);
653 // If we didn't find an existing login, or if the username
654 // changed, save as a new login.
655 if (!selectedLogin) {
656 // add as new
657 var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
658 newLogin.init(aHostname, null, aRealm, aUser.value, aPass.value, "", "");
659 this.pwmgr.addLogin(newLogin);
660 } else if (aPass.value != selectedLogin.password) {
661 // update password
662 this.updateLogin(selectedLogin, aPass.value);
663 } else {
664 this.updateLogin(selectedLogin);
665 }
666 },
668 updateLogin: function pu_updateLogin(aLogin, aPassword) {
669 let now = Date.now();
670 let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag);
671 if (aPassword) {
672 propBag.setProperty("password", aPassword);
673 // Explicitly set the password change time here (even though it would
674 // be changed automatically), to ensure that it's exactly the same
675 // value as timeLastUsed.
676 propBag.setProperty("timePasswordChanged", now);
677 }
678 propBag.setProperty("timeLastUsed", now);
679 propBag.setProperty("timesUsedIncrement", 1);
681 this.pwmgr.modifyLogin(aLogin, propBag);
682 },
684 // JS port of http://mxr.mozilla.org/mozilla-central/source/embedding/components/windowwatcher/src/nsPrompt.cpp#388
685 makeDialogText: function pu_makeDialogText(aChannel, aAuthInfo) {
686 let isProxy = (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY);
687 let isPassOnly = (aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD);
689 let username = aAuthInfo.username;
690 let [displayHost, realm] = this.getAuthTarget(aChannel, aAuthInfo);
692 // Suppress "the site says: $realm" when we synthesized a missing realm.
693 if (!aAuthInfo.realm && !isProxy)
694 realm = "";
696 // Trim obnoxiously long realms.
697 if (realm.length > 150) {
698 realm = realm.substring(0, 150);
699 // Append "..." (or localized equivalent).
700 realm += this.ellipsis;
701 }
703 let text;
704 if (isProxy)
705 text = this.bundle.formatStringFromName("EnterLoginForProxy", [realm, displayHost], 2);
706 else if (isPassOnly)
707 text = this.bundle.formatStringFromName("EnterPasswordFor", [username, displayHost], 2);
708 else if (!realm)
709 text = this.bundle.formatStringFromName("EnterUserPasswordFor", [displayHost], 1);
710 else
711 text = this.bundle.formatStringFromName("EnterLoginForRealm", [realm, displayHost], 2);
713 return text;
714 },
716 // JS port of http://mxr.mozilla.org/mozilla-central/source/embedding/components/windowwatcher/public/nsPromptUtils.h#89
717 getAuthHostPort: function pu_getAuthHostPort(aChannel, aAuthInfo) {
718 let uri = aChannel.URI;
719 let res = { host: null, port: -1 };
720 if (aAuthInfo.flags & aAuthInfo.AUTH_PROXY) {
721 let proxy = aChannel.QueryInterface(Ci.nsIProxiedChannel);
722 res.host = proxy.proxyInfo.host;
723 res.port = proxy.proxyInfo.port;
724 } else {
725 res.host = uri.host;
726 res.port = uri.port;
727 }
728 return res;
729 },
731 getAuthTarget : function pu_getAuthTarget(aChannel, aAuthInfo) {
732 let hostname, realm;
733 // If our proxy is demanding authentication, don't use the
734 // channel's actual destination.
735 if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
736 if (!(aChannel instanceof Ci.nsIProxiedChannel))
737 throw "proxy auth needs nsIProxiedChannel";
739 let info = aChannel.proxyInfo;
740 if (!info)
741 throw "proxy auth needs nsIProxyInfo";
743 // Proxies don't have a scheme, but we'll use "moz-proxy://"
744 // so that it's more obvious what the login is for.
745 let idnService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
746 hostname = "moz-proxy://" + idnService.convertUTF8toACE(info.host) + ":" + info.port;
747 realm = aAuthInfo.realm;
748 if (!realm)
749 realm = hostname;
751 return [hostname, realm];
752 }
753 hostname = this.getFormattedHostname(aChannel.URI);
755 // If a HTTP WWW-Authenticate header specified a realm, that value
756 // will be available here. If it wasn't set or wasn't HTTP, we'll use
757 // the formatted hostname instead.
758 realm = aAuthInfo.realm;
759 if (!realm)
760 realm = hostname;
762 return [hostname, realm];
763 },
765 getAuthInfo : function pu_getAuthInfo(aAuthInfo) {
766 let flags = aAuthInfo.flags;
767 let username = {value: ""};
768 let password = {value: ""};
770 if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain)
771 username.value = aAuthInfo.domain + "\\" + aAuthInfo.username;
772 else
773 username.value = aAuthInfo.username;
775 password.value = aAuthInfo.password
777 return [username, password];
778 },
780 setAuthInfo : function (aAuthInfo, username, password) {
781 var flags = aAuthInfo.flags;
782 if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
783 // Domain is separated from username by a backslash
784 var idx = username.indexOf("\\");
785 if (idx == -1) {
786 aAuthInfo.username = username;
787 } else {
788 aAuthInfo.domain = username.substring(0, idx);
789 aAuthInfo.username = username.substring(idx+1);
790 }
791 } else {
792 aAuthInfo.username = username;
793 }
794 aAuthInfo.password = password;
795 },
797 getFormattedHostname : function pu_getFormattedHostname(uri) {
798 let scheme = uri.scheme;
799 let hostname = scheme + "://" + uri.host;
801 // If the URI explicitly specified a port, only include it when
802 // it's not the default. (We never want "http://foo.com:80")
803 port = uri.port;
804 if (port != -1) {
805 let handler = Services.io.getProtocolHandler(scheme);
806 if (port != handler.defaultPort)
807 hostname += ":" + port;
808 }
809 return hostname;
810 },
812 fireDialogEvent: function(aDomWin, aEventName) {
813 // accessing the document object can throw if this window no longer exists. See bug 789888.
814 try {
815 if (!aDomWin.document)
816 return;
817 let event = aDomWin.document.createEvent("Events");
818 event.initEvent(aEventName, true, true);
819 let winUtils = aDomWin.QueryInterface(Ci.nsIInterfaceRequestor)
820 .getInterface(Ci.nsIDOMWindowUtils);
821 winUtils.dispatchEventToChromeOnly(aDomWin, event);
822 } catch(ex) {
823 }
824 }
825 };
827 XPCOMUtils.defineLazyGetter(PromptUtils, "passwdBundle", function () {
828 return Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
829 });
831 XPCOMUtils.defineLazyGetter(PromptUtils, "bundle", function () {
832 return Services.strings.createBundle("chrome://global/locale/commonDialogs.properties");
833 });
835 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PromptService]);