• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5cr.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