summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorzysxqn@google.com <zysxqn@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-19 17:52:21 +0000
committerzysxqn@google.com <zysxqn@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-19 17:52:21 +0000
commitc3ebaa9ca4b5288448d6d5e5dee08f2aa68c0a52 (patch)
tree9a5ac0b11bb1df472971de1915a155823b818ce4
parent8dfcccdcf5ead74a511c0139933bfc62a263f41c (diff)
downloadchromium_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
-rw-r--r--chrome/browser/password_manager/password_form_manager.cc16
-rw-r--r--chrome/browser/password_manager/password_form_manager.h13
-rw-r--r--chrome/browser/password_manager/password_form_manager_unittest.cc136
-rw-r--r--chrome/browser/password_manager/password_manager.cc12
-rw-r--r--chrome/browser/password_manager/password_manager.h8
-rw-r--r--chrome/common/autofill_messages.h5
-rw-r--r--chrome/renderer/autofill/password_generation_manager.cc77
-rw-r--r--chrome/renderer/autofill/password_generation_manager.h23
-rw-r--r--chrome/renderer/autofill/password_generation_manager_browsertest.cc75
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