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