Home | History | Annotate | Download | only in options
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 cr.define('options', function() {
      6   /** @const */ var OptionsPage = options.OptionsPage;
      7   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
      8 
      9   /////////////////////////////////////////////////////////////////////////////
     10   // PasswordManager class:
     11 
     12   /**
     13    * Encapsulated handling of password and exceptions page.
     14    * @constructor
     15    */
     16   function PasswordManager() {
     17     this.activeNavTab = null;
     18     OptionsPage.call(this,
     19                      'passwords',
     20                      loadTimeData.getString('passwordsPageTabTitle'),
     21                      'password-manager');
     22   }
     23 
     24   cr.addSingletonGetter(PasswordManager);
     25 
     26   PasswordManager.prototype = {
     27     __proto__: OptionsPage.prototype,
     28 
     29     /**
     30      * The saved passwords list.
     31      * @type {DeletableItemList}
     32      * @private
     33      */
     34     savedPasswordsList_: null,
     35 
     36     /**
     37      * The password exceptions list.
     38      * @type {DeletableItemList}
     39      * @private
     40      */
     41     passwordExceptionsList_: null,
     42 
     43     /**
     44      * The timer id of the timer set on search query change events.
     45      * @type {number}
     46      * @private
     47      */
     48     queryDelayTimerId_: 0,
     49 
     50     /**
     51      * The most recent search query, or null if the query is empty.
     52      * @type {?string}
     53      * @private
     54      */
     55     lastQuery_: null,
     56 
     57     /** @override */
     58     initializePage: function() {
     59       OptionsPage.prototype.initializePage.call(this);
     60 
     61       $('password-manager-confirm').onclick = function() {
     62         OptionsPage.closeOverlay();
     63       };
     64 
     65       $('password-search-box').addEventListener('search',
     66           this.handleSearchQueryChange_.bind(this));
     67 
     68       this.createSavedPasswordsList_();
     69       this.createPasswordExceptionsList_();
     70     },
     71 
     72     /** @override */
     73     canShowPage: function() {
     74       return !(cr.isChromeOS && UIAccountTweaks.loggedInAsGuest());
     75     },
     76 
     77     /** @override */
     78     didShowPage: function() {
     79       // Updating the password lists may cause a blocking platform dialog pop up
     80       // (Mac, Linux), so we delay this operation until the page is shown.
     81       chrome.send('updatePasswordLists');
     82       $('password-search-box').focus();
     83     },
     84 
     85     /**
     86      * Creates, decorates and initializes the saved passwords list.
     87      * @private
     88      */
     89     createSavedPasswordsList_: function() {
     90       this.savedPasswordsList_ = $('saved-passwords-list');
     91       options.passwordManager.PasswordsList.decorate(this.savedPasswordsList_);
     92       this.savedPasswordsList_.autoExpands = true;
     93     },
     94 
     95     /**
     96      * Creates, decorates and initializes the password exceptions list.
     97      * @private
     98      */
     99     createPasswordExceptionsList_: function() {
    100       this.passwordExceptionsList_ = $('password-exceptions-list');
    101       options.passwordManager.PasswordExceptionsList.decorate(
    102           this.passwordExceptionsList_);
    103       this.passwordExceptionsList_.autoExpands = true;
    104     },
    105 
    106     /**
    107      * Handles search query changes.
    108      * @param {!Event} e The event object.
    109      * @private
    110      */
    111     handleSearchQueryChange_: function(e) {
    112       if (this.queryDelayTimerId_)
    113         window.clearTimeout(this.queryDelayTimerId_);
    114 
    115       // Searching cookies uses a timeout of 500ms. We use a shorter timeout
    116       // because there are probably fewer passwords and we want the UI to be
    117       // snappier since users will expect that it's "less work."
    118       this.queryDelayTimerId_ = window.setTimeout(
    119           this.searchPasswords_.bind(this), 250);
    120     },
    121 
    122     /**
    123      * Search passwords using text in |password-search-box|.
    124      * @private
    125      */
    126     searchPasswords_: function() {
    127       this.queryDelayTimerId_ = 0;
    128       var filter = $('password-search-box').value;
    129       filter = (filter == '') ? null : filter;
    130       if (this.lastQuery_ != filter) {
    131         this.lastQuery_ = filter;
    132         // Searching for passwords has the side effect of requerying the
    133         // underlying password store. This is done intentionally, as on OS X and
    134         // Linux they can change from outside and we won't be notified of it.
    135         chrome.send('updatePasswordLists');
    136       }
    137     },
    138 
    139     /**
    140      * Updates the visibility of the list and empty list placeholder.
    141      * @param {!List} list The list to toggle visilibility for.
    142      */
    143     updateListVisibility_: function(list) {
    144       var empty = list.dataModel.length == 0;
    145       var listPlaceHolderID = list.id + '-empty-placeholder';
    146       list.hidden = empty;
    147       $(listPlaceHolderID).hidden = !empty;
    148     },
    149 
    150     /**
    151      * Updates the data model for the saved passwords list with the values from
    152      * |entries|.
    153      * @param {Array} entries The list of saved password data.
    154      */
    155     setSavedPasswordsList_: function(entries) {
    156       if (this.lastQuery_) {
    157         // Implement password searching here in javascript, rather than in C++.
    158         // The number of saved passwords shouldn't be too big for us to handle.
    159         var query = this.lastQuery_;
    160         var filter = function(entry, index, list) {
    161           // Search both URL and username.
    162           if (entry[0].toLowerCase().indexOf(query.toLowerCase()) >= 0 ||
    163               entry[1].toLowerCase().indexOf(query.toLowerCase()) >= 0) {
    164             // Keep the original index so we can delete correctly. See also
    165             // deleteItemAtIndex() in password_manager_list.js that uses this.
    166             entry[3] = index;
    167             return true;
    168           }
    169           return false;
    170         };
    171         entries = entries.filter(filter);
    172       }
    173       this.savedPasswordsList_.dataModel = new ArrayDataModel(entries);
    174       this.updateListVisibility_(this.savedPasswordsList_);
    175     },
    176 
    177     /**
    178      * Updates the data model for the password exceptions list with the values
    179      * from |entries|.
    180      * @param {Array} entries The list of password exception data.
    181      */
    182     setPasswordExceptionsList_: function(entries) {
    183       this.passwordExceptionsList_.dataModel = new ArrayDataModel(entries);
    184       this.updateListVisibility_(this.passwordExceptionsList_);
    185     },
    186 
    187     /**
    188      * Reveals the password for a saved password entry. This is called by the
    189      * backend after it has authenticated the user.
    190      * @param {number} index The original index of the entry in the model.
    191      * @param {string} password The saved password.
    192      */
    193     showPassword_: function(index, password) {
    194       var model = this.savedPasswordsList_.dataModel;
    195       if (this.lastQuery_) {
    196         // When a filter is active, |index| does not represent the current
    197         // index in the model, but each entry stores its original index, so
    198         // we can find the item using a linear search.
    199         for (var i = 0; i < model.length; ++i) {
    200           if (model.item(i)[3] == index) {
    201             index = i;
    202             break;
    203           }
    204         }
    205       }
    206 
    207       // Reveal the password in the UI.
    208       var item = this.savedPasswordsList_.getListItemByIndex(index);
    209       item.showPassword(password);
    210     },
    211   };
    212 
    213   /**
    214    * Removes a saved password.
    215    * @param {number} rowIndex indicating the row to remove.
    216    */
    217   PasswordManager.removeSavedPassword = function(rowIndex) {
    218       chrome.send('removeSavedPassword', [String(rowIndex)]);
    219   };
    220 
    221   /**
    222    * Removes a password exception.
    223    * @param {number} rowIndex indicating the row to remove.
    224    */
    225   PasswordManager.removePasswordException = function(rowIndex) {
    226       chrome.send('removePasswordException', [String(rowIndex)]);
    227   };
    228 
    229   PasswordManager.requestShowPassword = function(index) {
    230     chrome.send('requestShowPassword', [index]);
    231   };
    232 
    233   // Forward public APIs to private implementations on the singleton instance.
    234   [
    235     'setSavedPasswordsList',
    236     'setPasswordExceptionsList',
    237     'showPassword'
    238    ].forEach(function(name) {
    239      PasswordManager[name] = function() {
    240       var instance = PasswordManager.getInstance();
    241       return instance[name + '_'].apply(instance, arguments);
    242     };
    243   });
    244 
    245   // Export
    246   return {
    247     PasswordManager: PasswordManager
    248   };
    249 
    250 });
    251