Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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 = ["LoginManagerContent"];
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/XPCOMUtils.jsm");
13 Cu.import("resource://gre/modules/Services.jsm");
14 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
16 // These mirror signon.* prefs.
17 var gEnabled, gDebug, gAutofillForms, gStoreWhenAutocompleteOff;
19 function log(...pieces) {
20 function generateLogMessage(args) {
21 let strings = ['Login Manager (content):'];
23 args.forEach(function(arg) {
24 if (typeof arg === 'string') {
25 strings.push(arg);
26 } else if (typeof arg === 'undefined') {
27 strings.push('undefined');
28 } else if (arg === null) {
29 strings.push('null');
30 } else {
31 try {
32 strings.push(JSON.stringify(arg, null, 2));
33 } catch(err) {
34 strings.push("<<something>>");
35 }
36 }
37 });
38 return strings.join(' ');
39 }
41 if (!gDebug)
42 return;
44 let message = generateLogMessage(pieces);
45 dump(message + "\n");
46 Services.console.logStringMessage(message);
47 }
50 var observer = {
51 QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
52 Ci.nsIFormSubmitObserver,
53 Ci.nsISupportsWeakReference]),
55 // nsIFormSubmitObserver
56 notify : function (formElement, aWindow, actionURI) {
57 log("observer notified for form submission.");
59 // We're invoked before the content's |onsubmit| handlers, so we
60 // can grab form data before it might be modified (see bug 257781).
62 try {
63 LoginManagerContent._onFormSubmit(formElement);
64 } catch (e) {
65 log("Caught error in onFormSubmit:", e);
66 }
68 return true; // Always return true, or form submit will be canceled.
69 },
71 onPrefChange : function() {
72 gDebug = Services.prefs.getBoolPref("signon.debug");
73 gEnabled = Services.prefs.getBoolPref("signon.rememberSignons");
74 gAutofillForms = Services.prefs.getBoolPref("signon.autofillForms");
75 gStoreWhenAutocompleteOff = Services.prefs.getBoolPref("signon.storeWhenAutocompleteOff");
76 },
77 };
79 Services.obs.addObserver(observer, "earlyformsubmit", false);
80 var prefBranch = Services.prefs.getBranch("signon.");
81 prefBranch.addObserver("", observer.onPrefChange, false);
83 observer.onPrefChange(); // read initial values
86 var LoginManagerContent = {
88 __formFillService : null, // FormFillController, for username autocompleting
89 get _formFillService() {
90 if (!this.__formFillService)
91 this.__formFillService =
92 Cc["@mozilla.org/satchel/form-fill-controller;1"].
93 getService(Ci.nsIFormFillController);
94 return this.__formFillService;
95 },
98 onFormPassword: function (event) {
99 if (!event.isTrusted)
100 return;
102 if (!gEnabled)
103 return;
105 let form = event.target;
106 let doc = form.ownerDocument;
108 log("onFormPassword for", doc.documentURI);
110 // If there are no logins for this site, bail out now.
111 let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
112 if (!Services.logins.countLogins(formOrigin, "", null))
113 return;
115 // If we're currently displaying a master password prompt, defer
116 // processing this form until the user handles the prompt.
117 if (Services.logins.uiBusy) {
118 log("deferring onFormPassword for", doc.documentURI);
119 let self = this;
120 let observer = {
121 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
123 observe: function (subject, topic, data) {
124 log("Got deferred onFormPassword notification:", topic);
125 // Only run observer once.
126 Services.obs.removeObserver(this, "passwordmgr-crypto-login");
127 Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
128 if (topic == "passwordmgr-crypto-loginCanceled")
129 return;
130 self.onFormPassword(event);
131 },
132 handleEvent : function (event) {
133 // Not expected to be called
134 }
135 };
136 // Trickyness follows: We want an observer, but don't want it to
137 // cause leaks. So add the observer with a weak reference, and use
138 // a dummy event listener (a strong reference) to keep it alive
139 // until the form is destroyed.
140 Services.obs.addObserver(observer, "passwordmgr-crypto-login", true);
141 Services.obs.addObserver(observer, "passwordmgr-crypto-loginCanceled", true);
142 form.addEventListener("mozCleverClosureHack", observer);
143 return;
144 }
146 let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isWindowPrivate(doc.defaultView);
148 this._fillForm(form, autofillForm, false, false, null);
149 },
152 /*
153 * onUsernameInput
154 *
155 * Listens for DOMAutoComplete and blur events on an input field.
156 */
157 onUsernameInput : function(event) {
158 if (!event.isTrusted)
159 return;
161 if (!gEnabled)
162 return;
164 var acInputField = event.target;
166 // This is probably a bit over-conservatative.
167 if (!(acInputField.ownerDocument instanceof Ci.nsIDOMHTMLDocument))
168 return;
170 if (!this._isUsernameFieldType(acInputField))
171 return;
173 var acForm = acInputField.form;
174 if (!acForm)
175 return;
177 // If the username is blank, bail out now -- we don't want
178 // fillForm() to try filling in a login without a username
179 // to filter on (bug 471906).
180 if (!acInputField.value)
181 return;
183 log("onUsernameInput from", event.type);
185 // Make sure the username field fillForm will use is the
186 // same field as the autocomplete was activated on.
187 var [usernameField, passwordField, ignored] =
188 this._getFormFields(acForm, false);
189 if (usernameField == acInputField && passwordField) {
190 // If the user has a master password but itsn't logged in, bail
191 // out now to prevent annoying prompts.
192 if (!Services.logins.isLoggedIn)
193 return;
195 this._fillForm(acForm, true, true, true, null);
196 } else {
197 // Ignore the event, it's for some input we don't care about.
198 }
199 },
202 /*
203 * _getPasswordFields
204 *
205 * Returns an array of password field elements for the specified form.
206 * If no pw fields are found, or if more than 3 are found, then null
207 * is returned.
208 *
209 * skipEmptyFields can be set to ignore password fields with no value.
210 */
211 _getPasswordFields : function (form, skipEmptyFields) {
212 // Locate the password fields in the form.
213 var pwFields = [];
214 for (var i = 0; i < form.elements.length; i++) {
215 var element = form.elements[i];
216 if (!(element instanceof Ci.nsIDOMHTMLInputElement) ||
217 element.type != "password")
218 continue;
220 if (skipEmptyFields && !element.value)
221 continue;
223 pwFields[pwFields.length] = {
224 index : i,
225 element : element
226 };
227 }
229 // If too few or too many fields, bail out.
230 if (pwFields.length == 0) {
231 log("(form ignored -- no password fields.)");
232 return null;
233 } else if (pwFields.length > 3) {
234 log("(form ignored -- too many password fields. [ got ",
235 pwFields.length, "])");
236 return null;
237 }
239 return pwFields;
240 },
243 _isUsernameFieldType: function(element) {
244 if (!(element instanceof Ci.nsIDOMHTMLInputElement))
245 return false;
247 let fieldType = (element.hasAttribute("type") ?
248 element.getAttribute("type").toLowerCase() :
249 element.type);
250 if (fieldType == "text" ||
251 fieldType == "email" ||
252 fieldType == "url" ||
253 fieldType == "tel" ||
254 fieldType == "number") {
255 return true;
256 }
257 return false;
258 },
261 /*
262 * _getFormFields
263 *
264 * Returns the username and password fields found in the form.
265 * Can handle complex forms by trying to figure out what the
266 * relevant fields are.
267 *
268 * Returns: [usernameField, newPasswordField, oldPasswordField]
269 *
270 * usernameField may be null.
271 * newPasswordField will always be non-null.
272 * oldPasswordField may be null. If null, newPasswordField is just
273 * "theLoginField". If not null, the form is apparently a
274 * change-password field, with oldPasswordField containing the password
275 * that is being changed.
276 */
277 _getFormFields : function (form, isSubmission) {
278 var usernameField = null;
280 // Locate the password field(s) in the form. Up to 3 supported.
281 // If there's no password field, there's nothing for us to do.
282 var pwFields = this._getPasswordFields(form, isSubmission);
283 if (!pwFields)
284 return [null, null, null];
287 // Locate the username field in the form by searching backwards
288 // from the first passwordfield, assume the first text field is the
289 // username. We might not find a username field if the user is
290 // already logged in to the site.
291 for (var i = pwFields[0].index - 1; i >= 0; i--) {
292 var element = form.elements[i];
293 if (this._isUsernameFieldType(element)) {
294 usernameField = element;
295 break;
296 }
297 }
299 if (!usernameField)
300 log("(form -- no username field found)");
303 // If we're not submitting a form (it's a page load), there are no
304 // password field values for us to use for identifying fields. So,
305 // just assume the first password field is the one to be filled in.
306 if (!isSubmission || pwFields.length == 1)
307 return [usernameField, pwFields[0].element, null];
310 // Try to figure out WTF is in the form based on the password values.
311 var oldPasswordField, newPasswordField;
312 var pw1 = pwFields[0].element.value;
313 var pw2 = pwFields[1].element.value;
314 var pw3 = (pwFields[2] ? pwFields[2].element.value : null);
316 if (pwFields.length == 3) {
317 // Look for two identical passwords, that's the new password
319 if (pw1 == pw2 && pw2 == pw3) {
320 // All 3 passwords the same? Weird! Treat as if 1 pw field.
321 newPasswordField = pwFields[0].element;
322 oldPasswordField = null;
323 } else if (pw1 == pw2) {
324 newPasswordField = pwFields[0].element;
325 oldPasswordField = pwFields[2].element;
326 } else if (pw2 == pw3) {
327 oldPasswordField = pwFields[0].element;
328 newPasswordField = pwFields[2].element;
329 } else if (pw1 == pw3) {
330 // A bit odd, but could make sense with the right page layout.
331 newPasswordField = pwFields[0].element;
332 oldPasswordField = pwFields[1].element;
333 } else {
334 // We can't tell which of the 3 passwords should be saved.
335 log("(form ignored -- all 3 pw fields differ)");
336 return [null, null, null];
337 }
338 } else { // pwFields.length == 2
339 if (pw1 == pw2) {
340 // Treat as if 1 pw field
341 newPasswordField = pwFields[0].element;
342 oldPasswordField = null;
343 } else {
344 // Just assume that the 2nd password is the new password
345 oldPasswordField = pwFields[0].element;
346 newPasswordField = pwFields[1].element;
347 }
348 }
350 return [usernameField, newPasswordField, oldPasswordField];
351 },
354 /*
355 * _isAutoCompleteDisabled
356 *
357 * Returns true if the page requests autocomplete be disabled for the
358 * specified form input.
359 */
360 _isAutocompleteDisabled : function (element) {
361 if (element && element.hasAttribute("autocomplete") &&
362 element.getAttribute("autocomplete").toLowerCase() == "off")
363 return true;
365 return false;
366 },
369 /*
370 * _onFormSubmit
371 *
372 * Called by the our observer when notified of a form submission.
373 * [Note that this happens before any DOM onsubmit handlers are invoked.]
374 * Looks for a password change in the submitted form, so we can update
375 * our stored password.
376 */
377 _onFormSubmit : function (form) {
379 // For E10S this will need to move.
380 function getPrompter(aWindow) {
381 var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
382 createInstance(Ci.nsILoginManagerPrompter);
383 prompterSvc.init(aWindow);
384 return prompterSvc;
385 }
387 var doc = form.ownerDocument;
388 var win = doc.defaultView;
390 if (PrivateBrowsingUtils.isWindowPrivate(win)) {
391 // We won't do anything in private browsing mode anyway,
392 // so there's no need to perform further checks.
393 log("(form submission ignored in private browsing mode)");
394 return;
395 }
397 // If password saving is disabled (globally or for host), bail out now.
398 if (!gEnabled)
399 return;
401 var hostname = LoginUtils._getPasswordOrigin(doc.documentURI);
402 if (!hostname) {
403 log("(form submission ignored -- invalid hostname)");
404 return;
405 }
407 // Somewhat gross hack - we don't want to show the "remember password"
408 // notification on about:accounts for Firefox.
409 let topWin = win.top;
410 if (/^about:accounts($|\?)/i.test(topWin.document.documentURI)) {
411 log("(form submission ignored -- about:accounts)");
412 return;
413 }
415 var formSubmitURL = LoginUtils._getActionOrigin(form)
416 if (!Services.logins.getLoginSavingEnabled(hostname)) {
417 log("(form submission ignored -- saving is disabled for:", hostname, ")");
418 return;
419 }
422 // Get the appropriate fields from the form.
423 var [usernameField, newPasswordField, oldPasswordField] =
424 this._getFormFields(form, true);
426 // Need at least 1 valid password field to do anything.
427 if (newPasswordField == null)
428 return;
430 // Check for autocomplete=off attribute. We don't use it to prevent
431 // autofilling (for existing logins), but won't save logins when it's
432 // present and the storeWhenAutocompleteOff pref is false.
433 // XXX spin out a bug that we don't update timeLastUsed in this case?
434 if ((this._isAutocompleteDisabled(form) ||
435 this._isAutocompleteDisabled(usernameField) ||
436 this._isAutocompleteDisabled(newPasswordField) ||
437 this._isAutocompleteDisabled(oldPasswordField)) &&
438 !gStoreWhenAutocompleteOff) {
439 log("(form submission ignored -- autocomplete=off found)");
440 return;
441 }
444 var formLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
445 createInstance(Ci.nsILoginInfo);
446 formLogin.init(hostname, formSubmitURL, null,
447 (usernameField ? usernameField.value : ""),
448 newPasswordField.value,
449 (usernameField ? usernameField.name : ""),
450 newPasswordField.name);
452 // If we didn't find a username field, but seem to be changing a
453 // password, allow the user to select from a list of applicable
454 // logins to update the password for.
455 if (!usernameField && oldPasswordField) {
457 var logins = Services.logins.findLogins({}, hostname, formSubmitURL, null);
459 if (logins.length == 0) {
460 // Could prompt to save this as a new password-only login.
461 // This seems uncommon, and might be wrong, so ignore.
462 log("(no logins for this host -- pwchange ignored)");
463 return;
464 }
466 var prompter = getPrompter(win);
468 if (logins.length == 1) {
469 var oldLogin = logins[0];
470 formLogin.username = oldLogin.username;
471 formLogin.usernameField = oldLogin.usernameField;
473 prompter.promptToChangePassword(oldLogin, formLogin);
474 } else {
475 prompter.promptToChangePasswordWithUsernames(
476 logins, logins.length, formLogin);
477 }
479 return;
480 }
483 // Look for an existing login that matches the form login.
484 var existingLogin = null;
485 var logins = Services.logins.findLogins({}, hostname, formSubmitURL, null);
487 for (var i = 0; i < logins.length; i++) {
488 var same, login = logins[i];
490 // If one login has a username but the other doesn't, ignore
491 // the username when comparing and only match if they have the
492 // same password. Otherwise, compare the logins and match even
493 // if the passwords differ.
494 if (!login.username && formLogin.username) {
495 var restoreMe = formLogin.username;
496 formLogin.username = "";
497 same = formLogin.matches(login, false);
498 formLogin.username = restoreMe;
499 } else if (!formLogin.username && login.username) {
500 formLogin.username = login.username;
501 same = formLogin.matches(login, false);
502 formLogin.username = ""; // we know it's always blank.
503 } else {
504 same = formLogin.matches(login, true);
505 }
507 if (same) {
508 existingLogin = login;
509 break;
510 }
511 }
513 if (existingLogin) {
514 log("Found an existing login matching this form submission");
516 // Change password if needed.
517 if (existingLogin.password != formLogin.password) {
518 log("...passwords differ, prompting to change.");
519 prompter = getPrompter(win);
520 prompter.promptToChangePassword(existingLogin, formLogin);
521 } else {
522 // Update the lastUsed timestamp.
523 var propBag = Cc["@mozilla.org/hash-property-bag;1"].
524 createInstance(Ci.nsIWritablePropertyBag);
525 propBag.setProperty("timeLastUsed", Date.now());
526 propBag.setProperty("timesUsedIncrement", 1);
527 Services.logins.modifyLogin(existingLogin, propBag);
528 }
530 return;
531 }
534 // Prompt user to save login (via dialog or notification bar)
535 prompter = getPrompter(win);
536 prompter.promptToSavePassword(formLogin);
537 },
540 /*
541 * _fillform
542 *
543 * Fill the form with login information if we can find it. This will find
544 * an array of logins if not given any, otherwise it will use the logins
545 * passed in. The logins are returned so they can be reused for
546 * optimization. Success of action is also returned in format
547 * [success, foundLogins]. autofillForm denotes if we should fill the form
548 * in automatically, ignoreAutocomplete denotes if we should ignore
549 * autocomplete=off attributes, and foundLogins is an array of nsILoginInfo
550 * for optimization
551 */
552 _fillForm : function (form, autofillForm, ignoreAutocomplete,
553 clobberPassword, foundLogins) {
554 // Heuristically determine what the user/pass fields are
555 // We do this before checking to see if logins are stored,
556 // so that the user isn't prompted for a master password
557 // without need.
558 var [usernameField, passwordField, ignored] =
559 this._getFormFields(form, false);
561 // Need a valid password field to do anything.
562 if (passwordField == null)
563 return [false, foundLogins];
565 // If the password field is disabled or read-only, there's nothing to do.
566 if (passwordField.disabled || passwordField.readOnly) {
567 log("not filling form, password field disabled or read-only");
568 return [false, foundLogins];
569 }
571 // Need to get a list of logins if we weren't given them
572 if (foundLogins == null) {
573 var formOrigin =
574 LoginUtils._getPasswordOrigin(form.ownerDocument.documentURI);
575 var actionOrigin = LoginUtils._getActionOrigin(form);
576 foundLogins = Services.logins.findLogins({}, formOrigin, actionOrigin, null);
577 log("found", foundLogins.length, "matching logins.");
578 } else {
579 log("reusing logins from last form.");
580 }
582 // Discard logins which have username/password values that don't
583 // fit into the fields (as specified by the maxlength attribute).
584 // The user couldn't enter these values anyway, and it helps
585 // with sites that have an extra PIN to be entered (bug 391514)
586 var maxUsernameLen = Number.MAX_VALUE;
587 var maxPasswordLen = Number.MAX_VALUE;
589 // If attribute wasn't set, default is -1.
590 if (usernameField && usernameField.maxLength >= 0)
591 maxUsernameLen = usernameField.maxLength;
592 if (passwordField.maxLength >= 0)
593 maxPasswordLen = passwordField.maxLength;
595 var logins = foundLogins.filter(function (l) {
596 var fit = (l.username.length <= maxUsernameLen &&
597 l.password.length <= maxPasswordLen);
598 if (!fit)
599 log("Ignored", l.username, "login: won't fit");
601 return fit;
602 }, this);
605 // Nothing to do if we have no matching logins available.
606 if (logins.length == 0)
607 return [false, foundLogins];
610 // The reason we didn't end up filling the form, if any. We include
611 // this in the formInfo object we send with the passwordmgr-found-logins
612 // notification. See the _notifyFoundLogins docs for possible values.
613 var didntFillReason = null;
615 // Attach autocomplete stuff to the username field, if we have
616 // one. This is normally used to select from multiple accounts,
617 // but even with one account we should refill if the user edits.
618 if (usernameField)
619 this._formFillService.markAsLoginManagerField(usernameField);
621 // Don't clobber an existing password.
622 if (passwordField.value && !clobberPassword) {
623 didntFillReason = "existingPassword";
624 this._notifyFoundLogins(didntFillReason, usernameField,
625 passwordField, foundLogins, null);
626 return [false, foundLogins];
627 }
629 // If the form has an autocomplete=off attribute in play, don't
630 // fill in the login automatically. We check this after attaching
631 // the autocomplete stuff to the username field, so the user can
632 // still manually select a login to be filled in.
633 var isFormDisabled = false;
634 if (!ignoreAutocomplete &&
635 (this._isAutocompleteDisabled(form) ||
636 this._isAutocompleteDisabled(usernameField) ||
637 this._isAutocompleteDisabled(passwordField))) {
639 isFormDisabled = true;
640 log("form not filled, has autocomplete=off");
641 }
643 // Variable such that we reduce code duplication and can be sure we
644 // should be firing notifications if and only if we can fill the form.
645 var selectedLogin = null;
647 if (usernameField && (usernameField.value || usernameField.disabled || usernameField.readOnly)) {
648 // If username was specified in the field, it's disabled or it's readOnly, only fill in the
649 // password if we find a matching login.
650 var username = usernameField.value.toLowerCase();
652 let matchingLogins = logins.filter(function(l)
653 l.username.toLowerCase() == username);
654 if (matchingLogins.length) {
655 selectedLogin = matchingLogins[0];
656 } else {
657 didntFillReason = "existingUsername";
658 log("Password not filled. None of the stored logins match the username already present.");
659 }
660 } else if (logins.length == 1) {
661 selectedLogin = logins[0];
662 } else {
663 // We have multiple logins. Handle a special case here, for sites
664 // which have a normal user+pass login *and* a password-only login
665 // (eg, a PIN). Prefer the login that matches the type of the form
666 // (user+pass or pass-only) when there's exactly one that matches.
667 let matchingLogins;
668 if (usernameField)
669 matchingLogins = logins.filter(function(l) l.username);
670 else
671 matchingLogins = logins.filter(function(l) !l.username);
672 if (matchingLogins.length == 1) {
673 selectedLogin = matchingLogins[0];
674 } else {
675 didntFillReason = "multipleLogins";
676 log("Multiple logins for form, so not filling any.");
677 }
678 }
680 var didFillForm = false;
681 if (selectedLogin && autofillForm && !isFormDisabled) {
682 // Fill the form
683 // Don't modify the username field if it's disabled or readOnly so we preserve its case.
684 if (usernameField && !(usernameField.disabled || usernameField.readOnly))
685 usernameField.value = selectedLogin.username;
686 passwordField.value = selectedLogin.password;
687 didFillForm = true;
688 } else if (selectedLogin && !autofillForm) {
689 // For when autofillForm is false, but we still have the information
690 // to fill a form, we notify observers.
691 didntFillReason = "noAutofillForms";
692 Services.obs.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
693 log("autofillForms=false but form can be filled; notified observers");
694 } else if (selectedLogin && isFormDisabled) {
695 // For when autocomplete is off, but we still have the information
696 // to fill a form, we notify observers.
697 didntFillReason = "autocompleteOff";
698 Services.obs.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
699 log("autocomplete=off but form can be filled; notified observers");
700 }
702 this._notifyFoundLogins(didntFillReason, usernameField, passwordField,
703 foundLogins, selectedLogin);
705 return [didFillForm, foundLogins];
706 },
709 /**
710 * Notify observers about an attempt to fill a form that resulted in some
711 * saved logins being found for the form.
712 *
713 * This does not get called if the login manager attempts to fill a form
714 * but does not find any saved logins. It does, however, get called when
715 * the login manager does find saved logins whether or not it actually
716 * fills the form with one of them.
717 *
718 * @param didntFillReason {String}
719 * the reason the login manager didn't fill the form, if any;
720 * if the value of this parameter is null, then the form was filled;
721 * otherwise, this parameter will be one of these values:
722 * existingUsername: the username field already contains a username
723 * that doesn't match any stored usernames
724 * existingPassword: the password field already contains a password
725 * autocompleteOff: autocomplete has been disabled for the form
726 * or its username or password fields
727 * multipleLogins: we have multiple logins for the form
728 * noAutofillForms: the autofillForms pref is set to false
729 *
730 * @param usernameField {HTMLInputElement}
731 * the username field detected by the login manager, if any;
732 * otherwise null
733 *
734 * @param passwordField {HTMLInputElement}
735 * the password field detected by the login manager
736 *
737 * @param foundLogins {Array}
738 * an array of nsILoginInfos that can be used to fill the form
739 *
740 * @param selectedLogin {nsILoginInfo}
741 * the nsILoginInfo that was/would be used to fill the form, if any;
742 * otherwise null; whether or not it was actually used depends on
743 * the value of the didntFillReason parameter
744 */
745 _notifyFoundLogins : function (didntFillReason, usernameField,
746 passwordField, foundLogins, selectedLogin) {
747 // We need .setProperty(), which is a method on the original
748 // nsIWritablePropertyBag. Strangley enough, nsIWritablePropertyBag2
749 // doesn't inherit from that, so the additional QI is needed.
750 let formInfo = Cc["@mozilla.org/hash-property-bag;1"].
751 createInstance(Ci.nsIWritablePropertyBag2).
752 QueryInterface(Ci.nsIWritablePropertyBag);
754 formInfo.setPropertyAsACString("didntFillReason", didntFillReason);
755 formInfo.setPropertyAsInterface("usernameField", usernameField);
756 formInfo.setPropertyAsInterface("passwordField", passwordField);
757 formInfo.setProperty("foundLogins", foundLogins.concat());
758 formInfo.setPropertyAsInterface("selectedLogin", selectedLogin);
760 Services.obs.notifyObservers(formInfo, "passwordmgr-found-logins", null);
761 },
763 };
768 LoginUtils = {
769 /*
770 * _getPasswordOrigin
771 *
772 * Get the parts of the URL we want for identification.
773 */
774 _getPasswordOrigin : function (uriString, allowJS) {
775 var realm = "";
776 try {
777 var uri = Services.io.newURI(uriString, null, null);
779 if (allowJS && uri.scheme == "javascript")
780 return "javascript:"
782 realm = uri.scheme + "://" + uri.host;
784 // If the URI explicitly specified a port, only include it when
785 // it's not the default. (We never want "http://foo.com:80")
786 var port = uri.port;
787 if (port != -1) {
788 var handler = Services.io.getProtocolHandler(uri.scheme);
789 if (port != handler.defaultPort)
790 realm += ":" + port;
791 }
793 } catch (e) {
794 // bug 159484 - disallow url types that don't support a hostPort.
795 // (although we handle "javascript:..." as a special case above.)
796 log("Couldn't parse origin for", uriString);
797 realm = null;
798 }
800 return realm;
801 },
803 _getActionOrigin : function (form) {
804 var uriString = form.action;
806 // A blank or missing action submits to where it came from.
807 if (uriString == "")
808 uriString = form.baseURI; // ala bug 297761
810 return this._getPasswordOrigin(uriString, true);
811 },
813 };