| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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 "components/password_manager/core/browser/password_form_manager.h" | 5 #include "components/password_manager/core/browser/password_form_manager.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <set> | 8 #include <set> |
| 9 | 9 |
| 10 #include "base/metrics/histogram.h" | 10 #include "base/metrics/histogram.h" |
| (...skipping 366 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 377 } | 377 } |
| 378 | 378 |
| 379 // Do not autofill on sign-up or change password forms (until we have some | 379 // Do not autofill on sign-up or change password forms (until we have some |
| 380 // working change password functionality). | 380 // working change password functionality). |
| 381 if (!observed_form_.new_password_element.empty()) { | 381 if (!observed_form_.new_password_element.empty()) { |
| 382 if (logger) | 382 if (logger) |
| 383 logger->LogMessage(Logger::STRING_FORM_NOT_AUTOFILLED); | 383 logger->LogMessage(Logger::STRING_FORM_NOT_AUTOFILLED); |
| 384 client_->AutofillResultsComputed(); | 384 client_->AutofillResultsComputed(); |
| 385 // There is no point in looking for the credentials in the store when they | 385 // There is no point in looking for the credentials in the store when they |
| 386 // won't be autofilled, so pretend there were none. | 386 // won't be autofilled, so pretend there were none. |
| 387 std::vector<autofill::PasswordForm*> dummy_results; | 387 OnGetPasswordStoreResults(ScopedVector<autofill::PasswordForm>()); |
| 388 OnGetPasswordStoreResults(dummy_results); | |
| 389 return; | 388 return; |
| 390 } | 389 } |
| 391 | 390 |
| 392 PasswordStore* password_store = client_->GetPasswordStore(); | 391 PasswordStore* password_store = client_->GetPasswordStore(); |
| 393 if (!password_store) { | 392 if (!password_store) { |
| 394 if (logger) | 393 if (logger) |
| 395 logger->LogMessage(Logger::STRING_NO_STORE); | 394 logger->LogMessage(Logger::STRING_NO_STORE); |
| 396 NOTREACHED(); | 395 NOTREACHED(); |
| 397 return; | 396 return; |
| 398 } | 397 } |
| 399 password_store->GetLogins(observed_form_, prompt_policy, this); | 398 password_store->GetLogins(observed_form_, prompt_policy, this); |
| 400 } | 399 } |
| 401 | 400 |
| 402 bool PasswordFormManager::HasCompletedMatching() const { | 401 bool PasswordFormManager::HasCompletedMatching() const { |
| 403 return state_ == POST_MATCHING_PHASE; | 402 return state_ == POST_MATCHING_PHASE; |
| 404 } | 403 } |
| 405 | 404 |
| 406 bool PasswordFormManager::IsIgnorableChangePasswordForm() const { | 405 bool PasswordFormManager::IsIgnorableChangePasswordForm() const { |
| 407 bool is_change_password_form = !observed_form_.new_password_element.empty() && | 406 bool is_change_password_form = !observed_form_.new_password_element.empty() && |
| 408 !observed_form_.password_element.empty(); | 407 !observed_form_.password_element.empty(); |
| 409 bool is_username_certainly_correct = observed_form_.username_marked_by_site; | 408 bool is_username_certainly_correct = observed_form_.username_marked_by_site; |
| 410 return is_change_password_form && !is_username_certainly_correct; | 409 return is_change_password_form && !is_username_certainly_correct; |
| 411 } | 410 } |
| 412 | 411 |
| 413 void PasswordFormManager::OnRequestDone( | 412 void PasswordFormManager::OnRequestDone( |
| 414 const std::vector<PasswordForm*>& logins_result) { | 413 ScopedVector<PasswordForm> logins_result) { |
| 415 // Note that the result gets deleted after this call completes, but we own | 414 const size_t logins_result_size = logins_result.size(); |
| 416 // the PasswordForm objects pointed to by the result vector, thus we keep | |
| 417 // copies to a minimum here. | |
| 418 | 415 |
| 419 scoped_ptr<BrowserSavePasswordProgressLogger> logger; | 416 scoped_ptr<BrowserSavePasswordProgressLogger> logger; |
| 420 if (client_->IsLoggingActive()) { | 417 if (client_->IsLoggingActive()) { |
| 421 logger.reset(new BrowserSavePasswordProgressLogger(client_)); | 418 logger.reset(new BrowserSavePasswordProgressLogger(client_)); |
| 422 logger->LogMessage(Logger::STRING_ON_REQUEST_DONE_METHOD); | 419 logger->LogMessage(Logger::STRING_ON_REQUEST_DONE_METHOD); |
| 423 } | 420 } |
| 424 | 421 |
| 425 int best_score = 0; | 422 int best_score = 0; |
| 426 // These credentials will be in the final result regardless of score. | 423 // These credentials will be in the final result regardless of score. |
| 427 std::vector<PasswordForm> credentials_to_keep; | 424 ScopedVector<PasswordForm> credentials_to_keep; |
| 428 for (size_t i = 0; i < logins_result.size(); i++) { | 425 for (auto& login_ptr : logins_result) { |
| 429 if (ShouldIgnoreResult(*logins_result[i])) { | 426 scoped_ptr<PasswordForm> login(login_ptr); |
| 430 delete logins_result[i]; | 427 login_ptr = nullptr; |
| 428 // A stable handle to the form while the owners change. |
| 429 const PasswordForm& const_login(*login); |
| 430 |
| 431 if (ShouldIgnoreResult(const_login)) |
| 431 continue; | 432 continue; |
| 432 } | 433 |
| 433 // Score and update best matches. | 434 // Score and update best matches. |
| 434 int current_score = ScoreResult(*logins_result[i]); | 435 int current_score = ScoreResult(const_login); |
| 435 // This check is here so we can append empty path matches in the event | |
| 436 // they don't score as high as others and aren't added to best_matches_. | |
| 437 // This is most commonly imported firefox logins. We skip blacklisted | |
| 438 // ones because clearly we don't want to autofill them, and secondly | |
| 439 // because they only mean something when we have no other matches already | |
| 440 // saved in Chrome - in which case they'll make it through the regular | |
| 441 // scoring flow below by design. Note signon_realm == origin implies empty | |
| 442 // path logins_result, since signon_realm is a prefix of origin for HTML | |
| 443 // password forms. | |
| 444 // TODO(timsteele): Bug 1269400. We probably should do something more | |
| 445 // elegant for any shorter-path match instead of explicitly handling empty | |
| 446 // path matches. | |
| 447 if ((observed_form_.scheme == PasswordForm::SCHEME_HTML) && | 436 if ((observed_form_.scheme == PasswordForm::SCHEME_HTML) && |
| 448 (observed_form_.signon_realm == logins_result[i]->origin.spec()) && | 437 (observed_form_.signon_realm == const_login.origin.spec()) && |
| 449 (current_score > 0) && (!logins_result[i]->blacklisted_by_user)) { | 438 (current_score > 0) && (!const_login.blacklisted_by_user)) { |
| 450 credentials_to_keep.push_back(*logins_result[i]); | 439 // This check is here so we can append empty path matches in the event |
| 440 // they don't score as high as others and aren't added to best_matches_. |
| 441 // This is most commonly imported firefox logins. We skip blacklisted |
| 442 // ones because clearly we don't want to autofill them, and secondly |
| 443 // because they only mean something when we have no other matches already |
| 444 // saved in Chrome - in which case they'll make it through the regular |
| 445 // scoring flow below by design. Note signon_realm == origin implies empty |
| 446 // path logins_result, since signon_realm is a prefix of origin for HTML |
| 447 // password forms. |
| 448 // TODO(timsteele): Bug 1269400. We probably should do something more |
| 449 // elegant for any shorter-path match instead of explicitly handling empty |
| 450 // path matches. |
| 451 credentials_to_keep.push_back(login.release()); |
| 452 } else if (const_login.type == PasswordForm::TYPE_GENERATED) { |
| 453 // Always keep generated passwords as part of the result set. If a user |
| 454 // generates a password on a signup form, it should show on a login form |
| 455 // even if they have a previous login saved. |
| 456 // TODO(gcasto): We don't want to cut credentials that were saved on |
| 457 // signup forms even if they weren't generated, but currently it's hard to |
| 458 // distinguish between those forms and two different login forms on the |
| 459 // same domain. Filed http://crbug.com/294468 to look into this. |
| 460 credentials_to_keep.push_back(login.release()); |
| 451 } | 461 } |
| 452 | 462 |
| 453 // Always keep generated passwords as part of the result set. If a user | 463 // End of exceptions, only interested in good-scoring candidates since now. |
| 454 // generates a password on a signup form, it should show on a login form | |
| 455 // even if they have a previous login saved. | |
| 456 // TODO(gcasto): We don't want to cut credentials that were saved on signup | |
| 457 // forms even if they weren't generated, but currently it's hard to | |
| 458 // distinguish between those forms and two different login forms on the | |
| 459 // same domain. Filed http://crbug.com/294468 to look into this. | |
| 460 if (logins_result[i]->type == PasswordForm::TYPE_GENERATED) | |
| 461 credentials_to_keep.push_back(*logins_result[i]); | |
| 462 | |
| 463 if (current_score < best_score) { | 464 if (current_score < best_score) { |
| 464 delete logins_result[i]; | |
| 465 continue; | 465 continue; |
| 466 } else if (current_score == best_score) { |
| 467 auto& best_match = best_matches_[const_login.username_value]; |
| 468 if (best_match == preferred_match_) |
| 469 preferred_match_ = nullptr; |
| 470 delete best_match; |
| 471 best_match = login.release(); |
| 472 } else { // current_score > best_score |
| 473 best_score = current_score; |
| 474 preferred_match_ = nullptr; // Don't delete, its owned by best_matches_. |
| 475 STLDeleteValues(&best_matches_); // |login| tops the previous matches. |
| 476 best_matches_[const_login.username_value] = login.release(); |
| 466 } | 477 } |
| 467 if (current_score == best_score) { | 478 preferred_match_ = const_login.preferred ? &const_login : preferred_match_; |
| 468 PasswordForm* old_form = best_matches_[logins_result[i]->username_value]; | |
| 469 if (old_form) { | |
| 470 if (preferred_match_ == old_form) | |
| 471 preferred_match_ = NULL; | |
| 472 delete old_form; | |
| 473 } | |
| 474 best_matches_[logins_result[i]->username_value] = logins_result[i]; | |
| 475 } else if (current_score > best_score) { | |
| 476 best_score = current_score; | |
| 477 // This new login has a better score than all those up to this point | |
| 478 // Note 'this' owns all the PasswordForms in best_matches_. | |
| 479 STLDeleteValues(&best_matches_); | |
| 480 preferred_match_ = NULL; // Don't delete, its owned by best_matches_. | |
| 481 best_matches_[logins_result[i]->username_value] = logins_result[i]; | |
| 482 } | |
| 483 preferred_match_ = | |
| 484 logins_result[i]->preferred ? logins_result[i] : preferred_match_; | |
| 485 } | 479 } |
| 480 logins_result.weak_clear(); // It contains just nulls, speed up destruction. |
| 486 | 481 |
| 487 client_->AutofillResultsComputed(); | 482 client_->AutofillResultsComputed(); |
| 488 | 483 |
| 489 // TODO(gcasto): Change this to check that best_matches_ is empty. This should | 484 // TODO(gcasto): Change this to check that best_matches_ is empty. This should |
| 490 // be equivalent for the moment, but it's less clear and may not be | 485 // be equivalent for the moment, but it's less clear and may not be |
| 491 // equivalent in the future. | 486 // equivalent in the future. |
| 492 if (best_score <= 0) { | 487 if (best_score <= 0) { |
| 493 if (logger) | 488 if (logger) |
| 494 logger->LogNumber(Logger::STRING_BEST_SCORE, best_score); | 489 logger->LogNumber(Logger::STRING_BEST_SCORE, best_score); |
| 495 return; | 490 return; |
| 496 } | 491 } |
| 497 | 492 |
| 498 for (std::vector<PasswordForm>::const_iterator it = | 493 for (auto& form : credentials_to_keep) { |
| 499 credentials_to_keep.begin(); | |
| 500 it != credentials_to_keep.end(); ++it) { | |
| 501 // If we don't already have a result with the same username, add the | 494 // If we don't already have a result with the same username, add the |
| 502 // lower-scored match (if it had equal score it would already be in | 495 // lower-scored match (if it had equal score it would already be in |
| 503 // best_matches_). | 496 // best_matches_). |
| 504 if (best_matches_.find(it->username_value) == best_matches_.end()) | 497 auto& corresponding_best_match = best_matches_[form->username_value]; |
| 505 best_matches_[it->username_value] = new PasswordForm(*it); | 498 if (!corresponding_best_match) { |
| 499 corresponding_best_match = form; |
| 500 form = nullptr; |
| 501 } |
| 506 } | 502 } |
| 507 | 503 |
| 508 UMA_HISTOGRAM_COUNTS("PasswordManager.NumPasswordsNotShown", | 504 UMA_HISTOGRAM_COUNTS("PasswordManager.NumPasswordsNotShown", |
| 509 logins_result.size() - best_matches_.size()); | 505 logins_result_size - best_matches_.size()); |
| 510 | 506 |
| 511 // It is possible we have at least one match but have no preferred_match_, | 507 // It is possible we have at least one match but have no preferred_match_, |
| 512 // because a user may have chosen to 'Forget' the preferred match. So we | 508 // because a user may have chosen to 'Forget' the preferred match. So we |
| 513 // just pick the first one and whichever the user selects for submit will | 509 // just pick the first one and whichever the user selects for submit will |
| 514 // be saved as preferred. | 510 // be saved as preferred. |
| 515 DCHECK(!best_matches_.empty()); | 511 DCHECK(!best_matches_.empty()); |
| 516 if (!preferred_match_) | 512 if (!preferred_match_) |
| 517 preferred_match_ = best_matches_.begin()->second; | 513 preferred_match_ = best_matches_.begin()->second; |
| 518 | 514 |
| 519 // Check to see if the user told us to ignore this site in the past. | 515 // Check to see if the user told us to ignore this site in the past. |
| (...skipping 29 matching lines...) Expand all Loading... |
| 549 preferred_match_->IsPublicSuffixMatch(); | 545 preferred_match_->IsPublicSuffixMatch(); |
| 550 if (wait_for_username) | 546 if (wait_for_username) |
| 551 manager_action_ = kManagerActionNone; | 547 manager_action_ = kManagerActionNone; |
| 552 else | 548 else |
| 553 manager_action_ = kManagerActionAutofilled; | 549 manager_action_ = kManagerActionAutofilled; |
| 554 password_manager_->Autofill(driver.get(), observed_form_, best_matches_, | 550 password_manager_->Autofill(driver.get(), observed_form_, best_matches_, |
| 555 *preferred_match_, wait_for_username); | 551 *preferred_match_, wait_for_username); |
| 556 } | 552 } |
| 557 | 553 |
| 558 void PasswordFormManager::OnGetPasswordStoreResults( | 554 void PasswordFormManager::OnGetPasswordStoreResults( |
| 559 const std::vector<autofill::PasswordForm*>& results) { | 555 ScopedVector<PasswordForm> results) { |
| 560 DCHECK_EQ(state_, MATCHING_PHASE); | 556 DCHECK_EQ(state_, MATCHING_PHASE); |
| 561 | 557 |
| 562 scoped_ptr<BrowserSavePasswordProgressLogger> logger; | 558 scoped_ptr<BrowserSavePasswordProgressLogger> logger; |
| 563 if (client_->IsLoggingActive()) { | 559 if (client_->IsLoggingActive()) { |
| 564 logger.reset(new BrowserSavePasswordProgressLogger(client_)); | 560 logger.reset(new BrowserSavePasswordProgressLogger(client_)); |
| 565 logger->LogMessage(Logger::STRING_ON_GET_STORE_RESULTS_METHOD); | 561 logger->LogMessage(Logger::STRING_ON_GET_STORE_RESULTS_METHOD); |
| 566 logger->LogNumber(Logger::STRING_NUMBER_RESULTS, results.size()); | 562 logger->LogNumber(Logger::STRING_NUMBER_RESULTS, results.size()); |
| 567 } | 563 } |
| 568 | 564 |
| 569 if (!results.empty()) | 565 if (!results.empty()) |
| 570 OnRequestDone(results); | 566 OnRequestDone(results.Pass()); |
| 571 state_ = POST_MATCHING_PHASE; | 567 state_ = POST_MATCHING_PHASE; |
| 572 | 568 |
| 573 if (manager_action_ != kManagerActionBlacklisted) { | 569 if (manager_action_ != kManagerActionBlacklisted) { |
| 574 for (auto const& driver : drivers_) | 570 for (auto const& driver : drivers_) |
| 575 ProcessFrame(driver); | 571 ProcessFrame(driver); |
| 576 } | 572 } |
| 577 drivers_.clear(); | 573 drivers_.clear(); |
| 578 } | 574 } |
| 579 | 575 |
| 580 bool PasswordFormManager::ShouldIgnoreResult(const PasswordForm& form) const { | 576 bool PasswordFormManager::ShouldIgnoreResult(const PasswordForm& form) const { |
| (...skipping 281 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 862 LogPasswordGenerationSubmissionEvent(PASSWORD_SUBMITTED); | 858 LogPasswordGenerationSubmissionEvent(PASSWORD_SUBMITTED); |
| 863 } | 859 } |
| 864 | 860 |
| 865 void PasswordFormManager::SubmitFailed() { | 861 void PasswordFormManager::SubmitFailed() { |
| 866 submit_result_ = kSubmitResultFailed; | 862 submit_result_ = kSubmitResultFailed; |
| 867 if (has_generated_password_) | 863 if (has_generated_password_) |
| 868 LogPasswordGenerationSubmissionEvent(PASSWORD_SUBMISSION_FAILED); | 864 LogPasswordGenerationSubmissionEvent(PASSWORD_SUBMISSION_FAILED); |
| 869 } | 865 } |
| 870 | 866 |
| 871 } // namespace password_manager | 867 } // namespace password_manager |
| OLD | NEW |