diff options
author | zysxqn@google.com <zysxqn@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-19 17:52:21 +0000 |
---|---|---|
committer | zysxqn@google.com <zysxqn@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-19 17:52:21 +0000 |
commit | c3ebaa9ca4b5288448d6d5e5dee08f2aa68c0a52 (patch) | |
tree | 9a5ac0b11bb1df472971de1915a155823b818ce4 | |
parent | 8dfcccdcf5ead74a511c0139933bfc62a263f41c (diff) | |
download | chromium_src-c3ebaa9ca4b5288448d6d5e5dee08f2aa68c0a52.zip chromium_src-c3ebaa9ca4b5288448d6d5e5dee08f2aa68c0a52.tar.gz chromium_src-c3ebaa9ca4b5288448d6d5e5dee08f2aa68c0a52.tar.bz2 |
Check whether the users have blacklisted the site (i.e. "never save passwords") before we show the password generation bubble.
BUG=125413
TEST=PasswordGenerationTest,PasswordManagerTest,PasswordFormManagerTest
Review URL: https://chromiumcodereview.appspot.com/10545094
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@142987 0039d316-1c4b-4281-b951-d872f2087c98
9 files changed, 320 insertions, 45 deletions
diff --git a/chrome/browser/password_manager/password_form_manager.cc b/chrome/browser/password_manager/password_form_manager.cc index 13c942d..b9cc0bc 100644 --- a/chrome/browser/password_manager/password_form_manager.cc +++ b/chrome/browser/password_manager/password_form_manager.cc @@ -13,6 +13,8 @@ #include "chrome/browser/password_manager/password_store.h" #include "chrome/browser/password_manager/password_store_factory.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/common/autofill_messages.h" +#include "content/public/browser/render_view_host.h" #include "webkit/forms/password_form_dom_manager.h" using base::Time; @@ -21,6 +23,7 @@ using webkit::forms::PasswordFormMap; PasswordFormManager::PasswordFormManager(Profile* profile, PasswordManager* password_manager, + content::RenderViewHost* host, const PasswordForm& observed_form, bool ssl_valid) : best_matches_deleter_(&best_matches_), @@ -32,6 +35,7 @@ PasswordFormManager::PasswordFormManager(Profile* profile, preferred_match_(NULL), state_(PRE_MATCHING_PHASE), profile_(profile), + host_(host), manager_action_(kManagerActionNone), user_action_(kUserActionNone), submit_result_(kSubmitResultNotSubmitted) { @@ -306,6 +310,9 @@ void PasswordFormManager::OnRequestDone(int handle, return; } + // If not blacklisted, send a message to allow password generation. + SendNotBlacklistedToRenderer(); + // Proceed to autofill. // Note that we provide the choices but don't actually prefill a value if // either: (1) we are in Incognito mode, or (2) the ACTION paths don't match. @@ -329,6 +336,10 @@ void PasswordFormManager::OnPasswordStoreRequestDone( if (result.empty()) { state_ = POST_MATCHING_PHASE; + // No result means that we visit this site the first time so we don't need + // to check whether this site is blacklisted or not. Just send a message + // to allow password generation. + SendNotBlacklistedToRenderer(); return; } @@ -494,3 +505,8 @@ void PasswordFormManager::SubmitPassed() { void PasswordFormManager::SubmitFailed() { submit_result_ = kSubmitResultFailed; } + +void PasswordFormManager::SendNotBlacklistedToRenderer() { + host_->Send(new AutofillMsg_FormNotBlacklisted(host_->GetRoutingID(), + observed_form_)); +} diff --git a/chrome/browser/password_manager/password_form_manager.h b/chrome/browser/password_manager/password_form_manager.h index 10c0d77..8de86a1 100644 --- a/chrome/browser/password_manager/password_form_manager.h +++ b/chrome/browser/password_manager/password_form_manager.h @@ -15,6 +15,10 @@ #include "chrome/browser/password_manager/password_store_consumer.h" #include "webkit/forms/password_form.h" +namespace content { +class RenderViewHost; +} // namespace content + class PasswordManager; class PasswordStore; class Profile; @@ -31,6 +35,7 @@ class PasswordFormManager : public PasswordStoreConsumer { // used to filter login results from database. PasswordFormManager(Profile* profile, PasswordManager* password_manager, + content::RenderViewHost* host, const webkit::forms::PasswordForm& observed_form, bool ssl_valid); virtual ~PasswordFormManager(); @@ -178,6 +183,11 @@ class PasswordFormManager : public PasswordStoreConsumer { // UMA. int GetActionsTaken(); + // Informs the renderer that the user has not blacklisted observed_form_ by + // choosing "never save passwords for this site". This is used by the password + // generation manager to deside whether to show the password generation icon. + virtual void SendNotBlacklistedToRenderer(); + // Set of PasswordForms from the DB that best match the form // being managed by this. Use a map instead of vector, because we most // frequently require lookups by username value in IsNewLogin. @@ -233,6 +243,9 @@ class PasswordFormManager : public PasswordStoreConsumer { // The profile from which we get the PasswordStore. Profile* profile_; + // Render view host for sending messages to the corresponding renderer. + content::RenderViewHost* host_; + // These three fields record the "ActionsTaken" by the browser and // the user with this form, and the result. They are combined and // recorded in UMA when the manager is destroyed. diff --git a/chrome/browser/password_manager/password_form_manager_unittest.cc b/chrome/browser/password_manager/password_form_manager_unittest.cc index 90b3772..09af894 100644 --- a/chrome/browser/password_manager/password_form_manager_unittest.cc +++ b/chrome/browser/password_manager/password_form_manager_unittest.cc @@ -1,19 +1,69 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "testing/gtest/include/gtest/gtest.h" +#include "base/memory/scoped_ptr.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "chrome/browser/password_manager/password_form_manager.h" #include "chrome/browser/password_manager/password_manager.h" +#include "chrome/browser/password_manager/password_manager_delegate.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/test/base/testing_profile.h" #include "webkit/forms/password_form.h" using webkit::forms::PasswordForm; +class TestPasswordManagerDelegate : public PasswordManagerDelegate { + public: + explicit TestPasswordManagerDelegate(Profile* profile) : profile_(profile) {} + + virtual void FillPasswordForm( + const webkit::forms::PasswordFormFillData& form_data) OVERRIDE {} + virtual void AddSavePasswordInfoBarIfPermitted( + PasswordFormManager* form_to_save) OVERRIDE {} + virtual Profile* GetProfile() OVERRIDE { return profile_; } + virtual bool DidLastPageLoadEncounterSSLErrors() OVERRIDE { return false; } + + private: + Profile* profile_; +}; + +class TestPasswordManager : public PasswordManager { + public: + explicit TestPasswordManager(PasswordManagerDelegate* delegate) + : PasswordManager(NULL, delegate) {} + + virtual void Autofill( + const webkit::forms::PasswordForm& form_for_autofill, + const webkit::forms::PasswordFormMap& best_matches, + const webkit::forms::PasswordForm& preferred_match, + bool wait_for_username) const OVERRIDE {} +}; + +class TestPasswordFormManager : public PasswordFormManager { + public: + TestPasswordFormManager(Profile* profile, + PasswordManager* manager, + const webkit::forms::PasswordForm& observed_form, + bool ssl_valid) + : PasswordFormManager(profile, manager, NULL, observed_form, ssl_valid), + num_sent_messages_(0) {} + + virtual void SendNotBlacklistedToRenderer() OVERRIDE { + ++num_sent_messages_; + } + + size_t num_sent_messages() { + return num_sent_messages_; + } + + private: + size_t num_sent_messages_; +}; + class PasswordFormManagerTest : public testing::Test { public: PasswordFormManagerTest() { @@ -55,6 +105,23 @@ class PasswordFormManagerTest : public testing::Test { p->preferred_match_ = match; } + void SimulateFetchMatchingLoginsFromPasswordStore( + PasswordFormManager* manager, + int handle) { + // Just need to update the internal states. + manager->state_ = PasswordFormManager::MATCHING_PHASE; + manager->pending_login_query_ = handle; + } + + void SimulateResponseFromPasswordStore( + PasswordFormManager* manager, + int handle, + const std::vector<PasswordForm*>& result) { + // Simply call the callback method when request done. This will transfer + // the ownership of the objects in |result| to the |manager|. + manager->OnPasswordStoreRequestDone(handle, result); + } + bool IgnoredResult(PasswordFormManager* p, PasswordForm* form) { return p->IgnoreResult(*form); } @@ -63,6 +130,12 @@ class PasswordFormManagerTest : public testing::Test { PasswordForm* observed_form() { return &observed_form_; } PasswordForm* saved_match() { return &saved_match_; } + PasswordForm* CreateSavedMatch(bool blacklisted) { + // Owned by the caller of this method. + PasswordForm* match = new PasswordForm(saved_match_); + match->blacklisted_by_user = blacklisted; + return match; + } private: PasswordForm observed_form_; @@ -72,7 +145,7 @@ class PasswordFormManagerTest : public testing::Test { TEST_F(PasswordFormManagerTest, TestNewLogin) { PasswordFormManager* manager = new PasswordFormManager( - profile(), NULL, *observed_form(), false); + profile(), NULL, NULL, *observed_form(), false); SimulateMatchingPhase(manager, false); // User submits credentials for the observed form. PasswordForm credentials = *observed_form(); @@ -127,7 +200,7 @@ TEST_F(PasswordFormManagerTest, TestUpdatePassword) { // Create a PasswordFormManager with observed_form, as if we just // saw this form and need to find matching logins. PasswordFormManager* manager = new PasswordFormManager( - profile(), NULL, *observed_form(), false); + profile(), NULL, NULL, *observed_form(), false); SimulateMatchingPhase(manager, true); // User submits credentials for the observed form using a username previously @@ -163,7 +236,7 @@ TEST_F(PasswordFormManagerTest, TestUpdatePassword) { TEST_F(PasswordFormManagerTest, TestIgnoreResult) { PasswordFormManager* manager = new PasswordFormManager( - profile(), NULL, *observed_form(), false); + profile(), NULL, NULL, *observed_form(), false); // Make sure we don't match a PasswordForm if it was originally saved on // an SSL-valid page and we are now on a page with invalid certificate. saved_match()->ssl_valid = true; @@ -181,7 +254,7 @@ TEST_F(PasswordFormManagerTest, TestIgnoreResult) { TEST_F(PasswordFormManagerTest, TestEmptyAction) { scoped_ptr<PasswordFormManager> manager(new PasswordFormManager( - profile(), NULL, *observed_form(), false)); + profile(), NULL, NULL, *observed_form(), false)); saved_match()->action = GURL(); SimulateMatchingPhase(manager.get(), true); @@ -205,27 +278,27 @@ TEST_F(PasswordFormManagerTest, TestValidForms) { credentials.password_value = saved_match()->password_value; // Form with both username_element and password_element. - PasswordFormManager manager1(profile(), NULL, credentials, false); + PasswordFormManager manager1(profile(), NULL, NULL, credentials, false); SimulateMatchingPhase(&manager1, false); EXPECT_TRUE(manager1.HasValidPasswordForm()); // Form without a username_element but with a password_element. credentials.username_element.clear(); - PasswordFormManager manager2(profile(), NULL, credentials, false); + PasswordFormManager manager2(profile(), NULL, NULL, credentials, false); SimulateMatchingPhase(&manager2, false); EXPECT_FALSE(manager2.HasValidPasswordForm()); // Form without a password_element but with a username_element. credentials.username_element = saved_match()->username_element; credentials.password_element.clear(); - PasswordFormManager manager3(profile(), NULL, credentials, false); + PasswordFormManager manager3(profile(), NULL, NULL, credentials, false); SimulateMatchingPhase(&manager3, false); EXPECT_FALSE(manager3.HasValidPasswordForm()); // Form with neither a password_element nor a username_element. credentials.username_element.clear(); credentials.password_element.clear(); - PasswordFormManager manager4(profile(), NULL, credentials, false); + PasswordFormManager manager4(profile(), NULL, NULL, credentials, false); SimulateMatchingPhase(&manager4, false); EXPECT_FALSE(manager4.HasValidPasswordForm()); } @@ -238,27 +311,64 @@ TEST_F(PasswordFormManagerTest, TestValidFormsBasic) { credentials.password_value = saved_match()->password_value; // Form with both username_element and password_element. - PasswordFormManager manager1(profile(), NULL, credentials, false); + PasswordFormManager manager1(profile(), NULL, NULL, credentials, false); SimulateMatchingPhase(&manager1, false); EXPECT_TRUE(manager1.HasValidPasswordForm()); // Form without a username_element but with a password_element. credentials.username_element.clear(); - PasswordFormManager manager2(profile(), NULL, credentials, false); + PasswordFormManager manager2(profile(), NULL, NULL, credentials, false); SimulateMatchingPhase(&manager2, false); EXPECT_TRUE(manager2.HasValidPasswordForm()); // Form without a password_element but with a username_element. credentials.username_element = saved_match()->username_element; credentials.password_element.clear(); - PasswordFormManager manager3(profile(), NULL, credentials, false); + PasswordFormManager manager3(profile(), NULL, NULL, credentials, false); SimulateMatchingPhase(&manager3, false); EXPECT_TRUE(manager3.HasValidPasswordForm()); // Form with neither a password_element nor a username_element. credentials.username_element.clear(); credentials.password_element.clear(); - PasswordFormManager manager4(profile(), NULL, credentials, false); + PasswordFormManager manager4(profile(), NULL, NULL, credentials, false); SimulateMatchingPhase(&manager4, false); EXPECT_TRUE(manager4.HasValidPasswordForm()); } + +TEST_F(PasswordFormManagerTest, TestSendNotBlacklistedMessage) { + // A dumb password manager. + TestPasswordManagerDelegate delegate(profile()); + TestPasswordManager password_manager(&delegate); + + // First time sign up attempt; No login result is found from password store; + // We should send the not blacklisted message. + scoped_ptr<TestPasswordFormManager> manager(new TestPasswordFormManager( + profile(), &password_manager, *observed_form(), false)); + SimulateFetchMatchingLoginsFromPasswordStore(manager.get(), 1); + std::vector<PasswordForm*> result; + SimulateResponseFromPasswordStore(manager.get(), 1, result); + EXPECT_EQ(1u, manager->num_sent_messages()); + + // Sign up attempt to previously visited sites; Login result is found from + // password store, and is not blacklisted; We should send the not blacklisted + // message. + manager.reset(new TestPasswordFormManager( + profile(), &password_manager, *observed_form(), false)); + SimulateFetchMatchingLoginsFromPasswordStore(manager.get(), 2); + // We need add heap allocated objects to result. + result.push_back(CreateSavedMatch(false)); + SimulateResponseFromPasswordStore(manager.get(), 2, result); + EXPECT_EQ(1u, manager->num_sent_messages()); + + // Sign up attempt to previously visited sites; Login result is found from + // password store, but is blacklisted; We should not send the not blacklisted + // message. + manager.reset(new TestPasswordFormManager( + profile(), &password_manager, *observed_form(), false)); + SimulateFetchMatchingLoginsFromPasswordStore(manager.get(), 3); + result.clear(); + result.push_back(CreateSavedMatch(true)); + SimulateResponseFromPasswordStore(manager.get(), 3, result); + EXPECT_EQ(0u, manager->num_sent_messages()); +} diff --git a/chrome/browser/password_manager/password_manager.cc b/chrome/browser/password_manager/password_manager.cc index f83ce24..46c8322 100644 --- a/chrome/browser/password_manager/password_manager.cc +++ b/chrome/browser/password_manager/password_manager.cc @@ -13,6 +13,7 @@ #include "chrome/common/autofill_messages.h" #include "chrome/common/pref_names.h" #include "content/public/browser/user_metrics.h" +#include "content/public/browser/web_contents.h" #include "content/public/common/frame_navigate_params.h" #include "grit/generated_resources.h" @@ -89,7 +90,11 @@ void PasswordManager::SetFormHasGeneratedPassword(const PasswordForm& form) { bool ssl_valid = (form.origin.SchemeIsSecure() && !delegate_->DidLastPageLoadEncounterSSLErrors()); PasswordFormManager* manager = - new PasswordFormManager(delegate_->GetProfile(), this, form, ssl_valid); + new PasswordFormManager(delegate_->GetProfile(), + this, + web_contents()->GetRenderViewHost(), + form, + ssl_valid); pending_login_managers_.push_back(manager); manager->SetHasGeneratedPassword(); // TODO(gcasto): Add UMA stats to track this. @@ -193,7 +198,10 @@ void PasswordManager::OnPasswordFormsParsed( iter != forms.end(); ++iter) { bool ssl_valid = iter->origin.SchemeIsSecure() && !had_ssl_error; PasswordFormManager* manager = - new PasswordFormManager(delegate_->GetProfile(), this, *iter, + new PasswordFormManager(delegate_->GetProfile(), + this, + web_contents()->GetRenderViewHost(), + *iter, ssl_valid); pending_login_managers_.push_back(manager); manager->FetchMatchingLoginsFromPasswordStore(); diff --git a/chrome/browser/password_manager/password_manager.h b/chrome/browser/password_manager/password_manager.h index afeab33..06d292c 100644 --- a/chrome/browser/password_manager/password_manager.h +++ b/chrome/browser/password_manager/password_manager.h @@ -45,10 +45,10 @@ class PasswordManager : public LoginModel, // Called by a PasswordFormManager when it decides a form can be autofilled // on the page. - void Autofill(const webkit::forms::PasswordForm& form_for_autofill, - const webkit::forms::PasswordFormMap& best_matches, - const webkit::forms::PasswordForm& preferred_match, - bool wait_for_username) const; + virtual void Autofill(const webkit::forms::PasswordForm& form_for_autofill, + const webkit::forms::PasswordFormMap& best_matches, + const webkit::forms::PasswordForm& preferred_match, + bool wait_for_username) const; // LoginModel implementation. virtual void SetObserver(LoginModelObserver* observer) OVERRIDE; diff --git a/chrome/common/autofill_messages.h b/chrome/common/autofill_messages.h index 636a15b..4317b94 100644 --- a/chrome/common/autofill_messages.h +++ b/chrome/common/autofill_messages.h @@ -130,6 +130,11 @@ IPC_MESSAGE_ROUTED1(AutofillMsg_PasswordGenerationEnabled, IPC_MESSAGE_ROUTED1(AutofillMsg_AcceptPasswordAutofillSuggestion, string16 /* username value*/) +// Tells the renderer that this password form is not blacklisted. A form can +// be blacklisted if a user chooses "never save passwords for this site". +IPC_MESSAGE_ROUTED1(AutofillMsg_FormNotBlacklisted, + webkit::forms::PasswordForm /* form checked */) + // Autofill messages sent from the renderer to the browser. // Notification that forms have been seen that are candidates for diff --git a/chrome/renderer/autofill/password_generation_manager.cc b/chrome/renderer/autofill/password_generation_manager.cc index beccc02..ac93760 100644 --- a/chrome/renderer/autofill/password_generation_manager.cc +++ b/chrome/renderer/autofill/password_generation_manager.cc @@ -27,21 +27,6 @@ namespace { bool GetAccountCreationPasswordFields( const WebKit::WebFormElement& form, std::vector<WebKit::WebInputElement>* passwords) { - // Ignore forms with autocomplete turned off for now. We may remove this in - // the future, as we only want to avoid creating passwords if the signin - // form has autocomplete turned off. - if (form.isNull() || !form.autoComplete()) - return false; - - // Bail out if |form| is not a valid PasswordForm. In this case the password - // wouldn't get saved even if generated. - scoped_ptr<webkit::forms::PasswordForm> password_form( - webkit::forms::PasswordFormDomManager::CreatePasswordForm(form)); - if (!password_form.get()) { - DVLOG(2) << "Invalid action on form"; - return false; - } - // Grab all of the passwords for the form. WebKit::WebVector<WebKit::WebFormControlElement> control_elements; form.getFormControlElements(control_elements); @@ -76,10 +61,19 @@ PasswordGenerationManager::PasswordGenerationManager( } PasswordGenerationManager::~PasswordGenerationManager() {} -void PasswordGenerationManager::DidFinishLoad(WebKit::WebFrame* frame) { - // Clear previous state. +void PasswordGenerationManager::DidFinishDocumentLoad(WebKit::WebFrame* frame) { + // In every navigation, the IPC message sent by the password autofill manager + // to query whether the current form is blacklisted or not happens when the + // document load finishes, so we need to clear previous states here before we + // hear back from the browser. Note that we assume there is only one account + // creation form, but there could be multiple password forms in each frame. + not_blacklisted_password_form_origins_.clear(); + // Initialize to an empty and invalid GURL. + account_creation_form_origin_ = GURL(); passwords_.clear(); +} +void PasswordGenerationManager::DidFinishLoad(WebKit::WebFrame* frame) { // We don't want to generate passwords if the browser won't store or sync // them. if (!enabled_) @@ -91,13 +85,27 @@ void PasswordGenerationManager::DidFinishLoad(WebKit::WebFrame* frame) { WebKit::WebVector<WebKit::WebFormElement> forms; frame->document().forms(forms); for (size_t i = 0; i < forms.size(); ++i) { + // Ignore forms with autocomplete turned off for now. We may remove this in + // the future, as we only want to avoid creating passwords if the signin + // form has autocomplete turned off. + if (forms[i].isNull() || !forms[i].autoComplete()) + continue; + + // If we can't get a valid PasswordForm, we skip this form because the + // the password won't get saved even if we generate it. + scoped_ptr<webkit::forms::PasswordForm> password_form( + webkit::forms::PasswordFormDomManager::CreatePasswordForm(forms[i])); + if (!password_form.get()) { + DVLOG(2) << "Invalid action on form"; + continue; + } + std::vector<WebKit::WebInputElement> passwords; if (GetAccountCreationPasswordFields(forms[i], &passwords)) { DVLOG(2) << "Account creation form detected"; passwords_ = passwords; - // Make the decoration visible for this element. - passwords[0].decorationElementFor(this).setAttribute("style", - "display:block"); + account_creation_form_origin_ = password_form->origin; + MaybeShowIcon(); // We assume that there is only one account creation field per URL. return; } @@ -159,6 +167,8 @@ void PasswordGenerationManager::willDetach( bool PasswordGenerationManager::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(PasswordGenerationManager, message) + IPC_MESSAGE_HANDLER(AutofillMsg_FormNotBlacklisted, + OnFormNotBlacklisted) IPC_MESSAGE_HANDLER(AutofillMsg_GeneratedPasswordAccepted, OnPasswordAccepted) IPC_MESSAGE_HANDLER(AutofillMsg_PasswordGenerationEnabled, @@ -168,6 +178,12 @@ bool PasswordGenerationManager::OnMessageReceived(const IPC::Message& message) { return handled; } +void PasswordGenerationManager::OnFormNotBlacklisted( + const webkit::forms::PasswordForm& form) { + not_blacklisted_password_form_origins_.push_back(form.origin); + MaybeShowIcon(); +} + void PasswordGenerationManager::OnPasswordAccepted(const string16& password) { for (std::vector<WebKit::WebInputElement>::iterator it = passwords_.begin(); it != passwords_.end(); ++it) { @@ -180,4 +196,25 @@ void PasswordGenerationManager::OnPasswordGenerationEnabled(bool enabled) { enabled_ = enabled; } +void PasswordGenerationManager::MaybeShowIcon() { + // We should show the password generation icon only when we have detected + // account creation form and we have confirmed from browser that this form + // is not blacklisted by the users. + if (!account_creation_form_origin_.is_valid() || + passwords_.empty() || + not_blacklisted_password_form_origins_.empty()) { + return; + } + + for (std::vector<GURL>::iterator it = + not_blacklisted_password_form_origins_.begin(); + it != not_blacklisted_password_form_origins_.end(); ++it) { + if (*it == account_creation_form_origin_) { + passwords_[0].decorationElementFor(this).setAttribute("style", + "display:block"); + return; + } + } +} + } // namespace autofill diff --git a/chrome/renderer/autofill/password_generation_manager.h b/chrome/renderer/autofill/password_generation_manager.h index 72c236c..c5fffa2 100644 --- a/chrome/renderer/autofill/password_generation_manager.h +++ b/chrome/renderer/autofill/password_generation_manager.h @@ -11,6 +11,7 @@ #include <vector> #include "content/public/renderer/render_view_observer.h" +#include "googleurl/src/gurl.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebTextFieldDecoratorClient.h" @@ -19,6 +20,12 @@ class WebCString; class WebDocument; } +namespace webkit { +namespace forms { +struct PasswordForm; +} // namespace forms +} // namespace webkit + namespace autofill { // This class is responsible for controlling communication for password @@ -40,6 +47,7 @@ class PasswordGenerationManager : public content::RenderViewObserver, private: // RenderViewObserver: + virtual void DidFinishDocumentLoad(WebKit::WebFrame* frame) OVERRIDE; virtual void DidFinishLoad(WebKit::WebFrame* frame) OVERRIDE; // WebTextFieldDecoratorClient: @@ -52,17 +60,26 @@ class PasswordGenerationManager : public content::RenderViewObserver, virtual void handleClick(WebKit::WebInputElement& element) OVERRIDE; virtual void willDetach(const WebKit::WebInputElement& element) OVERRIDE; - bool IsAccountCreationForm(const WebKit::WebFormElement& form, - std::vector<WebKit::WebInputElement>* passwords); - // Message handlers. + void OnFormNotBlacklisted(const webkit::forms::PasswordForm& form); void OnPasswordAccepted(const string16& password); void OnPasswordGenerationEnabled(bool enabled); + // Helper function to decide whether we should show password generation icon. + void MaybeShowIcon(); + // True if password generation is enabled for the profile associated // with this renderer. bool enabled_; + // Stores the origin of the account creation form we detected. + GURL account_creation_form_origin_; + + // Stores the origins of the password forms confirmed not to be blacklisted + // by the browser. A form can be blacklisted if a user chooses "never save + // passwords for this site". + std::vector<GURL> not_blacklisted_password_form_origins_; + std::vector<WebKit::WebInputElement> passwords_; DISALLOW_COPY_AND_ASSIGN(PasswordGenerationManager); diff --git a/chrome/renderer/autofill/password_generation_manager_browsertest.cc b/chrome/renderer/autofill/password_generation_manager_browsertest.cc index d0d924d..0095c4a 100644 --- a/chrome/renderer/autofill/password_generation_manager_browsertest.cc +++ b/chrome/renderer/autofill/password_generation_manager_browsertest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <string.h> + #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" #include "base/utf_string_conversions.h" @@ -60,7 +62,7 @@ class PasswordGenerationManagerTest : public ChromeRenderViewTest { virtual void SetUp() { // We don't actually create a PasswordGenerationManager during // ChromeRenderViewTest::SetUp because it's behind a flag. Since we want - // to use a test manager anyway, we just create out own. + // to use a test manager anyway, we just create our own. ChromeRenderViewTest::SetUp(); generation_manager_.reset(new TestPasswordGenerationManager(view_)); } @@ -77,6 +79,14 @@ class PasswordGenerationManagerTest : public ChromeRenderViewTest { return decoration.hasNonEmptyBoundingBox(); } + void SetNotBlacklistedMessage(const char* form_str) { + webkit::forms::PasswordForm form; + form.origin = + GURL(StringPrintf("data:text/html;charset=utf-8,%s",form_str)); + AutofillMsg_FormNotBlacklisted msg(0, form); + generation_manager_->OnMessageReceived(msg); + } + protected: scoped_ptr<TestPasswordGenerationManager> generation_manager_; @@ -138,9 +148,11 @@ TEST_F(PasswordGenerationManagerTest, DetectionTest) { AutofillMsg_PasswordGenerationEnabled msg(0, true); generation_manager_->OnMessageReceived(msg); - // Now check that the decoration is visible on the first password field and - // that we send a message after the decoration is clicked. LoadHTML(kAccountCreationFormHTML); + + // Pretend like we have received message indicating site is not blacklisted. + SetNotBlacklistedMessage(kAccountCreationFormHTML); + document = GetMainFrame()->document(); element = document.getElementById(WebString::fromUTF8("first_password")); ASSERT_FALSE(element.isNull()); @@ -153,6 +165,7 @@ TEST_F(PasswordGenerationManagerTest, DetectionTest) { // This doesn't trigger because hidden password fields are ignored. LoadHTML(kHiddenPasswordAccountCreationFormHTML); + SetNotBlacklistedMessage(kAccountCreationFormHTML); document = GetMainFrame()->document(); element = document.getElementById(WebString::fromUTF8("first_password")); ASSERT_FALSE(element.isNull()); @@ -161,6 +174,7 @@ TEST_F(PasswordGenerationManagerTest, DetectionTest) { // This doesn't trigger because the form action is invalid. LoadHTML(kInvalidActionAccountCreationFormHTML); + SetNotBlacklistedMessage(kAccountCreationFormHTML); document = GetMainFrame()->document(); element = document.getElementById(WebString::fromUTF8("first_password")); ASSERT_FALSE(element.isNull()); @@ -198,4 +212,59 @@ TEST_F(PasswordGenerationManagerTest, FillTest) { EXPECT_TRUE(second_password_element.isAutofilled()); } +TEST_F(PasswordGenerationManagerTest, BlacklistedTest) { + // Make sure password generation is enabled. + AutofillMsg_PasswordGenerationEnabled enabled_msg(0, true); + generation_manager_->OnMessageReceived(enabled_msg); + + // Did not receive not blacklisted message. Don't show password generation + // icon. + LoadHTML(kAccountCreationFormHTML); + WebDocument document = GetMainFrame()->document(); + WebElement element = + document.getElementById(WebString::fromUTF8("first_password")); + ASSERT_FALSE(element.isNull()); + WebInputElement first_password_element = element.to<WebInputElement>(); + EXPECT_FALSE(DecorationIsVisible(&first_password_element)); + + // Receive one not blacklisted message for non account creation form. Don't + // show password generation icon. + LoadHTML(kAccountCreationFormHTML); + SetNotBlacklistedMessage(kSigninFormHTML); + document = GetMainFrame()->document(); + element = document.getElementById(WebString::fromUTF8("first_password")); + ASSERT_FALSE(element.isNull()); + first_password_element = element.to<WebInputElement>(); + EXPECT_FALSE(DecorationIsVisible(&first_password_element)); + + // Receive one not blackliste message for account creation form. Show password + // generation icon. + LoadHTML(kAccountCreationFormHTML); + SetNotBlacklistedMessage(kAccountCreationFormHTML); + document = GetMainFrame()->document(); + element = document.getElementById(WebString::fromUTF8("first_password")); + ASSERT_FALSE(element.isNull()); + first_password_element = element.to<WebInputElement>(); + EXPECT_TRUE(DecorationIsVisible(&first_password_element)); + SimulateClickOnDecoration(&first_password_element); + EXPECT_EQ(1u, generation_manager_->messages().size()); + EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID, + generation_manager_->messages()[0]->type()); + + // Receive two not blacklisted messages, one is for account creation form and + // the other is not. Show password generation icon. + LoadHTML(kAccountCreationFormHTML); + SetNotBlacklistedMessage(kAccountCreationFormHTML); + SetNotBlacklistedMessage(kSigninFormHTML); + document = GetMainFrame()->document(); + element = document.getElementById(WebString::fromUTF8("first_password")); + ASSERT_FALSE(element.isNull()); + first_password_element = element.to<WebInputElement>(); + EXPECT_TRUE(DecorationIsVisible(&first_password_element)); + SimulateClickOnDecoration(&first_password_element); + EXPECT_EQ(2u, generation_manager_->messages().size()); + EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID, + generation_manager_->messages()[1]->type()); +} + } // namespace autofill |