NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / password_manager / password_form_manager.cc
blobfd361b6e2679e72e3b2a7508336945a80f8c07b2
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.
5 #include "chrome/browser/password_manager/password_form_manager.h"
7 #include <algorithm>
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/string_util.h"
12 #include "chrome/browser/password_manager/password_manager.h"
13 #include "chrome/browser/password_manager/password_manager_client.h"
14 #include "chrome/browser/password_manager/password_manager_driver.h"
15 #include "chrome/browser/password_manager/password_store_factory.h"
16 #include "components/autofill/core/browser/autofill_manager.h"
17 #include "components/autofill/core/browser/form_structure.h"
18 #include "components/autofill/core/browser/validation.h"
19 #include "components/autofill/core/common/password_form.h"
20 #include "components/password_manager/core/browser/password_store.h"
22 using autofill::FormStructure;
23 using autofill::PasswordForm;
24 using autofill::PasswordFormMap;
25 using base::Time;
27 PasswordFormManager::PasswordFormManager(PasswordManager* password_manager,
28 PasswordManagerClient* client,
29 PasswordManagerDriver* driver,
30 const PasswordForm& observed_form,
31 bool ssl_valid)
32 : best_matches_deleter_(&best_matches_),
33 observed_form_(observed_form),
34 is_new_login_(true),
35 has_generated_password_(false),
36 password_manager_(password_manager),
37 preferred_match_(NULL),
38 state_(PRE_MATCHING_PHASE),
39 client_(client),
40 driver_(driver),
41 manager_action_(kManagerActionNone),
42 user_action_(kUserActionNone),
43 submit_result_(kSubmitResultNotSubmitted) {
44 if (observed_form_.origin.is_valid())
45 base::SplitString(observed_form_.origin.path(), '/', &form_path_tokens_);
46 observed_form_.ssl_valid = ssl_valid;
49 PasswordFormManager::~PasswordFormManager() {
50 UMA_HISTOGRAM_ENUMERATION("PasswordManager.ActionsTakenWithPsl",
51 GetActionsTaken(),
52 kMaxNumActionsTaken);
55 int PasswordFormManager::GetActionsTaken() {
56 return user_action_ + kUserActionMax * (manager_action_ +
57 kManagerActionMax * submit_result_);
60 // TODO(timsteele): use a hash of some sort in the future?
61 bool PasswordFormManager::DoesManage(const PasswordForm& form,
62 ActionMatch action_match) const {
63 if (form.scheme != PasswordForm::SCHEME_HTML)
64 return observed_form_.signon_realm == form.signon_realm;
66 // HTML form case.
67 // At a minimum, username and password element must match.
68 if (!((form.username_element == observed_form_.username_element) &&
69 (form.password_element == observed_form_.password_element))) {
70 return false;
73 // When action match is required, the action URL must match, but
74 // the form is allowed to have an empty action URL (See bug 1107719).
75 // Otherwise ignore action URL, this is to allow saving password form with
76 // dynamically changed action URL (See bug 27246).
77 if (form.action.is_valid() && (form.action != observed_form_.action)) {
78 if (action_match == ACTION_MATCH_REQUIRED)
79 return false;
82 // If this is a replay of the same form in the case a user entered an invalid
83 // password, the origin of the new form may equal the action of the "first"
84 // form.
85 if (!((form.origin == observed_form_.origin) ||
86 (form.origin == observed_form_.action))) {
87 if (form.origin.SchemeIsSecure() &&
88 !observed_form_.origin.SchemeIsSecure()) {
89 // Compare origins, ignoring scheme. There is no easy way to do this
90 // with GURL because clearing the scheme would result in an invalid url.
91 // This is for some sites (such as Hotmail) that begin on an http page and
92 // head to https for the retry when password was invalid.
93 std::string::const_iterator after_scheme1 = form.origin.spec().begin() +
94 form.origin.scheme().length();
95 std::string::const_iterator after_scheme2 =
96 observed_form_.origin.spec().begin() +
97 observed_form_.origin.scheme().length();
98 return std::search(after_scheme1,
99 form.origin.spec().end(),
100 after_scheme2,
101 observed_form_.origin.spec().end())
102 != form.origin.spec().end();
104 return false;
106 return true;
109 bool PasswordFormManager::IsBlacklisted() {
110 DCHECK_EQ(state_, POST_MATCHING_PHASE);
111 if (preferred_match_ && preferred_match_->blacklisted_by_user)
112 return true;
113 return false;
116 void PasswordFormManager::PermanentlyBlacklist() {
117 DCHECK_EQ(state_, POST_MATCHING_PHASE);
119 // Configure the form about to be saved for blacklist status.
120 pending_credentials_.preferred = true;
121 pending_credentials_.blacklisted_by_user = true;
122 pending_credentials_.username_value.clear();
123 pending_credentials_.password_value.clear();
125 // Retroactively forget existing matches for this form, so we NEVER prompt or
126 // autofill it again.
127 int num_passwords_deleted = 0;
128 if (!best_matches_.empty()) {
129 PasswordFormMap::const_iterator iter;
130 PasswordStore* password_store = client_->GetPasswordStore();
131 if (!password_store) {
132 NOTREACHED();
133 return;
135 for (iter = best_matches_.begin(); iter != best_matches_.end(); ++iter) {
136 // We want to remove existing matches for this form so that the exact
137 // origin match with |blackisted_by_user == true| is the only result that
138 // shows up in the future for this origin URL. However, we don't want to
139 // delete logins that were actually saved on a different page (hence with
140 // different origin URL) and just happened to match this form because of
141 // the scoring algorithm. See bug 1204493.
142 if (iter->second->origin == observed_form_.origin) {
143 password_store->RemoveLogin(*iter->second);
144 ++num_passwords_deleted;
149 UMA_HISTOGRAM_COUNTS("PasswordManager.NumPasswordsDeletedWhenBlacklisting",
150 num_passwords_deleted);
152 // Save the pending_credentials_ entry marked as blacklisted.
153 SaveAsNewLogin(false);
156 void PasswordFormManager::SetUseAdditionalPasswordAuthentication(
157 bool use_additional_authentication) {
158 pending_credentials_.use_additional_authentication =
159 use_additional_authentication;
162 bool PasswordFormManager::IsNewLogin() {
163 DCHECK_EQ(state_, POST_MATCHING_PHASE);
164 return is_new_login_;
167 bool PasswordFormManager::IsPendingCredentialsPublicSuffixMatch() {
168 return pending_credentials_.IsPublicSuffixMatch();
171 void PasswordFormManager::SetHasGeneratedPassword() {
172 has_generated_password_ = true;
175 bool PasswordFormManager::HasGeneratedPassword() {
176 // This check is permissive, as the user may have generated a password and
177 // then edited it in the form itself. However, even in this case the user
178 // has already given consent, so we treat these cases the same.
179 return has_generated_password_;
182 bool PasswordFormManager::HasValidPasswordForm() {
183 DCHECK_EQ(state_, POST_MATCHING_PHASE);
184 // Non-HTML password forms (primarily HTTP and FTP autentication)
185 // do not contain username_element and password_element values.
186 if (observed_form_.scheme != PasswordForm::SCHEME_HTML)
187 return true;
188 return !observed_form_.username_element.empty() &&
189 !observed_form_.password_element.empty();
192 void PasswordFormManager::ProvisionallySave(
193 const PasswordForm& credentials,
194 OtherPossibleUsernamesAction action) {
195 DCHECK_EQ(state_, POST_MATCHING_PHASE);
196 DCHECK(DoesManage(credentials, ACTION_MATCH_NOT_REQUIRED));
198 // Make sure the important fields stay the same as the initially observed or
199 // autofilled ones, as they may have changed if the user experienced a login
200 // failure.
201 // Look for these credentials in the list containing auto-fill entries.
202 PasswordFormMap::const_iterator it =
203 best_matches_.find(credentials.username_value);
204 if (it != best_matches_.end()) {
205 // The user signed in with a login we autofilled.
206 pending_credentials_ = *it->second;
208 // Public suffix matches should always be new logins, since we want to store
209 // them so they can automatically be filled in later.
210 is_new_login_ = IsPendingCredentialsPublicSuffixMatch();
211 if (is_new_login_)
212 user_action_ = kUserActionChoosePslMatch;
214 // Check to see if we're using a known username but a new password.
215 if (pending_credentials_.password_value != credentials.password_value)
216 user_action_ = kUserActionOverride;
217 } else if (action == ALLOW_OTHER_POSSIBLE_USERNAMES &&
218 UpdatePendingCredentialsIfOtherPossibleUsername(
219 credentials.username_value)) {
220 // |pending_credentials_| is now set. Note we don't update
221 // |pending_credentials_.username_value| to |credentials.username_value|
222 // yet because we need to keep the original username to modify the stored
223 // credential.
224 selected_username_ = credentials.username_value;
225 is_new_login_ = false;
226 } else {
227 // User typed in a new, unknown username.
228 user_action_ = kUserActionOverride;
229 pending_credentials_ = observed_form_;
230 pending_credentials_.username_value = credentials.username_value;
231 pending_credentials_.other_possible_usernames =
232 credentials.other_possible_usernames;
235 pending_credentials_.action = credentials.action;
236 // If the user selected credentials we autofilled from a PasswordForm
237 // that contained no action URL (IE6/7 imported passwords, for example),
238 // bless it with the action URL from the observed form. See bug 1107719.
239 if (pending_credentials_.action.is_empty())
240 pending_credentials_.action = observed_form_.action;
242 pending_credentials_.password_value = credentials.password_value;
243 pending_credentials_.preferred = credentials.preferred;
245 if (has_generated_password_)
246 pending_credentials_.type = PasswordForm::TYPE_GENERATED;
249 void PasswordFormManager::Save() {
250 DCHECK_EQ(state_, POST_MATCHING_PHASE);
251 DCHECK(!driver_->IsOffTheRecord());
253 if (IsNewLogin())
254 SaveAsNewLogin(true);
255 else
256 UpdateLogin();
259 void PasswordFormManager::FetchMatchingLoginsFromPasswordStore(
260 PasswordStore::AuthorizationPromptPolicy prompt_policy) {
261 DCHECK_EQ(state_, PRE_MATCHING_PHASE);
262 state_ = MATCHING_PHASE;
263 PasswordStore* password_store = client_->GetPasswordStore();
264 if (!password_store) {
265 NOTREACHED();
266 return;
268 password_store->GetLogins(observed_form_, prompt_policy, this);
271 bool PasswordFormManager::HasCompletedMatching() {
272 return state_ == POST_MATCHING_PHASE;
275 void PasswordFormManager::OnRequestDone(
276 const std::vector<PasswordForm*>& logins_result) {
277 // Note that the result gets deleted after this call completes, but we own
278 // the PasswordForm objects pointed to by the result vector, thus we keep
279 // copies to a minimum here.
281 int best_score = 0;
282 // These credentials will be in the final result regardless of score.
283 std::vector<PasswordForm> credentials_to_keep;
284 for (size_t i = 0; i < logins_result.size(); i++) {
285 if (IgnoreResult(*logins_result[i])) {
286 delete logins_result[i];
287 continue;
289 // Score and update best matches.
290 int current_score = ScoreResult(*logins_result[i]);
291 // This check is here so we can append empty path matches in the event
292 // they don't score as high as others and aren't added to best_matches_.
293 // This is most commonly imported firefox logins. We skip blacklisted
294 // ones because clearly we don't want to autofill them, and secondly
295 // because they only mean something when we have no other matches already
296 // saved in Chrome - in which case they'll make it through the regular
297 // scoring flow below by design. Note signon_realm == origin implies empty
298 // path logins_result, since signon_realm is a prefix of origin for HTML
299 // password forms.
300 // TODO(timsteele): Bug 1269400. We probably should do something more
301 // elegant for any shorter-path match instead of explicitly handling empty
302 // path matches.
303 if ((observed_form_.scheme == PasswordForm::SCHEME_HTML) &&
304 (observed_form_.signon_realm == logins_result[i]->origin.spec()) &&
305 (current_score > 0) && (!logins_result[i]->blacklisted_by_user)) {
306 credentials_to_keep.push_back(*logins_result[i]);
309 // Always keep generated passwords as part of the result set. If a user
310 // generates a password on a signup form, it should show on a login form
311 // even if they have a previous login saved.
312 // TODO(gcasto): We don't want to cut credentials that were saved on signup
313 // forms even if they weren't generated, but currently it's hard to
314 // distinguish between those forms and two different login forms on the
315 // same domain. Filed http://crbug.com/294468 to look into this.
316 if (logins_result[i]->type == PasswordForm::TYPE_GENERATED)
317 credentials_to_keep.push_back(*logins_result[i]);
319 if (current_score < best_score) {
320 delete logins_result[i];
321 continue;
323 if (current_score == best_score) {
324 best_matches_[logins_result[i]->username_value] = logins_result[i];
325 } else if (current_score > best_score) {
326 best_score = current_score;
327 // This new login has a better score than all those up to this point
328 // Note 'this' owns all the PasswordForms in best_matches_.
329 STLDeleteValues(&best_matches_);
330 best_matches_.clear();
331 preferred_match_ = NULL; // Don't delete, its owned by best_matches_.
332 best_matches_[logins_result[i]->username_value] = logins_result[i];
334 preferred_match_ = logins_result[i]->preferred ? logins_result[i]
335 : preferred_match_;
337 // We're done matching now.
338 state_ = POST_MATCHING_PHASE;
340 if (best_score <= 0) {
341 return;
344 for (std::vector<PasswordForm>::const_iterator it =
345 credentials_to_keep.begin();
346 it != credentials_to_keep.end(); ++it) {
347 // If we don't already have a result with the same username, add the
348 // lower-scored match (if it had equal score it would already be in
349 // best_matches_).
350 if (best_matches_.find(it->username_value) == best_matches_.end())
351 best_matches_[it->username_value] = new PasswordForm(*it);
354 UMA_HISTOGRAM_COUNTS("PasswordManager.NumPasswordsNotShown",
355 logins_result.size() - best_matches_.size());
357 // It is possible we have at least one match but have no preferred_match_,
358 // because a user may have chosen to 'Forget' the preferred match. So we
359 // just pick the first one and whichever the user selects for submit will
360 // be saved as preferred.
361 DCHECK(!best_matches_.empty());
362 if (!preferred_match_)
363 preferred_match_ = best_matches_.begin()->second;
365 // Check to see if the user told us to ignore this site in the past.
366 if (preferred_match_->blacklisted_by_user) {
367 manager_action_ = kManagerActionBlacklisted;
368 return;
371 // If not blacklisted, inform the driver that password generation is allowed
372 // for |observed_form_|.
373 driver_->AllowPasswordGenerationForForm(&observed_form_);
375 // Proceed to autofill.
376 // Note that we provide the choices but don't actually prefill a value if:
377 // (1) we are in Incognito mode, (2) the ACTION paths don't match,
378 // or (3) if it matched using public suffix domain matching.
379 bool wait_for_username =
380 driver_->IsOffTheRecord() ||
381 observed_form_.action.GetWithEmptyPath() !=
382 preferred_match_->action.GetWithEmptyPath() ||
383 preferred_match_->IsPublicSuffixMatch();
384 if (wait_for_username)
385 manager_action_ = kManagerActionNone;
386 else
387 manager_action_ = kManagerActionAutofilled;
388 password_manager_->Autofill(observed_form_, best_matches_,
389 *preferred_match_, wait_for_username);
392 void PasswordFormManager::OnGetPasswordStoreResults(
393 const std::vector<autofill::PasswordForm*>& results) {
394 DCHECK_EQ(state_, MATCHING_PHASE);
396 if (results.empty()) {
397 state_ = POST_MATCHING_PHASE;
398 // No result means that we visit this site the first time so we don't need
399 // to check whether this site is blacklisted or not. Just send a message
400 // to allow password generation.
401 driver_->AllowPasswordGenerationForForm(&observed_form_);
402 return;
404 OnRequestDone(results);
407 bool PasswordFormManager::IgnoreResult(const PasswordForm& form) const {
408 // Ignore change password forms until we have some change password
409 // functionality
410 if (observed_form_.old_password_element.length() != 0) {
411 return true;
413 // Don't match an invalid SSL form with one saved under secure
414 // circumstances.
415 if (form.ssl_valid && !observed_form_.ssl_valid) {
416 return true;
418 return false;
421 void PasswordFormManager::SaveAsNewLogin(bool reset_preferred_login) {
422 DCHECK_EQ(state_, POST_MATCHING_PHASE);
423 DCHECK(IsNewLogin());
424 // The new_form is being used to sign in, so it is preferred.
425 DCHECK(pending_credentials_.preferred);
426 // new_form contains the same basic data as observed_form_ (because its the
427 // same form), but with the newly added credentials.
429 DCHECK(!driver_->IsOffTheRecord());
431 PasswordStore* password_store = client_->GetPasswordStore();
432 if (!password_store) {
433 NOTREACHED();
434 return;
437 pending_credentials_.date_created = Time::Now();
438 SanitizePossibleUsernames(&pending_credentials_);
439 password_store->AddLogin(pending_credentials_);
441 if (reset_preferred_login) {
442 UpdatePreferredLoginState(password_store);
446 void PasswordFormManager::SanitizePossibleUsernames(PasswordForm* form) {
447 // Remove any possible usernames that could be credit cards or SSN for privacy
448 // reasons. Also remove duplicates, both in other_possible_usernames and
449 // between other_possible_usernames and username_value.
450 std::set<base::string16> set;
451 for (std::vector<base::string16>::iterator it =
452 form->other_possible_usernames.begin();
453 it != form->other_possible_usernames.end(); ++it) {
454 if (!autofill::IsValidCreditCardNumber(*it) && !autofill::IsSSN(*it))
455 set.insert(*it);
457 set.erase(form->username_value);
458 std::vector<base::string16> temp(set.begin(), set.end());
459 form->other_possible_usernames.swap(temp);
462 void PasswordFormManager::UpdatePreferredLoginState(
463 PasswordStore* password_store) {
464 DCHECK(password_store);
465 PasswordFormMap::iterator iter;
466 for (iter = best_matches_.begin(); iter != best_matches_.end(); iter++) {
467 if (iter->second->username_value != pending_credentials_.username_value &&
468 iter->second->preferred) {
469 // This wasn't the selected login but it used to be preferred.
470 iter->second->preferred = false;
471 if (user_action_ == kUserActionNone)
472 user_action_ = kUserActionChoose;
473 password_store->UpdateLogin(*iter->second);
478 void PasswordFormManager::UpdateLogin() {
479 DCHECK_EQ(state_, POST_MATCHING_PHASE);
480 DCHECK(preferred_match_);
481 // If we're doing an Update, we either autofilled correctly and need to
482 // update the stats, or the user typed in a new password for autofilled
483 // username, or the user selected one of the non-preferred matches,
484 // thus requiring a swap of preferred bits.
485 DCHECK(!IsNewLogin() && pending_credentials_.preferred);
486 DCHECK(!driver_->IsOffTheRecord());
488 PasswordStore* password_store = client_->GetPasswordStore();
489 if (!password_store) {
490 NOTREACHED();
491 return;
494 // Update metadata.
495 ++pending_credentials_.times_used;
497 // Check to see if this form is a candidate for password generation.
498 CheckForAccountCreationForm(pending_credentials_, observed_form_);
500 UpdatePreferredLoginState(password_store);
502 // Remove alternate usernames. At this point we assume that we have found
503 // the right username.
504 pending_credentials_.other_possible_usernames.clear();
506 // Update the new preferred login.
507 if (!selected_username_.empty()) {
508 // An other possible username is selected. We set this selected username
509 // as the real username. The PasswordStore API isn't designed to update
510 // username, so we delete the old credentials and add a new one instead.
511 password_store->RemoveLogin(pending_credentials_);
512 pending_credentials_.username_value = selected_username_;
513 password_store->AddLogin(pending_credentials_);
514 } else if ((observed_form_.scheme == PasswordForm::SCHEME_HTML) &&
515 (observed_form_.origin.spec().length() >
516 observed_form_.signon_realm.length()) &&
517 (observed_form_.signon_realm ==
518 pending_credentials_.origin.spec())) {
519 // Note origin.spec().length > signon_realm.length implies the origin has a
520 // path, since signon_realm is a prefix of origin for HTML password forms.
522 // The user logged in successfully with one of our autofilled logins on a
523 // page with non-empty path, but the autofilled entry was initially saved/
524 // imported with an empty path. Rather than just mark this entry preferred,
525 // we create a more specific copy for this exact page and leave the "master"
526 // unchanged. This is to prevent the case where that master login is used
527 // on several sites (e.g site.com/a and site.com/b) but the user actually
528 // has a different preference on each site. For example, on /a, he wants the
529 // general empty-path login so it is flagged as preferred, but on /b he logs
530 // in with a different saved entry - we don't want to remove the preferred
531 // status of the former because upon return to /a it won't be the default-
532 // fill match.
533 // TODO(timsteele): Bug 1188626 - expire the master copies.
534 PasswordForm copy(pending_credentials_);
535 copy.origin = observed_form_.origin;
536 copy.action = observed_form_.action;
537 password_store->AddLogin(copy);
538 } else {
539 password_store->UpdateLogin(pending_credentials_);
543 bool PasswordFormManager::UpdatePendingCredentialsIfOtherPossibleUsername(
544 const base::string16& username) {
545 for (PasswordFormMap::const_iterator it = best_matches_.begin();
546 it != best_matches_.end(); ++it) {
547 for (size_t i = 0; i < it->second->other_possible_usernames.size(); ++i) {
548 if (it->second->other_possible_usernames[i] == username) {
549 pending_credentials_ = *it->second;
550 return true;
554 return false;
557 void PasswordFormManager::CheckForAccountCreationForm(
558 const PasswordForm& pending, const PasswordForm& observed) {
559 // We check to see if the saved form_data is the same as the observed
560 // form_data, which should never be true for passwords saved on account
561 // creation forms. This check is only made the first time a password is used
562 // to cut down on false positives. Specifically a site may have multiple login
563 // forms with different markup, which might look similar to a signup form.
564 if (pending.times_used == 1) {
565 FormStructure pending_structure(pending.form_data);
566 FormStructure observed_structure(observed.form_data);
567 // Ignore |pending_structure| if its FormData has no fields. This is to
568 // weed out those credentials that were saved before FormData was added
569 // to PasswordForm. Even without this check, these FormStructure's won't
570 // be uploaded, but it makes it hard to see if we are encountering
571 // unexpected errors.
572 if (!pending.form_data.fields.empty() &&
573 pending_structure.FormSignature() !=
574 observed_structure.FormSignature()) {
575 autofill::AutofillManager* autofill_manager;
576 if ((autofill_manager = driver_->GetAutofillManager())) {
577 // Note that this doesn't guarantee that the upload succeeded, only that
578 // |pending.form_data| is considered uploadable.
579 bool success =
580 autofill_manager->UploadPasswordGenerationForm(pending.form_data);
581 UMA_HISTOGRAM_BOOLEAN("PasswordGeneration.UploadStarted", success);
587 int PasswordFormManager::ScoreResult(const PasswordForm& candidate) const {
588 DCHECK_EQ(state_, MATCHING_PHASE);
589 // For scoring of candidate login data:
590 // The most important element that should match is the origin, followed by
591 // the action, the password name, the submit button name, and finally the
592 // username input field name.
593 // Exact origin match gives an addition of 64 (1 << 6) + # of matching url
594 // dirs.
595 // Partial match gives an addition of 32 (1 << 5) + # matching url dirs
596 // That way, a partial match cannot trump an exact match even if
597 // the partial one matches all other attributes (action, elements) (and
598 // regardless of the matching depth in the URL path).
599 // If public suffix origin match was not used, it gives an addition of
600 // 16 (1 << 4).
601 int score = 0;
602 if (candidate.origin == observed_form_.origin) {
603 // This check is here for the most common case which
604 // is we have a single match in the db for the given host,
605 // so we don't generally need to walk the entire URL path (the else
606 // clause).
607 score += (1 << 6) + static_cast<int>(form_path_tokens_.size());
608 } else {
609 // Walk the origin URL paths one directory at a time to see how
610 // deep the two match.
611 std::vector<std::string> candidate_path_tokens;
612 base::SplitString(candidate.origin.path(), '/', &candidate_path_tokens);
613 size_t depth = 0;
614 size_t max_dirs = std::min(form_path_tokens_.size(),
615 candidate_path_tokens.size());
616 while ((depth < max_dirs) && (form_path_tokens_[depth] ==
617 candidate_path_tokens[depth])) {
618 depth++;
619 score++;
621 // do we have a partial match?
622 score += (depth > 0) ? 1 << 5 : 0;
624 if (observed_form_.scheme == PasswordForm::SCHEME_HTML) {
625 if (!candidate.IsPublicSuffixMatch())
626 score += 1 << 4;
627 if (candidate.action == observed_form_.action)
628 score += 1 << 3;
629 if (candidate.password_element == observed_form_.password_element)
630 score += 1 << 2;
631 if (candidate.submit_element == observed_form_.submit_element)
632 score += 1 << 1;
633 if (candidate.username_element == observed_form_.username_element)
634 score += 1 << 0;
637 return score;
640 void PasswordFormManager::SubmitPassed() {
641 submit_result_ = kSubmitResultPassed;
644 void PasswordFormManager::SubmitFailed() {
645 submit_result_ = kSubmitResultFailed;