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"
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_store.h"
14 #include "chrome/browser/password_manager/password_store_factory.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "components/autofill/content/browser/autofill_driver_impl.h"
17 #include "components/autofill/content/common/autofill_messages.h"
18 #include "components/autofill/core/browser/form_structure.h"
19 #include "components/autofill/core/browser/validation.h"
20 #include "components/autofill/core/common/password_form.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/web_contents.h"
24 using autofill::FormStructure
;
25 using autofill::PasswordForm
;
26 using autofill::PasswordFormMap
;
29 PasswordFormManager::PasswordFormManager(Profile
* profile
,
30 PasswordManager
* password_manager
,
31 content::WebContents
* web_contents
,
32 const PasswordForm
& observed_form
,
34 : best_matches_deleter_(&best_matches_
),
35 observed_form_(observed_form
),
37 has_generated_password_(false),
38 password_manager_(password_manager
),
39 preferred_match_(NULL
),
40 state_(PRE_MATCHING_PHASE
),
42 web_contents_(web_contents
),
43 manager_action_(kManagerActionNone
),
44 user_action_(kUserActionNone
),
45 submit_result_(kSubmitResultNotSubmitted
) {
47 if (observed_form_
.origin
.is_valid())
48 base::SplitString(observed_form_
.origin
.path(), '/', &form_path_tokens_
);
49 observed_form_
.ssl_valid
= ssl_valid
;
52 PasswordFormManager::~PasswordFormManager() {
53 UMA_HISTOGRAM_ENUMERATION("PasswordManager.ActionsTakenWithPsl",
58 int PasswordFormManager::GetActionsTaken() {
59 return user_action_
+ kUserActionMax
* (manager_action_
+
60 kManagerActionMax
* submit_result_
);
63 // TODO(timsteele): use a hash of some sort in the future?
64 bool PasswordFormManager::DoesManage(const PasswordForm
& form
,
65 ActionMatch action_match
) const {
66 if (form
.scheme
!= PasswordForm::SCHEME_HTML
)
67 return observed_form_
.signon_realm
== form
.signon_realm
;
70 // At a minimum, username and password element must match.
71 if (!((form
.username_element
== observed_form_
.username_element
) &&
72 (form
.password_element
== observed_form_
.password_element
))) {
76 // When action match is required, the action URL must match, but
77 // the form is allowed to have an empty action URL (See bug 1107719).
78 // Otherwise ignore action URL, this is to allow saving password form with
79 // dynamically changed action URL (See bug 27246).
80 if (form
.action
.is_valid() && (form
.action
!= observed_form_
.action
)) {
81 if (action_match
== ACTION_MATCH_REQUIRED
)
85 // If this is a replay of the same form in the case a user entered an invalid
86 // password, the origin of the new form may equal the action of the "first"
88 if (!((form
.origin
== observed_form_
.origin
) ||
89 (form
.origin
== observed_form_
.action
))) {
90 if (form
.origin
.SchemeIsSecure() &&
91 !observed_form_
.origin
.SchemeIsSecure()) {
92 // Compare origins, ignoring scheme. There is no easy way to do this
93 // with GURL because clearing the scheme would result in an invalid url.
94 // This is for some sites (such as Hotmail) that begin on an http page and
95 // head to https for the retry when password was invalid.
96 std::string::const_iterator after_scheme1
= form
.origin
.spec().begin() +
97 form
.origin
.scheme().length();
98 std::string::const_iterator after_scheme2
=
99 observed_form_
.origin
.spec().begin() +
100 observed_form_
.origin
.scheme().length();
101 return std::search(after_scheme1
,
102 form
.origin
.spec().end(),
104 observed_form_
.origin
.spec().end())
105 != form
.origin
.spec().end();
112 bool PasswordFormManager::IsBlacklisted() {
113 DCHECK_EQ(state_
, POST_MATCHING_PHASE
);
114 if (preferred_match_
&& preferred_match_
->blacklisted_by_user
)
119 void PasswordFormManager::PermanentlyBlacklist() {
120 DCHECK_EQ(state_
, POST_MATCHING_PHASE
);
122 // Configure the form about to be saved for blacklist status.
123 pending_credentials_
.preferred
= true;
124 pending_credentials_
.blacklisted_by_user
= true;
125 pending_credentials_
.username_value
.clear();
126 pending_credentials_
.password_value
.clear();
128 // Retroactively forget existing matches for this form, so we NEVER prompt or
129 // autofill it again.
130 int num_passwords_deleted
= 0;
131 if (!best_matches_
.empty()) {
132 PasswordFormMap::const_iterator iter
;
133 PasswordStore
* password_store
= PasswordStoreFactory::GetForProfile(
134 profile_
, Profile::EXPLICIT_ACCESS
).get();
135 if (!password_store
) {
139 for (iter
= best_matches_
.begin(); iter
!= best_matches_
.end(); ++iter
) {
140 // We want to remove existing matches for this form so that the exact
141 // origin match with |blackisted_by_user == true| is the only result that
142 // shows up in the future for this origin URL. However, we don't want to
143 // delete logins that were actually saved on a different page (hence with
144 // different origin URL) and just happened to match this form because of
145 // the scoring algorithm. See bug 1204493.
146 if (iter
->second
->origin
== observed_form_
.origin
) {
147 password_store
->RemoveLogin(*iter
->second
);
148 ++num_passwords_deleted
;
153 UMA_HISTOGRAM_COUNTS("PasswordManager.NumPasswordsDeletedWhenBlacklisting",
154 num_passwords_deleted
);
156 // Save the pending_credentials_ entry marked as blacklisted.
157 SaveAsNewLogin(false);
160 bool PasswordFormManager::IsNewLogin() {
161 DCHECK_EQ(state_
, POST_MATCHING_PHASE
);
162 return is_new_login_
;
165 bool PasswordFormManager::IsPendingCredentialsPublicSuffixMatch() {
166 return pending_credentials_
.IsPublicSuffixMatch();
169 void PasswordFormManager::SetHasGeneratedPassword() {
170 has_generated_password_
= true;
173 bool PasswordFormManager::HasGeneratedPassword() {
174 // This check is permissive, as the user may have generated a password and
175 // then edited it in the form itself. However, even in this case the user
176 // has already given consent, so we treat these cases the same.
177 return has_generated_password_
;
180 bool PasswordFormManager::HasValidPasswordForm() {
181 DCHECK_EQ(state_
, POST_MATCHING_PHASE
);
182 // Non-HTML password forms (primarily HTTP and FTP autentication)
183 // do not contain username_element and password_element values.
184 if (observed_form_
.scheme
!= PasswordForm::SCHEME_HTML
)
186 return !observed_form_
.username_element
.empty() &&
187 !observed_form_
.password_element
.empty();
190 void PasswordFormManager::ProvisionallySave(
191 const PasswordForm
& credentials
,
192 OtherPossibleUsernamesAction action
) {
193 DCHECK_EQ(state_
, POST_MATCHING_PHASE
);
194 DCHECK(DoesManage(credentials
, ACTION_MATCH_NOT_REQUIRED
));
196 // Make sure the important fields stay the same as the initially observed or
197 // autofilled ones, as they may have changed if the user experienced a login
199 // Look for these credentials in the list containing auto-fill entries.
200 PasswordFormMap::const_iterator it
=
201 best_matches_
.find(credentials
.username_value
);
202 if (it
!= best_matches_
.end()) {
203 // The user signed in with a login we autofilled.
204 pending_credentials_
= *it
->second
;
206 // Public suffix matches should always be new logins, since we want to store
207 // them so they can automatically be filled in later.
208 is_new_login_
= IsPendingCredentialsPublicSuffixMatch();
210 user_action_
= kUserActionChoosePslMatch
;
212 // Check to see if we're using a known username but a new password.
213 if (pending_credentials_
.password_value
!= credentials
.password_value
)
214 user_action_
= kUserActionOverride
;
215 } else if (action
== ALLOW_OTHER_POSSIBLE_USERNAMES
&&
216 UpdatePendingCredentialsIfOtherPossibleUsername(
217 credentials
.username_value
)) {
218 // |pending_credentials_| is now set. Note we don't update
219 // |pending_credentials_.username_value| to |credentials.username_value|
220 // yet because we need to keep the original username to modify the stored
222 selected_username_
= credentials
.username_value
;
223 is_new_login_
= false;
225 // User typed in a new, unknown username.
226 user_action_
= kUserActionOverride
;
227 pending_credentials_
= observed_form_
;
228 pending_credentials_
.username_value
= credentials
.username_value
;
229 pending_credentials_
.other_possible_usernames
=
230 credentials
.other_possible_usernames
;
233 pending_credentials_
.action
= credentials
.action
;
234 // If the user selected credentials we autofilled from a PasswordForm
235 // that contained no action URL (IE6/7 imported passwords, for example),
236 // bless it with the action URL from the observed form. See bug 1107719.
237 if (pending_credentials_
.action
.is_empty())
238 pending_credentials_
.action
= observed_form_
.action
;
240 pending_credentials_
.password_value
= credentials
.password_value
;
241 pending_credentials_
.preferred
= credentials
.preferred
;
243 if (has_generated_password_
)
244 pending_credentials_
.type
= PasswordForm::TYPE_GENERATED
;
247 void PasswordFormManager::Save() {
248 DCHECK_EQ(state_
, POST_MATCHING_PHASE
);
249 DCHECK(!profile_
->IsOffTheRecord());
252 SaveAsNewLogin(true);
257 void PasswordFormManager::FetchMatchingLoginsFromPasswordStore(
258 PasswordStore::AuthorizationPromptPolicy prompt_policy
) {
259 DCHECK_EQ(state_
, PRE_MATCHING_PHASE
);
260 state_
= MATCHING_PHASE
;
261 PasswordStore
* password_store
= PasswordStoreFactory::GetForProfile(
262 profile_
, Profile::EXPLICIT_ACCESS
).get();
263 if (!password_store
) {
267 password_store
->GetLogins(observed_form_
, prompt_policy
, this);
270 bool PasswordFormManager::HasCompletedMatching() {
271 return state_
== POST_MATCHING_PHASE
;
274 void PasswordFormManager::OnRequestDone(
275 const std::vector
<PasswordForm
*>& logins_result
) {
276 // Note that the result gets deleted after this call completes, but we own
277 // the PasswordForm objects pointed to by the result vector, thus we keep
278 // copies to a minimum here.
281 // These credentials will be in the final result regardless of score.
282 std::vector
<PasswordForm
> credentials_to_keep
;
283 for (size_t i
= 0; i
< logins_result
.size(); i
++) {
284 if (IgnoreResult(*logins_result
[i
])) {
285 delete logins_result
[i
];
288 // Score and update best matches.
289 int current_score
= ScoreResult(*logins_result
[i
]);
290 // This check is here so we can append empty path matches in the event
291 // they don't score as high as others and aren't added to best_matches_.
292 // This is most commonly imported firefox logins. We skip blacklisted
293 // ones because clearly we don't want to autofill them, and secondly
294 // because they only mean something when we have no other matches already
295 // saved in Chrome - in which case they'll make it through the regular
296 // scoring flow below by design. Note signon_realm == origin implies empty
297 // path logins_result, since signon_realm is a prefix of origin for HTML
299 // TODO(timsteele): Bug 1269400. We probably should do something more
300 // elegant for any shorter-path match instead of explicitly handling empty
302 if ((observed_form_
.scheme
== PasswordForm::SCHEME_HTML
) &&
303 (observed_form_
.signon_realm
== logins_result
[i
]->origin
.spec()) &&
304 (current_score
> 0) && (!logins_result
[i
]->blacklisted_by_user
)) {
305 credentials_to_keep
.push_back(*logins_result
[i
]);
308 // Always keep generated passwords as part of the result set. If a user
309 // generates a password on a signup form, it should show on a login form
310 // even if they have a previous login saved.
311 // TODO(gcasto): We don't want to cut credentials that were saved on signup
312 // forms even if they weren't generated, but currently it's hard to
313 // distinguish between those forms and two different login forms on the
314 // same domain. Filed http://crbug.com/294468 to look into this.
315 if (logins_result
[i
]->type
== PasswordForm::TYPE_GENERATED
)
316 credentials_to_keep
.push_back(*logins_result
[i
]);
318 if (current_score
< best_score
) {
319 delete logins_result
[i
];
322 if (current_score
== best_score
) {
323 best_matches_
[logins_result
[i
]->username_value
] = logins_result
[i
];
324 } else if (current_score
> best_score
) {
325 best_score
= current_score
;
326 // This new login has a better score than all those up to this point
327 // Note 'this' owns all the PasswordForms in best_matches_.
328 STLDeleteValues(&best_matches_
);
329 best_matches_
.clear();
330 preferred_match_
= NULL
; // Don't delete, its owned by best_matches_.
331 best_matches_
[logins_result
[i
]->username_value
] = logins_result
[i
];
333 preferred_match_
= logins_result
[i
]->preferred
? logins_result
[i
]
336 // We're done matching now.
337 state_
= POST_MATCHING_PHASE
;
339 if (best_score
<= 0) {
343 for (std::vector
<PasswordForm
>::const_iterator it
=
344 credentials_to_keep
.begin();
345 it
!= credentials_to_keep
.end(); ++it
) {
346 // If we don't already have a result with the same username, add the
347 // lower-scored match (if it had equal score it would already be in
349 if (best_matches_
.find(it
->username_value
) == best_matches_
.end())
350 best_matches_
[it
->username_value
] = new PasswordForm(*it
);
353 UMA_HISTOGRAM_COUNTS("PasswordManager.NumPasswordsNotShown",
354 logins_result
.size() - best_matches_
.size());
356 // It is possible we have at least one match but have no preferred_match_,
357 // because a user may have chosen to 'Forget' the preferred match. So we
358 // just pick the first one and whichever the user selects for submit will
359 // be saved as preferred.
360 DCHECK(!best_matches_
.empty());
361 if (!preferred_match_
)
362 preferred_match_
= best_matches_
.begin()->second
;
364 // Check to see if the user told us to ignore this site in the past.
365 if (preferred_match_
->blacklisted_by_user
) {
366 manager_action_
= kManagerActionBlacklisted
;
370 // If not blacklisted, send a message to allow password generation.
371 SendNotBlacklistedToRenderer();
373 // Proceed to autofill.
374 // Note that we provide the choices but don't actually prefill a value if:
375 // (1) we are in Incognito mode, (2) the ACTION paths don't match,
376 // or (3) if it matched using public suffix domain matching.
377 bool wait_for_username
=
378 profile_
->IsOffTheRecord() ||
379 observed_form_
.action
.GetWithEmptyPath() !=
380 preferred_match_
->action
.GetWithEmptyPath() ||
381 preferred_match_
->IsPublicSuffixMatch();
382 if (wait_for_username
)
383 manager_action_
= kManagerActionNone
;
385 manager_action_
= kManagerActionAutofilled
;
386 password_manager_
->Autofill(observed_form_
, best_matches_
,
387 *preferred_match_
, wait_for_username
);
390 void PasswordFormManager::OnPasswordStoreRequestDone(
391 CancelableRequestProvider::Handle handle
,
392 const std::vector
<autofill::PasswordForm
*>& result
) {
393 // TODO(kaiwang): Remove this function.
397 void PasswordFormManager::OnGetPasswordStoreResults(
398 const std::vector
<autofill::PasswordForm
*>& results
) {
399 DCHECK_EQ(state_
, MATCHING_PHASE
);
401 if (results
.empty()) {
402 state_
= POST_MATCHING_PHASE
;
403 // No result means that we visit this site the first time so we don't need
404 // to check whether this site is blacklisted or not. Just send a message
405 // to allow password generation.
406 SendNotBlacklistedToRenderer();
409 OnRequestDone(results
);
412 bool PasswordFormManager::IgnoreResult(const PasswordForm
& form
) const {
413 // Ignore change password forms until we have some change password
415 if (observed_form_
.old_password_element
.length() != 0) {
418 // Don't match an invalid SSL form with one saved under secure
420 if (form
.ssl_valid
&& !observed_form_
.ssl_valid
) {
426 void PasswordFormManager::SaveAsNewLogin(bool reset_preferred_login
) {
427 DCHECK_EQ(state_
, POST_MATCHING_PHASE
);
428 DCHECK(IsNewLogin());
429 // The new_form is being used to sign in, so it is preferred.
430 DCHECK(pending_credentials_
.preferred
);
431 // new_form contains the same basic data as observed_form_ (because its the
432 // same form), but with the newly added credentials.
434 DCHECK(!profile_
->IsOffTheRecord());
436 PasswordStore
* password_store
= PasswordStoreFactory::GetForProfile(
437 profile_
, Profile::IMPLICIT_ACCESS
).get();
438 if (!password_store
) {
443 pending_credentials_
.date_created
= Time::Now();
444 SanitizePossibleUsernames(&pending_credentials_
);
445 password_store
->AddLogin(pending_credentials_
);
447 if (reset_preferred_login
) {
448 UpdatePreferredLoginState(password_store
);
452 void PasswordFormManager::SanitizePossibleUsernames(PasswordForm
* form
) {
453 // Remove any possible usernames that could be credit cards or SSN for privacy
454 // reasons. Also remove duplicates, both in other_possible_usernames and
455 // between other_possible_usernames and username_value.
456 std::set
<base::string16
> set
;
457 for (std::vector
<base::string16
>::iterator it
=
458 form
->other_possible_usernames
.begin();
459 it
!= form
->other_possible_usernames
.end(); ++it
) {
460 if (!autofill::IsValidCreditCardNumber(*it
) && !autofill::IsSSN(*it
))
463 set
.erase(form
->username_value
);
464 std::vector
<base::string16
> temp(set
.begin(), set
.end());
465 form
->other_possible_usernames
.swap(temp
);
468 void PasswordFormManager::UpdatePreferredLoginState(
469 PasswordStore
* password_store
) {
470 DCHECK(password_store
);
471 PasswordFormMap::iterator iter
;
472 for (iter
= best_matches_
.begin(); iter
!= best_matches_
.end(); iter
++) {
473 if (iter
->second
->username_value
!= pending_credentials_
.username_value
&&
474 iter
->second
->preferred
) {
475 // This wasn't the selected login but it used to be preferred.
476 iter
->second
->preferred
= false;
477 if (user_action_
== kUserActionNone
)
478 user_action_
= kUserActionChoose
;
479 password_store
->UpdateLogin(*iter
->second
);
484 void PasswordFormManager::UpdateLogin() {
485 DCHECK_EQ(state_
, POST_MATCHING_PHASE
);
486 DCHECK(preferred_match_
);
487 // If we're doing an Update, we either autofilled correctly and need to
488 // update the stats, or the user typed in a new password for autofilled
489 // username, or the user selected one of the non-preferred matches,
490 // thus requiring a swap of preferred bits.
491 DCHECK(!IsNewLogin() && pending_credentials_
.preferred
);
492 DCHECK(!profile_
->IsOffTheRecord());
494 PasswordStore
* password_store
= PasswordStoreFactory::GetForProfile(
495 profile_
, Profile::IMPLICIT_ACCESS
).get();
496 if (!password_store
) {
502 ++pending_credentials_
.times_used
;
504 // Check to see if this form is a candidate for password generation.
505 CheckForAccountCreationForm(pending_credentials_
, observed_form_
);
507 UpdatePreferredLoginState(password_store
);
509 // Remove alternate usernames. At this point we assume that we have found
510 // the right username.
511 pending_credentials_
.other_possible_usernames
.clear();
513 // Update the new preferred login.
514 if (!selected_username_
.empty()) {
515 // An other possible username is selected. We set this selected username
516 // as the real username. The PasswordStore API isn't designed to update
517 // username, so we delete the old credentials and add a new one instead.
518 password_store
->RemoveLogin(pending_credentials_
);
519 pending_credentials_
.username_value
= selected_username_
;
520 password_store
->AddLogin(pending_credentials_
);
521 } else if ((observed_form_
.scheme
== PasswordForm::SCHEME_HTML
) &&
522 (observed_form_
.origin
.spec().length() >
523 observed_form_
.signon_realm
.length()) &&
524 (observed_form_
.signon_realm
==
525 pending_credentials_
.origin
.spec())) {
526 // Note origin.spec().length > signon_realm.length implies the origin has a
527 // path, since signon_realm is a prefix of origin for HTML password forms.
529 // The user logged in successfully with one of our autofilled logins on a
530 // page with non-empty path, but the autofilled entry was initially saved/
531 // imported with an empty path. Rather than just mark this entry preferred,
532 // we create a more specific copy for this exact page and leave the "master"
533 // unchanged. This is to prevent the case where that master login is used
534 // on several sites (e.g site.com/a and site.com/b) but the user actually
535 // has a different preference on each site. For example, on /a, he wants the
536 // general empty-path login so it is flagged as preferred, but on /b he logs
537 // in with a different saved entry - we don't want to remove the preferred
538 // status of the former because upon return to /a it won't be the default-
540 // TODO(timsteele): Bug 1188626 - expire the master copies.
541 PasswordForm
copy(pending_credentials_
);
542 copy
.origin
= observed_form_
.origin
;
543 copy
.action
= observed_form_
.action
;
544 password_store
->AddLogin(copy
);
546 password_store
->UpdateLogin(pending_credentials_
);
550 bool PasswordFormManager::UpdatePendingCredentialsIfOtherPossibleUsername(
551 const base::string16
& username
) {
552 for (PasswordFormMap::const_iterator it
= best_matches_
.begin();
553 it
!= best_matches_
.end(); ++it
) {
554 for (size_t i
= 0; i
< it
->second
->other_possible_usernames
.size(); ++i
) {
555 if (it
->second
->other_possible_usernames
[i
] == username
) {
556 pending_credentials_
= *it
->second
;
564 void PasswordFormManager::CheckForAccountCreationForm(
565 const PasswordForm
& pending
, const PasswordForm
& observed
) {
566 // We check to see if the saved form_data is the same as the observed
567 // form_data, which should never be true for passwords saved on account
568 // creation forms. This check is only made the first time a password is used
569 // to cut down on false positives. Specifically a site may have multiple login
570 // forms with different markup, which might look similar to a signup form.
571 if (pending
.times_used
== 1) {
572 FormStructure
pending_structure(pending
.form_data
);
573 FormStructure
observed_structure(observed
.form_data
);
574 // Ignore |pending_structure| if its FormData has no fields. This is to
575 // weed out those credentials that were saved before FormData was added
576 // to PasswordForm. Even without this check, these FormStructure's won't
577 // be uploaded, but it makes it hard to see if we are encountering
578 // unexpected errors.
579 if (!pending
.form_data
.fields
.empty() &&
580 pending_structure
.FormSignature() !=
581 observed_structure
.FormSignature()) {
582 autofill::AutofillDriverImpl
* driver
=
583 autofill::AutofillDriverImpl::FromWebContents(web_contents_
);
584 if (driver
&& driver
->autofill_manager()) {
585 // Note that this doesn't guarantee that the upload succeeded, only that
586 // |pending.form_data| is considered uploadable.
587 bool success
= driver
->autofill_manager()->UploadPasswordGenerationForm(
589 UMA_HISTOGRAM_BOOLEAN("PasswordGeneration.UploadStarted", success
);
595 int PasswordFormManager::ScoreResult(const PasswordForm
& candidate
) const {
596 DCHECK_EQ(state_
, MATCHING_PHASE
);
597 // For scoring of candidate login data:
598 // The most important element that should match is the origin, followed by
599 // the action, the password name, the submit button name, and finally the
600 // username input field name.
601 // Exact origin match gives an addition of 64 (1 << 6) + # of matching url
603 // Partial match gives an addition of 32 (1 << 5) + # matching url dirs
604 // That way, a partial match cannot trump an exact match even if
605 // the partial one matches all other attributes (action, elements) (and
606 // regardless of the matching depth in the URL path).
607 // If public suffix origin match was not used, it gives an addition of
610 if (candidate
.origin
== observed_form_
.origin
) {
611 // This check is here for the most common case which
612 // is we have a single match in the db for the given host,
613 // so we don't generally need to walk the entire URL path (the else
615 score
+= (1 << 6) + static_cast<int>(form_path_tokens_
.size());
617 // Walk the origin URL paths one directory at a time to see how
618 // deep the two match.
619 std::vector
<std::string
> candidate_path_tokens
;
620 base::SplitString(candidate
.origin
.path(), '/', &candidate_path_tokens
);
622 size_t max_dirs
= std::min(form_path_tokens_
.size(),
623 candidate_path_tokens
.size());
624 while ((depth
< max_dirs
) && (form_path_tokens_
[depth
] ==
625 candidate_path_tokens
[depth
])) {
629 // do we have a partial match?
630 score
+= (depth
> 0) ? 1 << 5 : 0;
632 if (observed_form_
.scheme
== PasswordForm::SCHEME_HTML
) {
633 if (!candidate
.IsPublicSuffixMatch())
635 if (candidate
.action
== observed_form_
.action
)
637 if (candidate
.password_element
== observed_form_
.password_element
)
639 if (candidate
.submit_element
== observed_form_
.submit_element
)
641 if (candidate
.username_element
== observed_form_
.username_element
)
648 void PasswordFormManager::SubmitPassed() {
649 submit_result_
= kSubmitResultPassed
;
652 void PasswordFormManager::SubmitFailed() {
653 submit_result_
= kSubmitResultFailed
;
656 void PasswordFormManager::SendNotBlacklistedToRenderer() {
657 content::RenderViewHost
* host
= web_contents_
->GetRenderViewHost();
658 host
->Send(new AutofillMsg_FormNotBlacklisted(host
->GetRoutingID(),