home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2010 April / PCA177.iso / ESSENTIALS / Firefox Setup.exe / nonlocalized / components / nsLoginManager.js < prev    next >
Encoding:
Text File  |  2009-07-15  |  49.8 KB  |  1,383 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is mozilla.org code.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla Corporation.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Justin Dolske <dolske@mozilla.com> (original author)
  22.  *  Ehsan Akhgari <ehsan.akhgari@gmail.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38.  
  39. const Cc = Components.classes;
  40. const Ci = Components.interfaces;
  41.  
  42. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  43.  
  44. function LoginManager() {
  45.     this.init();
  46. }
  47.  
  48. LoginManager.prototype = {
  49.  
  50.     classDescription: "LoginManager",
  51.     contractID: "@mozilla.org/login-manager;1",
  52.     classID: Components.ID("{cb9e0de8-3598-4ed7-857b-827f011ad5d8}"),
  53.     QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManager,
  54.                                             Ci.nsISupportsWeakReference,
  55.                                             Ci.nsILoginManager_MOZILLA_1_9_1,
  56.                                             Ci.nsIClassInfo]),
  57.  
  58.     /* ---------- extra requirements for nsIClassInfo ---------- */
  59.     getInterfaces: function(countRef) {
  60.         let interfaces = [Ci.nsILoginManager, Ci.nsISupportsWeakReference,
  61.                           Ci.nsILoginManager_MOZILLA_1_9_1, Ci.nsIClassInfo];
  62.         countRef.value = interfaces.length;
  63.         return interfaces;
  64.     },
  65.     getHelperForLanguage: function (language) null,
  66.     implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  67.     flags: Ci.nsIClassInfo.SINGLETON,
  68.  
  69.  
  70.     /* ---------- private memebers ---------- */
  71.  
  72.  
  73.     __logService : null, // Console logging service, used for debugging.
  74.     get _logService() {
  75.         if (!this.__logService)
  76.             this.__logService = Cc["@mozilla.org/consoleservice;1"].
  77.                                 getService(Ci.nsIConsoleService);
  78.         return this.__logService;
  79.     },
  80.  
  81.  
  82.     __ioService: null, // IO service for string -> nsIURI conversion
  83.     get _ioService() {
  84.         if (!this.__ioService)
  85.             this.__ioService = Cc["@mozilla.org/network/io-service;1"].
  86.                                getService(Ci.nsIIOService);
  87.         return this.__ioService;
  88.     },
  89.  
  90.  
  91.     __formFillService : null, // FormFillController, for username autocompleting
  92.     get _formFillService() {
  93.         if (!this.__formFillService)
  94.             this.__formFillService =
  95.                             Cc["@mozilla.org/satchel/form-fill-controller;1"].
  96.                             getService(Ci.nsIFormFillController);
  97.         return this.__formFillService;
  98.     },
  99.  
  100.  
  101.     __observerService : null, // Observer Service, for notifications
  102.     get _observerService() {
  103.         if (!this.__observerService)
  104.             this.__observerService = Cc["@mozilla.org/observer-service;1"].
  105.                                      getService(Ci.nsIObserverService);
  106.         return this.__observerService;
  107.     },
  108.  
  109.  
  110.     __storage : null, // Storage component which contains the saved logins
  111.     get _storage() {
  112.         if (!this.__storage) {
  113.  
  114.             var contractID = "@mozilla.org/login-manager/storage/mozStorage;1";
  115.             try {
  116.                 var catMan = Cc["@mozilla.org/categorymanager;1"].
  117.                              getService(Ci.nsICategoryManager);
  118.                 contractID = catMan.getCategoryEntry("login-manager-storage",
  119.                                                      "nsILoginManagerStorage");
  120.                 this.log("Found alternate nsILoginManagerStorage with " +
  121.                          "contract ID: " + contractID);
  122.             } catch (e) {
  123.                 this.log("No alternate nsILoginManagerStorage registered");
  124.             }
  125.  
  126.             this.__storage = Cc[contractID].
  127.                              createInstance(Ci.nsILoginManagerStorage);
  128.             try {
  129.                 this.__storage.init();
  130.             } catch (e) {
  131.                 this.log("Initialization of storage component failed: " + e);
  132.                 this.__storage = null;
  133.             }
  134.         }
  135.  
  136.         return this.__storage;
  137.     },
  138.  
  139.  
  140.     // Private Browsing Service
  141.     // If the service is not available, null will be returned.
  142.     __privateBrowsingService : undefined,
  143.     get _privateBrowsingService() {
  144.         if (this.__privateBrowsingService == undefined) {
  145.             if ("@mozilla.org/privatebrowsing;1" in Cc)
  146.                 this.__privateBrowsingService = Cc["@mozilla.org/privatebrowsing;1"].
  147.                                                 getService(Ci.nsIPrivateBrowsingService);
  148.             else
  149.                 this.__privateBrowsingService = null;
  150.         }
  151.         return this.__privateBrowsingService;
  152.     },
  153.  
  154.  
  155.     // Whether we are in private browsing mode
  156.     get _inPrivateBrowsing() {
  157.         var pbSvc = this._privateBrowsingService;
  158.         if (pbSvc)
  159.             return pbSvc.privateBrowsingEnabled;
  160.         else
  161.             return false;
  162.     },
  163.  
  164.     _prefBranch  : null, // Preferences service
  165.     _nsLoginInfo : null, // Constructor for nsILoginInfo implementation
  166.  
  167.     _remember : true,  // mirrors signon.rememberSignons preference
  168.     _debug    : false, // mirrors signon.debug
  169.  
  170.  
  171.     /*
  172.      * init
  173.      *
  174.      * Initialize the Login Manager. Automatically called when service
  175.      * is created.
  176.      *
  177.      * Note: Service created in /browser/base/content/browser.js,
  178.      *       delayedStartup()
  179.      */
  180.     init : function () {
  181.  
  182.         // Cache references to current |this| in utility objects
  183.         this._webProgressListener._domEventListener = this._domEventListener;
  184.         this._webProgressListener._pwmgr = this;
  185.         this._domEventListener._pwmgr    = this;
  186.         this._observer._pwmgr            = this;
  187.  
  188.         // Preferences. Add observer so we get notified of changes.
  189.         this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
  190.                            getService(Ci.nsIPrefService).getBranch("signon.");
  191.         this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  192.         this._prefBranch.addObserver("", this._observer, false);
  193.  
  194.         // Get current preference values.
  195.         this._debug = this._prefBranch.getBoolPref("debug");
  196.  
  197.         this._remember = this._prefBranch.getBoolPref("rememberSignons");
  198.  
  199.  
  200.         // Get constructor for nsILoginInfo
  201.         this._nsLoginInfo = new Components.Constructor(
  202.             "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
  203.  
  204.  
  205.         // Form submit observer checks forms for new logins and pw changes.
  206.         this._observerService.addObserver(this._observer, "earlyformsubmit", false);
  207.         this._observerService.addObserver(this._observer, "xpcom-shutdown", false);
  208.  
  209.         // WebProgressListener for getting notification of new doc loads.
  210.         var progress = Cc["@mozilla.org/docloaderservice;1"].
  211.                        getService(Ci.nsIWebProgress);
  212.         progress.addProgressListener(this._webProgressListener,
  213.                                      Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
  214.  
  215.  
  216.     },
  217.  
  218.  
  219.     /*
  220.      * log
  221.      *
  222.      * Internal function for logging debug messages to the Error Console window
  223.      */
  224.     log : function (message) {
  225.         if (!this._debug)
  226.             return;
  227.         dump("Login Manager: " + message + "\n");
  228.         this._logService.logStringMessage("Login Manager: " + message);
  229.     },
  230.  
  231.  
  232.     /* ---------- Utility objects ---------- */
  233.  
  234.  
  235.     /*
  236.      * _observer object
  237.      *
  238.      * Internal utility object, implements the nsIObserver interface.
  239.      * Used to receive notification for: form submission, preference changes.
  240.      */
  241.     _observer : {
  242.         _pwmgr : null,
  243.  
  244.         QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver, 
  245.                                                 Ci.nsIFormSubmitObserver,
  246.                                                 Ci.nsISupportsWeakReference]),
  247.  
  248.  
  249.         // nsFormSubmitObserver
  250.         notify : function (formElement, aWindow, actionURI) {
  251.             this._pwmgr.log("observer notified for form submission.");
  252.  
  253.             // We're invoked before the content's |onsubmit| handlers, so we
  254.             // can grab form data before it might be modified (see bug 257781).
  255.  
  256.             try {
  257.                 this._pwmgr._onFormSubmit(formElement);
  258.             } catch (e) {
  259.                 this._pwmgr.log("Caught error in onFormSubmit: " + e);
  260.             }
  261.  
  262.             return true; // Always return true, or form submit will be canceled.
  263.         },
  264.  
  265.         // nsObserver
  266.         observe : function (subject, topic, data) {
  267.  
  268.             if (topic == "nsPref:changed") {
  269.                 var prefName = data;
  270.                 this._pwmgr.log("got change to " + prefName + " preference");
  271.  
  272.                 if (prefName == "debug") {
  273.                     this._pwmgr._debug = 
  274.                         this._pwmgr._prefBranch.getBoolPref("debug");
  275.                 } else if (prefName == "rememberSignons") {
  276.                     this._pwmgr._remember =
  277.                         this._pwmgr._prefBranch.getBoolPref("rememberSignons");
  278.                 } else {
  279.                     this._pwmgr.log("Oops! Pref not handled, change ignored.");
  280.                 }
  281.             } else if (topic == "xpcom-shutdown") {
  282.                 for (let i in this._pwmgr) {
  283.                   try {
  284.                     this._pwmgr[i] = null;
  285.                   } catch(ex) {}
  286.                 }
  287.                 this._pwmgr = null;
  288.             } else {
  289.                 this._pwmgr.log("Oops! Unexpected notification: " + topic);
  290.             }
  291.         }
  292.     },
  293.  
  294.  
  295.     /*
  296.      * _webProgressListener object
  297.      *
  298.      * Internal utility object, implements nsIWebProgressListener interface.
  299.      * This is attached to the document loader service, so we get
  300.      * notifications about all page loads.
  301.      */
  302.     _webProgressListener : {
  303.         _pwmgr : null,
  304.         _domEventListener : null,
  305.  
  306.         QueryInterface : XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
  307.                                                 Ci.nsISupportsWeakReference]),
  308.  
  309.  
  310.         onStateChange : function (aWebProgress, aRequest,
  311.                                   aStateFlags,  aStatus) {
  312.  
  313.             // STATE_START is too early, doc is still the old page.
  314.             if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_TRANSFERRING))
  315.                 return;
  316.  
  317.             if (!this._pwmgr._remember)
  318.                 return;
  319.  
  320.             var domWin = aWebProgress.DOMWindow;
  321.             var domDoc = domWin.document;
  322.  
  323.             // Only process things which might have HTML forms.
  324.             if (!(domDoc instanceof Ci.nsIDOMHTMLDocument))
  325.                 return;
  326.  
  327.             this._pwmgr.log("onStateChange accepted: req = " +
  328.                             (aRequest ?  aRequest.name : "(null)") +
  329.                             ", flags = 0x" + aStateFlags.toString(16));
  330.  
  331.             // Fastback doesn't fire DOMContentLoaded, so process forms now.
  332.             if (aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) {
  333.                 this._pwmgr.log("onStateChange: restoring document");
  334.                 return this._pwmgr._fillDocument(domDoc);
  335.             }
  336.  
  337.             // Add event listener to process page when DOM is complete.
  338.             domDoc.addEventListener("DOMContentLoaded",
  339.                                     this._domEventListener, false);
  340.             return;
  341.         },
  342.  
  343.         // stubs for the nsIWebProgressListener interfaces which we don't use.
  344.         onProgressChange : function() { throw "Unexpected onProgressChange"; },
  345.         onLocationChange : function() { throw "Unexpected onLocationChange"; },
  346.         onStatusChange   : function() { throw "Unexpected onStatusChange";   },
  347.         onSecurityChange : function() { throw "Unexpected onSecurityChange"; }
  348.     },
  349.  
  350.  
  351.     /*
  352.      * _domEventListener object
  353.      *
  354.      * Internal utility object, implements nsIDOMEventListener
  355.      * Used to catch certain DOM events needed to properly implement form fill.
  356.      */
  357.     _domEventListener : {
  358.         _pwmgr : null,
  359.  
  360.         QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
  361.                                                 Ci.nsISupportsWeakReference]),
  362.  
  363.  
  364.         handleEvent : function (event) {
  365.             this._pwmgr.log("domEventListener: got event " + event.type);
  366.  
  367.             switch (event.type) {
  368.                 case "DOMContentLoaded":
  369.                     this._pwmgr._fillDocument(event.target);
  370.                     return;
  371.  
  372.                 case "DOMAutoComplete":
  373.                 case "blur":
  374.                     var acInputField = event.target;
  375.                     var acForm = acInputField.form;
  376.  
  377.                     // If the username is blank, bail out now -- we don't want
  378.                     // fillForm() to try filling in a login without a username
  379.                     // to filter on (bug 471906).
  380.                     if (!acInputField.value)
  381.                         return;
  382.  
  383.                     // Make sure the username field fillForm will use is the
  384.                     // same field as the autocomplete was activated on. If
  385.                     // not, the DOM has been altered and we'll just give up.
  386.                     var [usernameField, passwordField, ignored] =
  387.                         this._pwmgr._getFormFields(acForm, false);
  388.                     if (usernameField == acInputField && passwordField) {
  389.                         // Clobber any existing password.
  390.                         passwordField.value = "";
  391.                         this._pwmgr._fillForm(acForm, true, true, null);
  392.                     } else {
  393.                         this._pwmgr.log("Oops, form changed before AC invoked");
  394.                     }
  395.                     return;
  396.  
  397.                 default:
  398.                     this._pwmgr.log("Oops! This event unexpected.");
  399.                     return;
  400.             }
  401.         }
  402.     },
  403.  
  404.  
  405.  
  406.  
  407.     /* ---------- Primary Public interfaces ---------- */
  408.  
  409.  
  410.  
  411.  
  412.     /*
  413.      * addLogin
  414.      *
  415.      * Add a new login to login storage.
  416.      */
  417.     addLogin : function (login) {
  418.         // Sanity check the login
  419.         if (login.hostname == null || login.hostname.length == 0)
  420.             throw "Can't add a login with a null or empty hostname.";
  421.  
  422.         // For logins w/o a username, set to "", not null.
  423.         if (login.username == null)
  424.             throw "Can't add a login with a null username.";
  425.  
  426.         if (login.password == null || login.password.length == 0)
  427.             throw "Can't add a login with a null or empty password.";
  428.  
  429.         if (login.formSubmitURL || login.formSubmitURL == "") {
  430.             // We have a form submit URL. Can't have a HTTP realm.
  431.             if (login.httpRealm != null)
  432.                 throw "Can't add a login with both a httpRealm and formSubmitURL.";
  433.         } else if (login.httpRealm) {
  434.             // We have a HTTP realm. Can't have a form submit URL.
  435.             if (login.formSubmitURL != null)
  436.                 throw "Can't add a login with both a httpRealm and formSubmitURL.";
  437.         } else {
  438.             // Need one or the other!
  439.             throw "Can't add a login without a httpRealm or formSubmitURL.";
  440.         }
  441.  
  442.  
  443.         // Look for an existing entry.
  444.         var logins = this.findLogins({}, login.hostname, login.formSubmitURL,
  445.                                      login.httpRealm);
  446.  
  447.         if (logins.some(function(l) login.matches(l, true)))
  448.             throw "This login already exists.";
  449.  
  450.         this.log("Adding login: " + login);
  451.         return this._storage.addLogin(login);
  452.     },
  453.  
  454.  
  455.     /*
  456.      * removeLogin
  457.      *
  458.      * Remove the specified login from the stored logins.
  459.      */
  460.     removeLogin : function (login) {
  461.         this.log("Removing login: " + login);
  462.         return this._storage.removeLogin(login);
  463.     },
  464.  
  465.  
  466.     /*
  467.      * modifyLogin
  468.      *
  469.      * Change the specified login to match the new login.
  470.      */
  471.     modifyLogin : function (oldLogin, newLogin) {
  472.         this.log("Modifying oldLogin: " + oldLogin + " newLogin: " + newLogin);
  473.         return this._storage.modifyLogin(oldLogin, newLogin);
  474.     },
  475.  
  476.  
  477.     /*
  478.      * getAllLogins
  479.      *
  480.      * Get a dump of all stored logins. Used by the login manager UI.
  481.      *
  482.      * |count| is only needed for XPCOM.
  483.      *
  484.      * Returns an array of logins. If there are no logins, the array is empty.
  485.      */
  486.     getAllLogins : function (count) {
  487.         this.log("Getting a list of all logins");
  488.         return this._storage.getAllLogins(count);
  489.     },
  490.  
  491.  
  492.     /*
  493.      * removeAllLogins
  494.      *
  495.      * Remove all stored logins.
  496.      */
  497.     removeAllLogins : function () {
  498.         this.log("Removing all logins");
  499.         this._storage.removeAllLogins();
  500.     },
  501.  
  502.     /*
  503.      * getAllDisabledHosts
  504.      *
  505.      * Get a list of all hosts for which logins are disabled.
  506.      *
  507.      * |count| is only needed for XPCOM.
  508.      *
  509.      * Returns an array of disabled logins. If there are no disabled logins,
  510.      * the array is empty.
  511.      */
  512.     getAllDisabledHosts : function (count) {
  513.         this.log("Getting a list of all disabled hosts");
  514.         return this._storage.getAllDisabledHosts(count);
  515.     },
  516.  
  517.  
  518.     /*
  519.      * findLogins
  520.      *
  521.      * Search for the known logins for entries matching the specified criteria.
  522.      */
  523.     findLogins : function (count, hostname, formSubmitURL, httpRealm) {
  524.         this.log("Searching for logins matching host: " + hostname +
  525.             ", formSubmitURL: " + formSubmitURL + ", httpRealm: " + httpRealm);
  526.  
  527.         return this._storage.findLogins(count, hostname, formSubmitURL,
  528.                                         httpRealm);
  529.     },
  530.  
  531.  
  532.     /*
  533.      * searchLogins
  534.      *
  535.      * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
  536.      * JavaScript object and decrypt the results.
  537.      *
  538.      * Returns an array of decrypted nsILoginInfo.
  539.      */
  540.     searchLogins : function(count, matchData) {
  541.        this.log("Searching for logins");
  542.  
  543.         return this._storage.searchLogins(count, matchData);
  544.     },
  545.  
  546.  
  547.     /*
  548.      * countLogins
  549.      *
  550.      * Search for the known logins for entries matching the specified criteria,
  551.      * returns only the count.
  552.      */
  553.     countLogins : function (hostname, formSubmitURL, httpRealm) {
  554.         this.log("Counting logins matching host: " + hostname +
  555.             ", formSubmitURL: " + formSubmitURL + ", httpRealm: " + httpRealm);
  556.  
  557.         return this._storage.countLogins(hostname, formSubmitURL, httpRealm);
  558.     },
  559.  
  560.  
  561.     /*
  562.      * getLoginSavingEnabled
  563.      *
  564.      * Check to see if user has disabled saving logins for the host.
  565.      */
  566.     getLoginSavingEnabled : function (host) {
  567.         this.log("Checking if logins to " + host + " can be saved.");
  568.         if (!this._remember)
  569.             return false;
  570.  
  571.         return this._storage.getLoginSavingEnabled(host);
  572.     },
  573.  
  574.  
  575.     /*
  576.      * setLoginSavingEnabled
  577.      *
  578.      * Enable or disable storing logins for the specified host.
  579.      */
  580.     setLoginSavingEnabled : function (hostname, enabled) {
  581.         // Nulls won't round-trip with getAllDisabledHosts().
  582.         if (hostname.indexOf("\0") != -1)
  583.             throw "Invalid hostname";
  584.  
  585.         this.log("Saving logins for " + hostname + " enabled? " + enabled);
  586.         return this._storage.setLoginSavingEnabled(hostname, enabled);
  587.     },
  588.  
  589.  
  590.     /*
  591.      * autoCompleteSearch
  592.      *
  593.      * Yuck. This is called directly by satchel:
  594.      * nsFormFillController::StartSearch()
  595.      * [toolkit/components/satchel/src/nsFormFillController.cpp]
  596.      *
  597.      * We really ought to have a simple way for code to register an
  598.      * auto-complete provider, and not have satchel calling pwmgr directly.
  599.      */
  600.     autoCompleteSearch : function (aSearchString, aPreviousResult, aElement) {
  601.         // aPreviousResult & aResult are nsIAutoCompleteResult,
  602.         // aElement is nsIDOMHTMLInputElement
  603.  
  604.         if (!this._remember)
  605.             return false;
  606.  
  607.         this.log("AutoCompleteSearch invoked. Search is: " + aSearchString);
  608.  
  609.         var result = null;
  610.  
  611.         if (aPreviousResult) {
  612.             this.log("Using previous autocomplete result");
  613.             result = aPreviousResult;
  614.  
  615.             // We have a list of results for a shorter search string, so just
  616.             // filter them further based on the new search string.
  617.             // Count backwards, because result.matchCount is decremented
  618.             // when we remove an entry.
  619.             for (var i = result.matchCount - 1; i >= 0; i--) {
  620.                 var match = result.getValueAt(i);
  621.  
  622.                 // Remove results that are too short, or have different prefix.
  623.                 if (aSearchString.length > match.length ||
  624.                     aSearchString.toLowerCase() !=
  625.                         match.substr(0, aSearchString.length).toLowerCase())
  626.                 {
  627.                     this.log("Removing autocomplete entry '" + match + "'");
  628.                     result.removeValueAt(i, false);
  629.                 }
  630.             }
  631.         } else {
  632.             this.log("Creating new autocomplete search result.");
  633.  
  634.             var doc = aElement.ownerDocument;
  635.             var origin = this._getPasswordOrigin(doc.documentURI);
  636.             var actionOrigin = this._getActionOrigin(aElement.form);
  637.  
  638.             var logins = this.findLogins({}, origin, actionOrigin, null);
  639.             var matchingLogins = [];
  640.  
  641.             // Filter out logins that don't match the search prefix. Also
  642.             // filter logins without a username, since that's confusing to see
  643.             // in the dropdown and we can't autocomplete them anyway.
  644.             for (i = 0; i < logins.length; i++) {
  645.                 var username = logins[i].username.toLowerCase();
  646.                 if (username &&
  647.                     aSearchString.length <= username.length &&
  648.                     aSearchString.toLowerCase() ==
  649.                         username.substr(0, aSearchString.length))
  650.                 {
  651.                     matchingLogins.push(logins[i]);
  652.                 }
  653.             }
  654.             this.log(matchingLogins.length + " autocomplete logins avail.");
  655.             result = new UserAutoCompleteResult(aSearchString, matchingLogins);
  656.         }
  657.  
  658.         return result;
  659.     },
  660.  
  661.  
  662.  
  663.  
  664.     /* ------- Internal methods / callbacks for document integration ------- */
  665.  
  666.  
  667.  
  668.  
  669.     /*
  670.      * _getPasswordFields
  671.      *
  672.      * Returns an array of password field elements for the specified form.
  673.      * If no pw fields are found, or if more than 3 are found, then null
  674.      * is returned.
  675.      *
  676.      * skipEmptyFields can be set to ignore password fields with no value.
  677.      */
  678.     _getPasswordFields : function (form, skipEmptyFields) {
  679.         // Locate the password fields in the form.
  680.         var pwFields = [];
  681.         for (var i = 0; i < form.elements.length; i++) {
  682.             var element = form.elements[i];
  683.             if (!(element instanceof Ci.nsIDOMHTMLInputElement) ||
  684.                 element.type != "password")
  685.                 continue;
  686.  
  687.             if (skipEmptyFields && !element.value)
  688.                 continue;
  689.  
  690.             pwFields[pwFields.length] = {
  691.                                             index   : i,
  692.                                             element : element
  693.                                         };
  694.         }
  695.  
  696.         // If too few or too many fields, bail out.
  697.         if (pwFields.length == 0) {
  698.             this.log("(form ignored -- no password fields.)");
  699.             return null;
  700.         } else if (pwFields.length > 3) {
  701.             this.log("(form ignored -- too many password fields. [got " +
  702.                         pwFields.length + "])");
  703.             return null;
  704.         }
  705.  
  706.         return pwFields;
  707.     },
  708.  
  709.  
  710.     /*
  711.      * _getFormFields
  712.      *
  713.      * Returns the username and password fields found in the form.
  714.      * Can handle complex forms by trying to figure out what the
  715.      * relevant fields are.
  716.      *
  717.      * Returns: [usernameField, newPasswordField, oldPasswordField]
  718.      *
  719.      * usernameField may be null.
  720.      * newPasswordField will always be non-null.
  721.      * oldPasswordField may be null. If null, newPasswordField is just
  722.      * "theLoginField". If not null, the form is apparently a
  723.      * change-password field, with oldPasswordField containing the password
  724.      * that is being changed.
  725.      */
  726.     _getFormFields : function (form, isSubmission) {
  727.         var usernameField = null;
  728.  
  729.         // Locate the password field(s) in the form. Up to 3 supported.
  730.         // If there's no password field, there's nothing for us to do.
  731.         var pwFields = this._getPasswordFields(form, isSubmission);
  732.         if (!pwFields)
  733.             return [null, null, null];
  734.  
  735.  
  736.         // Locate the username field in the form by searching backwards
  737.         // from the first passwordfield, assume the first text field is the
  738.         // username. We might not find a username field if the user is
  739.         // already logged in to the site. 
  740.         for (var i = pwFields[0].index - 1; i >= 0; i--) {
  741.             if (form.elements[i].type == "text") {
  742.                 usernameField = form.elements[i];
  743.                 break;
  744.             }
  745.         }
  746.  
  747.         if (!usernameField)
  748.             this.log("(form -- no username field found)");
  749.  
  750.  
  751.         // If we're not submitting a form (it's a page load), there are no
  752.         // password field values for us to use for identifying fields. So,
  753.         // just assume the first password field is the one to be filled in.
  754.         if (!isSubmission || pwFields.length == 1)
  755.             return [usernameField, pwFields[0].element, null];
  756.  
  757.  
  758.         // Try to figure out WTF is in the form based on the password values.
  759.         var oldPasswordField, newPasswordField;
  760.         var pw1 = pwFields[0].element.value;
  761.         var pw2 = pwFields[1].element.value;
  762.         var pw3 = (pwFields[2] ? pwFields[2].element.value : null);
  763.  
  764.         if (pwFields.length == 3) {
  765.             // Look for two identical passwords, that's the new password
  766.  
  767.             if (pw1 == pw2 && pw2 == pw3) {
  768.                 // All 3 passwords the same? Weird! Treat as if 1 pw field.
  769.                 newPasswordField = pwFields[0].element;
  770.                 oldPasswordField = null;
  771.             } else if (pw1 == pw2) {
  772.                 newPasswordField = pwFields[0].element;
  773.                 oldPasswordField = pwFields[2].element;
  774.             } else if (pw2 == pw3) {
  775.                 oldPasswordField = pwFields[0].element;
  776.                 newPasswordField = pwFields[2].element;
  777.             } else  if (pw1 == pw3) {
  778.                 // A bit odd, but could make sense with the right page layout.
  779.                 newPasswordField = pwFields[0].element;
  780.                 oldPasswordField = pwFields[1].element;
  781.             } else {
  782.                 // We can't tell which of the 3 passwords should be saved.
  783.                 this.log("(form ignored -- all 3 pw fields differ)");
  784.                 return [null, null, null];
  785.             }
  786.         } else { // pwFields.length == 2
  787.             if (pw1 == pw2) {
  788.                 // Treat as if 1 pw field
  789.                 newPasswordField = pwFields[0].element;
  790.                 oldPasswordField = null;
  791.             } else {
  792.                 // Just assume that the 2nd password is the new password
  793.                 oldPasswordField = pwFields[0].element;
  794.                 newPasswordField = pwFields[1].element;
  795.             }
  796.         }
  797.  
  798.         return [usernameField, newPasswordField, oldPasswordField];
  799.     },
  800.  
  801.  
  802.     /*
  803.      * _isAutoCompleteDisabled
  804.      *
  805.      * Returns true if the page requests autocomplete be disabled for the
  806.      * specified form input.
  807.      */
  808.     _isAutocompleteDisabled :  function (element) {
  809.         if (element && element.hasAttribute("autocomplete") &&
  810.             element.getAttribute("autocomplete").toLowerCase() == "off")
  811.             return true;
  812.  
  813.         return false;
  814.     },
  815.  
  816.     /*
  817.      * _onFormSubmit
  818.      *
  819.      * Called by the our observer when notified of a form submission.
  820.      * [Note that this happens before any DOM onsubmit handlers are invoked.]
  821.      * Looks for a password change in the submitted form, so we can update
  822.      * our stored password.
  823.      */
  824.     _onFormSubmit : function (form) {
  825.  
  826.         // local helper function
  827.         function getPrompter(aWindow) {
  828.             var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
  829.                               createInstance(Ci.nsILoginManagerPrompter);
  830.             prompterSvc.init(aWindow);
  831.             return prompterSvc;
  832.         }
  833.  
  834.         if (this._inPrivateBrowsing) {
  835.             // We won't do anything in private browsing mode anyway,
  836.             // so there's no need to perform further checks.
  837.             this.log("(form submission ignored in private browsing mode)");
  838.             return;
  839.         }
  840.  
  841.         var doc = form.ownerDocument;
  842.         var win = doc.defaultView;
  843.  
  844.         // If password saving is disabled (globally or for host), bail out now.
  845.         if (!this._remember)
  846.             return;
  847.  
  848.         var hostname      = this._getPasswordOrigin(doc.documentURI);
  849.         var formSubmitURL = this._getActionOrigin(form)
  850.         if (!this.getLoginSavingEnabled(hostname)) {
  851.             this.log("(form submission ignored -- saving is " +
  852.                      "disabled for: " + hostname + ")");
  853.             return;
  854.         }
  855.  
  856.  
  857.         // Get the appropriate fields from the form.
  858.         var [usernameField, newPasswordField, oldPasswordField] =
  859.             this._getFormFields(form, true);
  860.  
  861.         // Need at least 1 valid password field to do anything.
  862.         if (newPasswordField == null)
  863.                 return;
  864.  
  865.         // Check for autocomplete=off attribute. We don't use it to prevent
  866.         // autofilling (for existing logins), but won't save logins when it's
  867.         // present.
  868.         if (this._isAutocompleteDisabled(form) ||
  869.             this._isAutocompleteDisabled(usernameField) ||
  870.             this._isAutocompleteDisabled(newPasswordField) ||
  871.             this._isAutocompleteDisabled(oldPasswordField)) {
  872.                 this.log("(form submission ignored -- autocomplete=off found)");
  873.                 return;
  874.         }
  875.  
  876.  
  877.         var formLogin = new this._nsLoginInfo();
  878.         formLogin.init(hostname, formSubmitURL, null,
  879.                     (usernameField ? usernameField.value : ""),
  880.                     newPasswordField.value,
  881.                     (usernameField ? usernameField.name  : ""),
  882.                     newPasswordField.name);
  883.  
  884.         // If we didn't find a username field, but seem to be changing a
  885.         // password, allow the user to select from a list of applicable
  886.         // logins to update the password for.
  887.         if (!usernameField && oldPasswordField) {
  888.  
  889.             var logins = this.findLogins({}, hostname, formSubmitURL, null);
  890.  
  891.             if (logins.length == 0) {
  892.                 // Could prompt to save this as a new password-only login.
  893.                 // This seems uncommon, and might be wrong, so ignore.
  894.                 this.log("(no logins for this host -- pwchange ignored)");
  895.                 return;
  896.             }
  897.  
  898.             var prompter = getPrompter(win);
  899.  
  900.             if (logins.length == 1) {
  901.                 var oldLogin = logins[0];
  902.                 formLogin.username      = oldLogin.username;
  903.                 formLogin.usernameField = oldLogin.usernameField;
  904.  
  905.                 prompter.promptToChangePassword(oldLogin, formLogin);
  906.             } else {
  907.                 prompter.promptToChangePasswordWithUsernames(
  908.                                     logins, logins.length, formLogin);
  909.             }
  910.  
  911.             return;
  912.         }
  913.  
  914.  
  915.         // Look for an existing login that matches the form login.
  916.         var existingLogin = null;
  917.         var logins = this.findLogins({}, hostname, formSubmitURL, null);
  918.  
  919.         for (var i = 0; i < logins.length; i++) {
  920.             var same, login = logins[i];
  921.  
  922.             // If one login has a username but the other doesn't, ignore
  923.             // the username when comparing and only match if they have the
  924.             // same password. Otherwise, compare the logins and match even
  925.             // if the passwords differ.
  926.             if (!login.username && formLogin.username) {
  927.                 var restoreMe = formLogin.username;
  928.                 formLogin.username = ""; 
  929.                 same = formLogin.matches(login, false);
  930.                 formLogin.username = restoreMe;
  931.             } else if (!formLogin.username && login.username) {
  932.                 formLogin.username = login.username;
  933.                 same = formLogin.matches(login, false);
  934.                 formLogin.username = ""; // we know it's always blank.
  935.             } else {
  936.                 same = formLogin.matches(login, true);
  937.             }
  938.  
  939.             if (same) {
  940.                 existingLogin = login;
  941.                 break;
  942.             }
  943.         }
  944.  
  945.         if (existingLogin) {
  946.             this.log("Found an existing login matching this form submission");
  947.  
  948.             // Change password if needed.
  949.             if (existingLogin.password != formLogin.password) {
  950.                 this.log("...passwords differ, prompting to change.");
  951.                 prompter = getPrompter(win);
  952.                 prompter.promptToChangePassword(existingLogin, formLogin);
  953.             }
  954.  
  955.             return;
  956.         }
  957.  
  958.  
  959.         // Prompt user to save login (via dialog or notification bar)
  960.         prompter = getPrompter(win);
  961.         prompter.promptToSavePassword(formLogin);
  962.     },
  963.  
  964.  
  965.     /*
  966.      * _getPasswordOrigin
  967.      *
  968.      * Get the parts of the URL we want for identification.
  969.      */
  970.     _getPasswordOrigin : function (uriString, allowJS) {
  971.         var realm = "";
  972.         try {
  973.             var uri = this._ioService.newURI(uriString, null, null);
  974.  
  975.             if (allowJS && uri.scheme == "javascript")
  976.                 return "javascript:"
  977.  
  978.             realm = uri.scheme + "://" + uri.host;
  979.  
  980.             // If the URI explicitly specified a port, only include it when
  981.             // it's not the default. (We never want "http://foo.com:80")
  982.             var port = uri.port;
  983.             if (port != -1) {
  984.                 var handler = this._ioService.getProtocolHandler(uri.scheme);
  985.                 if (port != handler.defaultPort)
  986.                     realm += ":" + port;
  987.             }
  988.  
  989.         } catch (e) {
  990.             // bug 159484 - disallow url types that don't support a hostPort.
  991.             // (although we handle "javascript:..." as a special case above.)
  992.             this.log("Couldn't parse origin for " + uriString);
  993.             realm = null;
  994.         }
  995.  
  996.         return realm;
  997.     },
  998.  
  999.     _getActionOrigin : function (form) {
  1000.         var uriString = form.action;
  1001.  
  1002.         // A blank or mission action submits to where it came from.
  1003.         if (uriString == "")
  1004.             uriString = form.baseURI; // ala bug 297761
  1005.  
  1006.         return this._getPasswordOrigin(uriString, true);
  1007.     },
  1008.  
  1009.  
  1010.     /*
  1011.      * _fillDocument
  1012.      *
  1013.      * Called when a page has loaded. For each form in the document,
  1014.      * we check to see if it can be filled with a stored login.
  1015.      */
  1016.     _fillDocument : function (doc) {
  1017.         var forms = doc.forms;
  1018.         if (!forms || forms.length == 0)
  1019.             return;
  1020.  
  1021.         var formOrigin = this._getPasswordOrigin(doc.documentURI);
  1022.  
  1023.         // If there are no logins for this site, bail out now.
  1024.         if (!this.countLogins(formOrigin, "", null))
  1025.             return;
  1026.  
  1027.         this.log("fillDocument processing " + forms.length +
  1028.                  " forms on " + doc.documentURI);
  1029.  
  1030.         var autofillForm = !this._inPrivateBrowsing &&
  1031.                            this._prefBranch.getBoolPref("autofillForms");
  1032.         var previousActionOrigin = null;
  1033.         var foundLogins = null;
  1034.  
  1035.         for (var i = 0; i < forms.length; i++) {
  1036.             var form = forms[i];
  1037.  
  1038.             // Only the actionOrigin might be changing, so if it's the same
  1039.             // as the last form on the page we can reuse the same logins.
  1040.             var actionOrigin = this._getActionOrigin(form);
  1041.             if (actionOrigin != previousActionOrigin) {
  1042.                 foundLogins = null;
  1043.                 previousActionOrigin = actionOrigin;
  1044.             }
  1045.             this.log("_fillDocument processing form[" + i + "]");
  1046.             foundLogins = this._fillForm(form, autofillForm, false, foundLogins)[1];
  1047.         } // foreach form
  1048.     },
  1049.  
  1050.  
  1051.     /*
  1052.      * _fillform
  1053.      *
  1054.      * Fill the form with login information if we can find it. This will find
  1055.      * an array of logins if not given any, otherwise it will use the logins
  1056.      * passed in. The logins are returned so they can be reused for
  1057.      * optimization. Success of action is also returned in format
  1058.      * [success, foundLogins]. autofillForm denotes if we should fill the form
  1059.      * in automatically, ignoreAutocomplete denotes if we should ignore
  1060.      * autocomplete=off attributes, and foundLogins is an array of nsILoginInfo
  1061.      * for optimization
  1062.      */
  1063.     _fillForm : function (form, autofillForm, ignoreAutocomplete, foundLogins) {
  1064.         // Heuristically determine what the user/pass fields are
  1065.         // We do this before checking to see if logins are stored,
  1066.         // so that the user isn't prompted for a master password
  1067.         // without need.
  1068.         var [usernameField, passwordField, ignored] =
  1069.             this._getFormFields(form, false);
  1070.  
  1071.         // Need a valid password field to do anything.
  1072.         if (passwordField == null)
  1073.             return [false, foundLogins];
  1074.  
  1075.         // If the fields are disabled or read-only, there's nothing to do.
  1076.         if (passwordField.disabled || passwordField.readOnly ||
  1077.             usernameField && (usernameField.disabled ||
  1078.                               usernameField.readOnly)) {
  1079.             this.log("not filling form, login fields disabled");
  1080.             return [false, foundLogins];
  1081.         }
  1082.  
  1083.         // Need to get a list of logins if we weren't given them
  1084.         if (foundLogins == null) {
  1085.             var formOrigin = 
  1086.                 this._getPasswordOrigin(form.ownerDocument.documentURI);
  1087.             var actionOrigin = this._getActionOrigin(form);
  1088.             foundLogins = this.findLogins({}, formOrigin, actionOrigin, null);
  1089.             this.log("found " + foundLogins.length + " matching logins.");
  1090.         } else {
  1091.             this.log("reusing logins from last form.");
  1092.         }
  1093.  
  1094.         // Discard logins which have username/password values that don't
  1095.         // fit into the fields (as specified by the maxlength attribute).
  1096.         // The user couldn't enter these values anyway, and it helps
  1097.         // with sites that have an extra PIN to be entered (bug 391514)
  1098.         var maxUsernameLen = Number.MAX_VALUE;
  1099.         var maxPasswordLen = Number.MAX_VALUE;
  1100.  
  1101.         // If attribute wasn't set, default is -1.
  1102.         if (usernameField && usernameField.maxLength >= 0)
  1103.             maxUsernameLen = usernameField.maxLength;
  1104.         if (passwordField.maxLength >= 0)
  1105.             maxPasswordLen = passwordField.maxLength;
  1106.  
  1107.         var logins = foundLogins.filter(function (l) {
  1108.                 var fit = (l.username.length <= maxUsernameLen &&
  1109.                            l.password.length <= maxPasswordLen);
  1110.                 if (!fit)
  1111.                     this.log("Ignored " + l.username + " login: won't fit");
  1112.  
  1113.                 return fit;
  1114.             }, this);
  1115.  
  1116.  
  1117.         // Nothing to do if we have no matching logins available.
  1118.         if (logins.length == 0)
  1119.             return [false, foundLogins];
  1120.  
  1121.  
  1122.         // The reason we didn't end up filling the form, if any.  We include
  1123.         // this in the formInfo object we send with the passwordmgr-found-logins
  1124.         // notification.  See the _notifyFoundLogins docs for possible values.
  1125.         var didntFillReason = null;
  1126.  
  1127.         // Attach autocomplete stuff to the username field, if we have
  1128.         // one. This is normally used to select from multiple accounts,
  1129.         // but even with one account we should refill if the user edits.
  1130.         if (usernameField)
  1131.             this._attachToInput(usernameField);
  1132.  
  1133.         // Don't clobber an existing password.
  1134.         if (passwordField.value) {
  1135.             didntFillReason = "existingPassword";
  1136.             this._notifyFoundLogins(didntFillReason, usernameField,
  1137.                                     passwordField, foundLogins, null);
  1138.             return [false, foundLogins];
  1139.         }
  1140.  
  1141.         // If the form has an autocomplete=off attribute in play, don't
  1142.         // fill in the login automatically. We check this after attaching
  1143.         // the autocomplete stuff to the username field, so the user can
  1144.         // still manually select a login to be filled in.
  1145.         var isFormDisabled = false;
  1146.         if (!ignoreAutocomplete &&
  1147.             (this._isAutocompleteDisabled(form) ||
  1148.              this._isAutocompleteDisabled(usernameField) ||
  1149.              this._isAutocompleteDisabled(passwordField))) {
  1150.  
  1151.             isFormDisabled = true;
  1152.             this.log("form not filled, has autocomplete=off");
  1153.         }
  1154.  
  1155.         // Variable such that we reduce code duplication and can be sure we
  1156.         // should be firing notifications if and only if we can fill the form.
  1157.         var selectedLogin = null;
  1158.  
  1159.         if (usernameField && usernameField.value) {
  1160.             // If username was specified in the form, only fill in the
  1161.             // password if we find a matching login.
  1162.             var username = usernameField.value.toLowerCase();
  1163.  
  1164.             let matchingLogins = logins.filter(function(l)
  1165.                                      l.username.toLowerCase() == username);
  1166.             if (matchingLogins.length) {
  1167.                 selectedLogin = matchingLogins[0];
  1168.             } else {
  1169.                 didntFillReason = "existingUsername";
  1170.                 this.log("Password not filled. None of the stored " +
  1171.                          "logins match the username already present.");
  1172.             }
  1173.         } else if (logins.length == 1) {
  1174.             selectedLogin = logins[0];
  1175.         } else {
  1176.             // We have multiple logins. Handle a special case here, for sites
  1177.             // which have a normal user+pass login *and* a password-only login
  1178.             // (eg, a PIN). Prefer the login that matches the type of the form
  1179.             // (user+pass or pass-only) when there's exactly one that matches.
  1180.             let matchingLogins;
  1181.             if (usernameField)
  1182.                 matchingLogins = logins.filter(function(l) l.username);
  1183.             else
  1184.                 matchingLogins = logins.filter(function(l) !l.username);
  1185.             if (matchingLogins.length == 1) {
  1186.                 selectedLogin = matchingLogins[0];
  1187.             } else {
  1188.                 didntFillReason = "multipleLogins";
  1189.                 this.log("Multiple logins for form, so not filling any.");
  1190.             }
  1191.         }
  1192.  
  1193.         var didFillForm = false;
  1194.         if (selectedLogin && autofillForm && !isFormDisabled) {
  1195.             // Fill the form
  1196.             if (usernameField)
  1197.                 usernameField.value = selectedLogin.username;
  1198.             passwordField.value = selectedLogin.password;
  1199.             didFillForm = true;
  1200.         } else if (selectedLogin && !autofillForm) {
  1201.             // For when autofillForm is false, but we still have the information
  1202.             // to fill a form, we notify observers.
  1203.             didntFillReason = "noAutofillForms";
  1204.             this._observerService.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
  1205.             this.log("autofillForms=false but form can be filled; notified observers");
  1206.         } else if (selectedLogin && isFormDisabled) {
  1207.             // For when autocomplete is off, but we still have the information
  1208.             // to fill a form, we notify observers.
  1209.             didntFillReason = "autocompleteOff";
  1210.             this._observerService.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
  1211.             this.log("autocomplete=off but form can be filled; notified observers");
  1212.         }
  1213.  
  1214.         this._notifyFoundLogins(didntFillReason, usernameField, passwordField,
  1215.                                 foundLogins, selectedLogin);
  1216.  
  1217.         return [didFillForm, foundLogins];
  1218.     },
  1219.  
  1220.     /**
  1221.      * Notify observers about an attempt to fill a form that resulted in some
  1222.      * saved logins being found for the form.
  1223.      *
  1224.      * This does not get called if the login manager attempts to fill a form
  1225.      * but does not find any saved logins.  It does, however, get called when
  1226.      * the login manager does find saved logins whether or not it actually
  1227.      * fills the form with one of them.
  1228.      *
  1229.      * @param didntFillReason {String}
  1230.      *        the reason the login manager didn't fill the form, if any;
  1231.      *        if the value of this parameter is null, then the form was filled;
  1232.      *        otherwise, this parameter will be one of these values:
  1233.      *          existingUsername: the username field already contains a username
  1234.      *                            that doesn't match any stored usernames
  1235.      *          existingPassword: the password field already contains a password
  1236.      *          autocompleteOff:  autocomplete has been disabled for the form
  1237.      *                            or its username or password fields
  1238.      *          multipleLogins:   we have multiple logins for the form
  1239.      *          noAutofillForms:  the autofillForms pref is set to false
  1240.      *
  1241.      * @param usernameField   {HTMLInputElement}
  1242.      *        the username field detected by the login manager, if any;
  1243.      *        otherwise null
  1244.      *
  1245.      * @param passwordField   {HTMLInputElement}
  1246.      *        the password field detected by the login manager
  1247.      *
  1248.      * @param foundLogins     {Array}
  1249.      *        an array of nsILoginInfos that can be used to fill the form
  1250.      *
  1251.      * @param selectedLogin   {nsILoginInfo}
  1252.      *        the nsILoginInfo that was/would be used to fill the form, if any;
  1253.      *        otherwise null; whether or not it was actually used depends on
  1254.      *        the value of the didntFillReason parameter
  1255.      */
  1256.     _notifyFoundLogins : function (didntFillReason, usernameField,
  1257.                                    passwordField, foundLogins, selectedLogin) {
  1258.         let formInfo = Cc["@mozilla.org/hash-property-bag;1"].
  1259.                        createInstance(Ci.nsIWritablePropertyBag2);
  1260.  
  1261.         formInfo.setPropertyAsACString("didntFillReason", didntFillReason);
  1262.         formInfo.setPropertyAsInterface("usernameField", usernameField);
  1263.         formInfo.setPropertyAsInterface("passwordField", passwordField);
  1264.         formInfo.setPropertyAsInterface("foundLogins", foundLogins.concat());
  1265.         formInfo.setPropertyAsInterface("selectedLogin", selectedLogin);
  1266.  
  1267.         this._observerService.notifyObservers(formInfo,
  1268.                                               "passwordmgr-found-logins",
  1269.                                               null);
  1270.     },
  1271.  
  1272.     /*
  1273.      * fillForm
  1274.      *
  1275.      * Fill the form with login information if we can find it.
  1276.      */
  1277.     fillForm : function (form) {
  1278.         this.log("fillForm processing form[id=" + form.id + "]");
  1279.         return this._fillForm(form, true, true, null)[0];
  1280.     },
  1281.  
  1282.  
  1283.     /*
  1284.      * _attachToInput
  1285.      *
  1286.      * Hooks up autocomplete support to a username field, to allow
  1287.      * a user editing the field to select an existing login and have
  1288.      * the password field filled in.
  1289.      */
  1290.     _attachToInput : function (element) {
  1291.         this.log("attaching autocomplete stuff");
  1292.         element.addEventListener("blur",
  1293.                                 this._domEventListener, false);
  1294.         element.addEventListener("DOMAutoComplete",
  1295.                                 this._domEventListener, false);
  1296.         this._formFillService.markAsLoginManagerField(element);
  1297.     }
  1298. }; // end of LoginManager implementation
  1299.  
  1300.  
  1301.  
  1302.  
  1303. // nsIAutoCompleteResult implementation
  1304. function UserAutoCompleteResult (aSearchString, matchingLogins) {
  1305.     function loginSort(a,b) {
  1306.         var userA = a.username.toLowerCase();
  1307.         var userB = b.username.toLowerCase();
  1308.  
  1309.         if (userA < userB)
  1310.             return -1;
  1311.  
  1312.         if (userB > userA)
  1313.             return  1;
  1314.  
  1315.         return 0;
  1316.     };
  1317.  
  1318.     this.searchString = aSearchString;
  1319.     this.logins = matchingLogins.sort(loginSort);
  1320.     this.matchCount = matchingLogins.length;
  1321.  
  1322.     if (this.matchCount > 0) {
  1323.         this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
  1324.         this.defaultIndex = 0;
  1325.     }
  1326. }
  1327.  
  1328. UserAutoCompleteResult.prototype = {
  1329.     QueryInterface : XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult,
  1330.                                             Ci.nsISupportsWeakReference]),
  1331.  
  1332.     // private
  1333.     logins : null,
  1334.  
  1335.     // Interfaces from idl...
  1336.     searchString : null,
  1337.     searchResult : Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
  1338.     defaultIndex : -1,
  1339.     errorDescription : "",
  1340.     matchCount : 0,
  1341.  
  1342.     getValueAt : function (index) {
  1343.         if (index < 0 || index >= this.logins.length)
  1344.             throw "Index out of range.";
  1345.  
  1346.         return this.logins[index].username;
  1347.     },
  1348.  
  1349.     getCommentAt : function (index) {
  1350.         return "";
  1351.     },
  1352.  
  1353.     getStyleAt : function (index) {
  1354.         return "";
  1355.     },
  1356.  
  1357.     getImageAt : function (index) {
  1358.         return "";
  1359.     },
  1360.  
  1361.     removeValueAt : function (index, removeFromDB) {
  1362.         if (index < 0 || index >= this.logins.length)
  1363.             throw "Index out of range.";
  1364.  
  1365.         var [removedLogin] = this.logins.splice(index, 1);
  1366.  
  1367.         this.matchCount--;
  1368.         if (this.defaultIndex > this.logins.length)
  1369.             this.defaultIndex--;
  1370.  
  1371.         if (removeFromDB) {
  1372.             var pwmgr = Cc["@mozilla.org/login-manager;1"].
  1373.                         getService(Ci.nsILoginManager);
  1374.             pwmgr.removeLogin(removedLogin);
  1375.         }
  1376.     }
  1377. };
  1378.  
  1379. var component = [LoginManager];
  1380. function NSGetModule (compMgr, fileSpec) {
  1381.     return XPCOMUtils.generateModule(component);
  1382. }
  1383.