Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(846)

Side by Side Diff: chrome/browser/password_manager/password_syncable_service.cc

Issue 139443004: Password sync refactoring: implemented ProcessSyncChanges() and GetAllSyncData() (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Added DCHECK() into ProcessSyncChanges() Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/password_manager/password_syncable_service.h" 5 #include "chrome/browser/password_manager/password_syncable_service.h"
6 6
7 #include "base/location.h" 7 #include "base/location.h"
8 #include "base/memory/scoped_vector.h" 8 #include "base/memory/scoped_vector.h"
9 #include "base/metrics/histogram.h" 9 #include "base/metrics/histogram.h"
10 #include "base/strings/utf_string_conversions.h" 10 #include "base/strings/utf_string_conversions.h"
11 #include "components/autofill/core/common/password_form.h" 11 #include "components/autofill/core/common/password_form.h"
12 #include "components/password_manager/core/browser/password_store.h" 12 #include "components/password_manager/core/browser/password_store.h"
13 #include "net/base/escape.h" 13 #include "net/base/escape.h"
14 #include "sync/api/sync_error_factory.h" 14 #include "sync/api/sync_error_factory.h"
15 15
16 namespace { 16 namespace {
17 17
18 // Describes the result of merging sync and local passwords.
19 enum MergeResult {
20 IDENTICAL,
21 SYNC,
22 LOCAL,
23 };
24
18 // Merges the local and sync passwords and outputs the entry into 25 // Merges the local and sync passwords and outputs the entry into
19 // |new_password_form|. Returns true if the local and the sync passwords differ. 26 // |new_password_form|. Returns MergeResult value describing the content of
20 // Returns false if they are identical. 27 // |new_password_form|.
21 bool MergeLocalAndSyncPasswords( 28 MergeResult MergeLocalAndSyncPasswords(
22 const sync_pb::PasswordSpecificsData& password_specifics, 29 const sync_pb::PasswordSpecificsData& password_specifics,
23 const autofill::PasswordForm& password_form, 30 const autofill::PasswordForm& password_form,
24 autofill::PasswordForm* new_password_form) { 31 autofill::PasswordForm* new_password_form) {
25 DCHECK(new_password_form); 32 DCHECK(new_password_form);
26 if (password_form.scheme == password_specifics.scheme() && 33 if (password_form.scheme == password_specifics.scheme() &&
27 password_form.signon_realm == password_specifics.signon_realm() && 34 password_form.signon_realm == password_specifics.signon_realm() &&
28 password_form.origin.spec() == password_specifics.origin() && 35 password_form.origin.spec() == password_specifics.origin() &&
29 password_form.action.spec() == password_specifics.action() && 36 password_form.action.spec() == password_specifics.action() &&
30 base::UTF16ToUTF8(password_form.username_element) == 37 base::UTF16ToUTF8(password_form.username_element) ==
31 password_specifics.username_element() && 38 password_specifics.username_element() &&
32 base::UTF16ToUTF8(password_form.password_element) == 39 base::UTF16ToUTF8(password_form.password_element) ==
33 password_specifics.password_element() && 40 password_specifics.password_element() &&
34 base::UTF16ToUTF8(password_form.username_value) == 41 base::UTF16ToUTF8(password_form.username_value) ==
35 password_specifics.username_value() && 42 password_specifics.username_value() &&
36 base::UTF16ToUTF8(password_form.password_value) == 43 base::UTF16ToUTF8(password_form.password_value) ==
37 password_specifics.password_value() && 44 password_specifics.password_value() &&
38 password_form.ssl_valid == password_specifics.ssl_valid() && 45 password_form.ssl_valid == password_specifics.ssl_valid() &&
39 password_form.preferred == password_specifics.preferred() && 46 password_form.preferred == password_specifics.preferred() &&
40 password_form.date_created.ToInternalValue() == 47 password_form.date_created.ToInternalValue() ==
41 password_specifics.date_created() && 48 password_specifics.date_created() &&
42 password_form.blacklisted_by_user == password_specifics.blacklisted()) { 49 password_form.blacklisted_by_user == password_specifics.blacklisted()) {
43 return false; 50 return IDENTICAL;
44 } 51 }
45 52
46 // If the passwords differ, take the one that was created more recently. 53 // If the passwords differ, take the one that was created more recently.
47 if (base::Time::FromInternalValue(password_specifics.date_created()) <= 54 if (base::Time::FromInternalValue(password_specifics.date_created()) <=
48 password_form.date_created) { 55 password_form.date_created) {
49 *new_password_form = password_form; 56 *new_password_form = password_form;
50 } else { 57 return LOCAL;
51 PasswordFromSpecifics(password_specifics, new_password_form);
52 } 58 }
53 59
54 return true; 60 PasswordFromSpecifics(password_specifics, new_password_form);
61 return SYNC;
55 } 62 }
56 63
57 std::string MakePasswordSyncTag(const std::string& origin_url, 64 std::string MakePasswordSyncTag(const std::string& origin_url,
58 const std::string& username_element, 65 const std::string& username_element,
59 const std::string& username_value, 66 const std::string& username_value,
60 const std::string& password_element, 67 const std::string& password_element,
61 const std::string& signon_realm) { 68 const std::string& signon_realm) {
62 return net::EscapePath(origin_url) + "|" + 69 return net::EscapePath(origin_url) + "|" +
63 net::EscapePath(username_element) + "|" + 70 net::EscapePath(username_element) + "|" +
64 net::EscapePath(username_value) + "|" + 71 net::EscapePath(username_value) + "|" +
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
107 syncer::SyncMergeResult PasswordSyncableService::MergeDataAndStartSyncing( 114 syncer::SyncMergeResult PasswordSyncableService::MergeDataAndStartSyncing(
108 syncer::ModelType type, 115 syncer::ModelType type,
109 const syncer::SyncDataList& initial_sync_data, 116 const syncer::SyncDataList& initial_sync_data,
110 scoped_ptr<syncer::SyncChangeProcessor> sync_processor, 117 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
111 scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) { 118 scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) {
112 DCHECK_EQ(syncer::PASSWORDS, type); 119 DCHECK_EQ(syncer::PASSWORDS, type);
113 syncer::SyncMergeResult merge_result(type); 120 syncer::SyncMergeResult merge_result(type);
114 sync_error_factory_ = sync_error_factory.Pass(); 121 sync_error_factory_ = sync_error_factory.Pass();
115 sync_processor_ = sync_processor.Pass(); 122 sync_processor_ = sync_processor.Pass();
116 123
124 // We add all the db entries as |new_local_entries| initially. During model
125 // association entries that match a sync entry will be removed and this list
126 // will only contain entries that are not in sync.
117 ScopedVector<autofill::PasswordForm> password_entries; 127 ScopedVector<autofill::PasswordForm> password_entries;
118 if (!password_store_->FillAutofillableLogins(&password_entries.get())) { 128 PasswordEntryMap new_local_entries;
119 // Password store often fails to load passwords. Track failures with UMA. 129 if (!ReadFromPasswordStore(&password_entries, &new_local_entries)) {
120 // (http://crbug.com/249000) 130 DCHECK(sync_error_factory_);
121 UMA_HISTOGRAM_ENUMERATION("Sync.LocalDataFailedToLoad",
122 syncer::PASSWORDS,
123 syncer::MODEL_TYPE_COUNT);
124 merge_result.set_error(sync_error_factory_->CreateAndUploadError( 131 merge_result.set_error(sync_error_factory_->CreateAndUploadError(
125 FROM_HERE, 132 FROM_HERE,
126 "Failed to get passwords from store.")); 133 "Failed to get passwords from store."));
127 return merge_result; 134 return merge_result;
128 } 135 }
129 136
130 PasswordEntryMap new_local_entries;
131 for (PasswordForms::iterator it = password_entries.begin();
132 it != password_entries.end(); ++it) {
133 autofill::PasswordForm* password_form = *it;
134 // We add all the db entries as |new_local_entries| initially. During
135 // model association entries that match a sync entry will be
136 // removed and this list will only contain entries that are not in sync.
137 new_local_entries.insert(
138 std::make_pair(MakePasswordSyncTag(*password_form), password_form));
139 }
140
141 merge_result.set_num_items_before_association(new_local_entries.size()); 137 merge_result.set_num_items_before_association(new_local_entries.size());
142 138
143 // List that contains the entries that are known only to sync. 139 // List that contains the entries that are known only to sync.
144 ScopedVector<autofill::PasswordForm> new_sync_entries; 140 ScopedVector<autofill::PasswordForm> new_sync_entries;
145 141
146 // List that contains the entries that are known to both sync and db but 142 // List that contains the entries that are known to both sync and db but
147 // have updates in sync. They need to be updated in the passwords db. 143 // have updates in sync. They need to be updated in the passwords db.
148 ScopedVector<autofill::PasswordForm> updated_sync_entries; 144 ScopedVector<autofill::PasswordForm> updated_sync_entries;
149 145
150 // Changes from password db that need to be propagated to sync. 146 // Changes from password db that need to be propagated to sync.
151 syncer::SyncChangeList updated_db_entries; 147 syncer::SyncChangeList updated_db_entries;
152 for (syncer::SyncDataList::const_iterator sync_iter = 148 for (syncer::SyncDataList::const_iterator sync_iter =
153 initial_sync_data.begin(); 149 initial_sync_data.begin();
154 sync_iter != initial_sync_data.end(); ++sync_iter) { 150 sync_iter != initial_sync_data.end(); ++sync_iter) {
155 CreateOrUpdateEntry(*sync_iter, 151 CreateOrUpdateEntry(*sync_iter,
156 &new_local_entries, 152 &new_local_entries,
157 &new_sync_entries, 153 &new_sync_entries,
158 &updated_sync_entries, 154 &updated_sync_entries,
159 &updated_db_entries); 155 &updated_db_entries);
160 } 156 }
161 157
162 WriteToPasswordStore(new_sync_entries.get(), 158 WriteToPasswordStore(new_sync_entries.get(),
163 updated_sync_entries.get()); 159 updated_sync_entries.get(),
160 PasswordForms());
164 161
165 merge_result.set_num_items_after_association( 162 merge_result.set_num_items_after_association(
166 merge_result.num_items_before_association() + new_sync_entries.size()); 163 merge_result.num_items_before_association() + new_sync_entries.size());
167 164
168 merge_result.set_num_items_added(new_sync_entries.size()); 165 merge_result.set_num_items_added(new_sync_entries.size());
169 166
170 merge_result.set_num_items_modified(updated_sync_entries.size()); 167 merge_result.set_num_items_modified(updated_sync_entries.size());
171 168
172 for (PasswordEntryMap::iterator it = new_local_entries.begin(); 169 for (PasswordEntryMap::iterator it = new_local_entries.begin();
173 it != new_local_entries.end(); 170 it != new_local_entries.end();
174 ++it) { 171 ++it) {
175 updated_db_entries.push_back( 172 updated_db_entries.push_back(
176 syncer::SyncChange(FROM_HERE, 173 syncer::SyncChange(FROM_HERE,
177 syncer::SyncChange::ACTION_ADD, 174 syncer::SyncChange::ACTION_ADD,
178 SyncDataFromPassword(*it->second))); 175 SyncDataFromPassword(*it->second)));
179 } 176 }
180 177
181 merge_result.set_error( 178 merge_result.set_error(
182 sync_processor_->ProcessSyncChanges(FROM_HERE, updated_db_entries)); 179 sync_processor_->ProcessSyncChanges(FROM_HERE, updated_db_entries));
183 return merge_result; 180 return merge_result;
184 } 181 }
185 182
186 void PasswordSyncableService::StopSyncing(syncer::ModelType type) { 183 void PasswordSyncableService::StopSyncing(syncer::ModelType type) {
187 sync_processor_.reset(); 184 sync_processor_.reset();
188 sync_error_factory_.reset(); 185 sync_error_factory_.reset();
189 } 186 }
190 187
191 syncer::SyncDataList PasswordSyncableService::GetAllSyncData( 188 syncer::SyncDataList PasswordSyncableService::GetAllSyncData(
192 syncer::ModelType type) const { 189 syncer::ModelType type) const {
190 DCHECK_EQ(syncer::PASSWORDS, type);
191 ScopedVector<autofill::PasswordForm> password_entries;
192 ReadFromPasswordStore(&password_entries, NULL);
193
193 syncer::SyncDataList sync_data; 194 syncer::SyncDataList sync_data;
195 for (PasswordForms::iterator it = password_entries.begin();
196 it != password_entries.end(); ++it) {
197 sync_data.push_back(SyncDataFromPassword(**it));
198 }
194 return sync_data; 199 return sync_data;
195 } 200 }
196 201
197 syncer::SyncError PasswordSyncableService::ProcessSyncChanges( 202 syncer::SyncError PasswordSyncableService::ProcessSyncChanges(
198 const tracked_objects::Location& from_here, 203 const tracked_objects::Location& from_here,
199 const syncer::SyncChangeList& change_list) { 204 const syncer::SyncChangeList& change_list) {
200 syncer::SyncError error(FROM_HERE, 205 // The |db_entries_map| and associated vectors are filled only in case of
201 syncer::SyncError::UNRECOVERABLE_ERROR, 206 // update change.
202 "Password Syncable Service Not Implemented.", 207 PasswordEntryMap db_entries_map;
203 syncer::PASSWORDS); 208 ScopedVector<autofill::PasswordForm> password_db_entries;
204 return error; 209 ScopedVector<autofill::PasswordForm> new_sync_entries;
210 ScopedVector<autofill::PasswordForm> updated_sync_entries;
211 ScopedVector<autofill::PasswordForm> deleted_entries;
212 bool has_read_passwords = false;
213
214 for (syncer::SyncChangeList::const_iterator it = change_list.begin();
215 it != change_list.end();
216 ++it) {
217 switch (it->change_type()) {
218 case syncer::SyncChange::ACTION_ADD: {
219 const sync_pb::EntitySpecifics& specifics =
220 it->sync_data().GetSpecifics();
221 new_sync_entries.push_back(new autofill::PasswordForm);
222 PasswordFromSpecifics(specifics.password().client_only_encrypted_data(),
223 new_sync_entries.back());
224 break;
225 }
226 case syncer::SyncChange::ACTION_UPDATE: {
227 if (!has_read_passwords) {
228 if (ReadFromPasswordStore(&password_db_entries,
229 &db_entries_map)) {
230 has_read_passwords = true;
231 } else {
232 return sync_error_factory_->CreateAndUploadError(
233 FROM_HERE,
234 "Failed to get passwords from store.");
235 }
236 }
237 syncer::SyncChangeList sync_changes;
238
239 CreateOrUpdateEntry(it->sync_data(),
240 &db_entries_map,
241 &new_sync_entries,
242 &updated_sync_entries,
243 &sync_changes);
244 DCHECK(sync_changes.empty());
245 break;
246 }
247
248 case syncer::SyncChange::ACTION_DELETE: {
249 const sync_pb::EntitySpecifics& specifics =
250 it->sync_data().GetSpecifics();
251 const sync_pb::PasswordSpecificsData& password_specifics(
252 specifics.password().client_only_encrypted_data());
253 deleted_entries.push_back(new autofill::PasswordForm);
254 PasswordFromSpecifics(password_specifics, deleted_entries.back());
255 break;
256 }
257 case syncer::SyncChange::ACTION_INVALID:
258 return sync_error_factory_->CreateAndUploadError(
259 FROM_HERE,
260 "Failed to process sync changes for passwords datatype.");
261 }
262 }
263 WriteToPasswordStore(new_sync_entries.get(),
264 updated_sync_entries.get(),
265 deleted_entries.get());
266 return syncer::SyncError();
205 } 267 }
206 268
207 void PasswordSyncableService::ActOnPasswordStoreChanges( 269 void PasswordSyncableService::ActOnPasswordStoreChanges(
208 const PasswordStoreChangeList& local_changes) { 270 const PasswordStoreChangeList& local_changes) {
209 if (!sync_processor_) 271 if (!sync_processor_)
210 return; 272 return;
211 syncer::SyncChangeList sync_changes; 273 syncer::SyncChangeList sync_changes;
212 for (PasswordStoreChangeList::const_iterator it = local_changes.begin(); 274 for (PasswordStoreChangeList::const_iterator it = local_changes.begin();
213 it != local_changes.end(); 275 it != local_changes.end();
214 ++it) { 276 ++it) {
215 sync_changes.push_back( 277 sync_changes.push_back(
216 syncer::SyncChange(FROM_HERE, 278 syncer::SyncChange(FROM_HERE,
217 GetSyncChangeType(it->type()), 279 GetSyncChangeType(it->type()),
218 SyncDataFromPassword(it->form()))); 280 SyncDataFromPassword(it->form())));
219 } 281 }
220 sync_processor_->ProcessSyncChanges(FROM_HERE, sync_changes); 282 sync_processor_->ProcessSyncChanges(FROM_HERE, sync_changes);
221 } 283 }
222 284
285 bool PasswordSyncableService::ReadFromPasswordStore(
286 ScopedVector<autofill::PasswordForm>* password_entries,
287 PasswordEntryMap* passwords_entry_map) const {
288 DCHECK(password_entries);
289 if (!password_store_->FillAutofillableLogins(&password_entries->get()) ||
290 !password_store_->FillBlacklistLogins(&password_entries->get())) {
291 // Password store often fails to load passwords. Track failures with UMA.
292 // (http://crbug.com/249000)
293 UMA_HISTOGRAM_ENUMERATION("Sync.LocalDataFailedToLoad",
294 ModelTypeToHistogramInt(syncer::PASSWORDS),
295 syncer::MODEL_TYPE_COUNT);
296 return false;
297 }
298
299 if (!passwords_entry_map)
300 return true;
301
302 for (PasswordForms::iterator it = password_entries->begin();
303 it != password_entries->end(); ++it) {
304 autofill::PasswordForm* password_form = *it;
305 passwords_entry_map->insert(
306 std::make_pair(MakePasswordSyncTag(*password_form), password_form));
307 }
308
309 return true;
310 }
311
223 void PasswordSyncableService::WriteToPasswordStore( 312 void PasswordSyncableService::WriteToPasswordStore(
224 const PasswordForms& new_entries, 313 const PasswordForms& new_entries,
225 const PasswordForms& updated_entries) { 314 const PasswordForms& updated_entries,
315 const PasswordForms& deleted_entries) {
226 PasswordStoreChangeList changes; 316 PasswordStoreChangeList changes;
227 for (std::vector<autofill::PasswordForm*>::const_iterator it = 317 for (std::vector<autofill::PasswordForm*>::const_iterator it =
228 new_entries.begin(); 318 new_entries.begin();
229 it != new_entries.end(); 319 it != new_entries.end();
230 ++it) { 320 ++it) {
231 AppendChanges(password_store_->AddLoginImpl(**it), &changes); 321 AppendChanges(password_store_->AddLoginImpl(**it), &changes);
232 } 322 }
233 323
234 for (std::vector<autofill::PasswordForm*>::const_iterator it = 324 for (std::vector<autofill::PasswordForm*>::const_iterator it =
235 updated_entries.begin(); 325 updated_entries.begin();
236 it != updated_entries.end(); 326 it != updated_entries.end();
237 ++it) { 327 ++it) {
238 AppendChanges(password_store_->UpdateLoginImpl(**it), &changes); 328 AppendChanges(password_store_->UpdateLoginImpl(**it), &changes);
239 } 329 }
240 330
331 for (std::vector<autofill::PasswordForm*>::const_iterator it =
332 deleted_entries.begin();
333 it != deleted_entries.end();
334 ++it) {
335 AppendChanges(password_store_->RemoveLoginImpl(**it), &changes);
336 }
337
241 // We have to notify password store observers of the change by hand since 338 // We have to notify password store observers of the change by hand since
242 // we use internal password store interfaces to make changes synchronously. 339 // we use internal password store interfaces to make changes synchronously.
243 NotifyPasswordStoreOfLoginChanges(changes); 340 NotifyPasswordStoreOfLoginChanges(changes);
244 } 341 }
245 342
246 void PasswordSyncableService::NotifyPasswordStoreOfLoginChanges( 343 void PasswordSyncableService::NotifyPasswordStoreOfLoginChanges(
247 const PasswordStoreChangeList& changes) { 344 const PasswordStoreChangeList& changes) {
248 password_store_->NotifyLoginsChanged(changes); 345 password_store_->NotifyLoginsChanged(changes);
249 } 346 }
250 347
(...skipping 14 matching lines...) Expand all
265 if (existing_local_entry_iter == umatched_data_from_password_db->end()) { 362 if (existing_local_entry_iter == umatched_data_from_password_db->end()) {
266 // The sync data is not in the password store, so we need to create it in 363 // The sync data is not in the password store, so we need to create it in
267 // the password store. Add the entry to the new_entries list. 364 // the password store. Add the entry to the new_entries list.
268 scoped_ptr<autofill::PasswordForm> new_password(new autofill::PasswordForm); 365 scoped_ptr<autofill::PasswordForm> new_password(new autofill::PasswordForm);
269 PasswordFromSpecifics(password_specifics, new_password.get()); 366 PasswordFromSpecifics(password_specifics, new_password.get());
270 new_sync_entries->push_back(new_password.release()); 367 new_sync_entries->push_back(new_password.release());
271 } else { 368 } else {
272 // The entry is in password store. If the entries are not identical, then 369 // The entry is in password store. If the entries are not identical, then
273 // the entries need to be merged. 370 // the entries need to be merged.
274 scoped_ptr<autofill::PasswordForm> new_password(new autofill::PasswordForm); 371 scoped_ptr<autofill::PasswordForm> new_password(new autofill::PasswordForm);
275 if (MergeLocalAndSyncPasswords(password_specifics, 372 switch (MergeLocalAndSyncPasswords(password_specifics,
276 *existing_local_entry_iter->second, 373 *existing_local_entry_iter->second,
277 new_password.get())) { 374 new_password.get())) {
278 // Rather than checking which database -- sync or local -- needs updating, 375 case IDENTICAL:
279 // simply push an update to both. This will end up being a noop for the 376 break;
280 // database that didn't need an update. 377 case SYNC:
281 updated_db_entries->push_back( 378 updated_sync_entries->push_back(new_password.release());
282 syncer::SyncChange(FROM_HERE, 379 break;
283 syncer::SyncChange::ACTION_UPDATE, 380 case LOCAL:
284 SyncDataFromPassword(*new_password))); 381 updated_db_entries->push_back(
285 382 syncer::SyncChange(FROM_HERE,
286 updated_sync_entries->push_back(new_password.release()); 383 syncer::SyncChange::ACTION_UPDATE,
384 SyncDataFromPassword(*new_password)));
385 break;
287 } 386 }
288 // Remove the entry from the entry map to indicate a match has been found. 387 // Remove the entry from the entry map to indicate a match has been found.
289 // Entries that remain in the map at the end of associating all sync entries 388 // Entries that remain in the map at the end of associating all sync entries
290 // will be treated as additions that need to be propagated to sync. 389 // will be treated as additions that need to be propagated to sync.
291 umatched_data_from_password_db->erase(existing_local_entry_iter); 390 umatched_data_from_password_db->erase(existing_local_entry_iter);
292 } 391 }
293 } 392 }
294 393
295 syncer::SyncData SyncDataFromPassword( 394 syncer::SyncData SyncDataFromPassword(
296 const autofill::PasswordForm& password_form) { 395 const autofill::PasswordForm& password_form) {
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
340 } 439 }
341 440
342 std::string MakePasswordSyncTag( 441 std::string MakePasswordSyncTag(
343 const sync_pb::PasswordSpecificsData& password) { 442 const sync_pb::PasswordSpecificsData& password) {
344 return MakePasswordSyncTag(password.origin(), 443 return MakePasswordSyncTag(password.origin(),
345 password.username_element(), 444 password.username_element(),
346 password.username_value(), 445 password.username_value(),
347 password.password_element(), 446 password.password_element(),
348 password.signon_realm()); 447 password.signon_realm());
349 } 448 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698