Commit 147162d9 authored by Dominic Battre's avatar Dominic Battre Committed by Commit Bot

Introduce Finch parameter for hiding a password generation popup

If a user focuses a field for which password generation is promoted, a popup is
shown.

http://crrev.com/288935 introduced that this popup remains visible until the
user has typed 5 characters, hypothesizing that users look at their keyboard
while typing and therefore not notice the popup.

Our new hypothesis is that this a) may not be true, and b) may bother users
because the popup occludes part of the website, in particular it can occlude
password requirements for users who chose to invent their own password.

This CL introduces a Finch experiement such that we can test whether keeping the
popup visible until the user has typed >5 characters produces a statistically
significant higher number of generated passwords. If not, we can just hide the
password generation popup as soon as the user has typed a key.

The default configuration sticks to the 5 character threshold.

Bug: 859472
Change-Id: I1d2a82ce90f696dc32d97633ac29ba15f3ea9bbd
Reviewed-on: https://chromium-review.googlesource.com/1122399
Commit-Queue: Dominic Battré <battre@chromium.org>
Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#572131}
parent 4f28a8f0
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h" #include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/renderer/autofill/fake_content_password_manager_driver.h" #include "chrome/renderer/autofill/fake_content_password_manager_driver.h"
#include "chrome/renderer/autofill/fake_password_manager_client.h" #include "chrome/renderer/autofill/fake_password_manager_client.h"
#include "chrome/renderer/autofill/password_generation_test_utils.h" #include "chrome/renderer/autofill/password_generation_test_utils.h"
...@@ -153,8 +152,6 @@ class PasswordGenerationAgentTest : public ChromeRenderViewTest { ...@@ -153,8 +152,6 @@ class PasswordGenerationAgentTest : public ChromeRenderViewTest {
FakePasswordManagerClient fake_pw_client_; FakePasswordManagerClient fake_pw_client_;
private: private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgentTest); DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgentTest);
}; };
...@@ -518,63 +515,74 @@ TEST_F(PasswordGenerationAgentTest, AccountCreationFormsDetectedTest) { ...@@ -518,63 +515,74 @@ TEST_F(PasswordGenerationAgentTest, AccountCreationFormsDetectedTest) {
} }
TEST_F(PasswordGenerationAgentTest, MaximumOfferSize) { TEST_F(PasswordGenerationAgentTest, MaximumOfferSize) {
base::HistogramTester histogram_tester; for (size_t maximum_offer_size : {0, 5}) {
SCOPED_TRACE(testing::Message()
LoadHTMLWithUserGesture(kAccountCreationFormHTML); << "maximum_offer_size = " << maximum_offer_size);
SetNotBlacklistedMessage(password_generation_, kAccountCreationFormHTML); password_generation_->set_maximum_offer_size_for_testing(
SetAccountCreationFormsDetectedMessage(password_generation_, maximum_offer_size);
GetMainFrame()->GetDocument(), 0, 1); EXPECT_EQ(maximum_offer_size, password_generation_->maximum_offer_size());
ExpectAutomaticGenerationAvailable("first_password", true);
base::HistogramTester histogram_tester;
WebDocument document = GetMainFrame()->GetDocument();
WebElement element = LoadHTMLWithUserGesture(kAccountCreationFormHTML);
document.GetElementById(WebString::FromUTF8("first_password")); SetNotBlacklistedMessage(password_generation_, kAccountCreationFormHTML);
ASSERT_FALSE(element.IsNull()); SetAccountCreationFormsDetectedMessage(password_generation_,
WebInputElement first_password_element = element.To<WebInputElement>(); GetMainFrame()->GetDocument(), 0, 1);
// There should now be a message to show the UI.
ExpectAutomaticGenerationAvailable("first_password", true);
// Make a password just under maximum offer size. WebDocument document = GetMainFrame()->GetDocument();
SimulateUserInputChangeForElement( WebElement element =
&first_password_element, document.GetElementById(WebString::FromUTF8("first_password"));
std::string(password_generation_->kMaximumOfferSize - 1, 'a')); ASSERT_FALSE(element.IsNull());
// There should now be a message to show the UI. WebInputElement first_password_element = element.To<WebInputElement>();
EXPECT_TRUE(GetCalledAutomaticGenerationStatusChangedTrue());
fake_pw_client_.reset_called_automatic_generation_status_changed_true(); // Make a password just under maximum offer size.
if (password_generation_->maximum_offer_size() > 0) {
SimulateUserInputChangeForElement(
&first_password_element,
std::string(password_generation_->maximum_offer_size(), 'a'));
// There should still be a message to show the UI.
EXPECT_TRUE(GetCalledAutomaticGenerationStatusChangedTrue());
fake_pw_client_.reset_called_automatic_generation_status_changed_true();
fake_pw_client_.reset_called_password_generation_rejected_by_typing();
}
fake_pw_client_.reset_called_password_generation_rejected_by_typing(); // Simulate a user typing a password just over maximum offer size.
// Simulate a user typing a password just over maximum offer size. SimulateUserTypingASCIICharacter('a', true);
SimulateUserTypingASCIICharacter('a', false); // There should now be a message that generation was rejected.
SimulateUserTypingASCIICharacter('a', true); fake_pw_client_.Flush();
// There should now be a message that generation was rejected. EXPECT_TRUE(
fake_pw_client_.Flush(); fake_pw_client_.called_password_generation_rejected_by_typing());
EXPECT_TRUE(fake_pw_client_.called_password_generation_rejected_by_typing()); fake_pw_client_.reset_called_show_manual_pw_generation_popup();
fake_pw_client_.reset_called_show_manual_pw_generation_popup();
// Simulate the user deleting characters. The generation popup should be shown // Simulate the user deleting characters. The generation popup should be
// again. // shown again.
SimulateUserTypingASCIICharacter(ui::VKEY_BACK, true); SimulateUserTypingASCIICharacter(ui::VKEY_BACK, true);
// There should now be a message to show the UI. // There should now be a message to show the UI.
EXPECT_TRUE(GetCalledAutomaticGenerationStatusChangedTrue()); EXPECT_TRUE(GetCalledAutomaticGenerationStatusChangedTrue());
fake_pw_client_.reset_called_automatic_generation_status_changed_true(); fake_pw_client_.reset_called_automatic_generation_status_changed_true();
// Change focus. Bubble should be hidden, but that is handled by AutofilAgent, // Change focus. Bubble should be hidden, but that is handled by
// so no messages are sent. // AutofilAgent, so no messages are sent.
ExecuteJavaScriptForTests("document.getElementById('username').focus();"); ExecuteJavaScriptForTests("document.getElementById('username').focus();");
EXPECT_FALSE(GetCalledAutomaticGenerationStatusChangedTrue()); EXPECT_FALSE(GetCalledAutomaticGenerationStatusChangedTrue());
// Focusing the password field will bring up the generation UI again. // Focusing the password field will bring up the generation UI again.
ExecuteJavaScriptForTests( ExecuteJavaScriptForTests(
"document.getElementById('first_password').focus();"); "document.getElementById('first_password').focus();");
EXPECT_TRUE(GetCalledAutomaticGenerationStatusChangedTrue()); EXPECT_TRUE(GetCalledAutomaticGenerationStatusChangedTrue());
fake_pw_client_.reset_called_automatic_generation_status_changed_true(); fake_pw_client_.reset_called_automatic_generation_status_changed_true();
// Loading a different page triggers UMA stat upload. Verify that only one // Loading a different page triggers UMA stat upload. Verify that only one
// display event is sent. // display event is sent.
LoadHTMLWithUserGesture(kSigninFormHTML); LoadHTMLWithUserGesture(kSigninFormHTML);
histogram_tester.ExpectBucketCount( histogram_tester.ExpectBucketCount(
"PasswordGeneration.Event", "PasswordGeneration.Event",
autofill::password_generation::GENERATION_POPUP_SHOWN, autofill::password_generation::GENERATION_POPUP_SHOWN, 1);
1); }
} }
TEST_F(PasswordGenerationAgentTest, DynamicFormTest) { TEST_F(PasswordGenerationAgentTest, DynamicFormTest) {
......
...@@ -10,7 +10,9 @@ ...@@ -10,7 +10,9 @@
#include "base/command_line.h" #include "base/command_line.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/metrics/field_trial_params.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "components/autofill/content/renderer/form_autofill_util.h" #include "components/autofill/content/renderer/form_autofill_util.h"
#include "components/autofill/content/renderer/form_classifier.h" #include "components/autofill/content/renderer/form_classifier.h"
...@@ -163,6 +165,23 @@ void CopyElementValueToOtherInputElements( ...@@ -163,6 +165,23 @@ void CopyElementValueToOtherInputElements(
} }
} }
// Returns the number of characters the user may type while password generation
// is offered. Once the user has typed more than the given number, a password
// generation popup is removed.
// TODO(crbug.com/859472) Delete this function once we know a good value from
// the field trial.
size_t GetMaximumOfferSize() {
std::string string_value = base::GetFieldTrialParamValue(
"PasswordGenerationMaximumOfferSize", "maximum_offer_size");
size_t parsed_value = 0;
if (!base::StringToSizeT(string_value, &parsed_value)) {
// This is the historic default value and remains in place for the time
// being. The hypothesis is, though, that 0 would be a better default.
return 5;
}
return parsed_value;
}
} // namespace } // namespace
PasswordGenerationAgent::AccountCreationFormData::AccountCreationFormData( PasswordGenerationAgent::AccountCreationFormData::AccountCreationFormData(
...@@ -191,7 +210,8 @@ PasswordGenerationAgent::PasswordGenerationAgent( ...@@ -191,7 +210,8 @@ PasswordGenerationAgent::PasswordGenerationAgent(
base::CommandLine::ForCurrentProcess()->HasSwitch( base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kShowAutofillSignatures)), switches::kShowAutofillSignatures)),
password_agent_(password_agent), password_agent_(password_agent),
binding_(this) { binding_(this),
maximum_offer_size_(GetMaximumOfferSize()) {
LogBoolean(Logger::STRING_GENERATION_RENDERER_ENABLED, enabled_); LogBoolean(Logger::STRING_GENERATION_RENDERER_ENABLED, enabled_);
registry->AddInterface(base::Bind(&PasswordGenerationAgent::BindRequest, registry->AddInterface(base::Bind(&PasswordGenerationAgent::BindRequest,
base::Unretained(this))); base::Unretained(this)));
...@@ -576,11 +596,11 @@ bool PasswordGenerationAgent::FocusedNodeHasChanged( ...@@ -576,11 +596,11 @@ bool PasswordGenerationAgent::FocusedNodeHasChanged(
return true; return true;
} }
// Assume that if the password field has less than kMaximumOfferSize // Assume that if the password field has less than maximum_offer_size_
// characters then the user is not finished typing their password and display // characters then the user is not finished typing their password and display
// the password suggestion. // the password suggestion.
if (!element->IsReadOnly() && element->IsEnabled() && if (!element->IsReadOnly() && element->IsEnabled() &&
element->Value().length() <= kMaximumOfferSize) { element->Value().length() <= maximum_offer_size_) {
MaybeOfferAutomaticGeneration(); MaybeOfferAutomaticGeneration();
return true; return true;
} }
...@@ -620,11 +640,11 @@ bool PasswordGenerationAgent::TextDidChangeInTextField( ...@@ -620,11 +640,11 @@ bool PasswordGenerationAgent::TextDidChangeInTextField(
if (presaved_form) { if (presaved_form) {
GetPasswordManagerClient()->PresaveGeneratedPassword(*presaved_form); GetPasswordManagerClient()->PresaveGeneratedPassword(*presaved_form);
} }
} else if (element.Value().length() > kMaximumOfferSize) { } else if (element.Value().length() > maximum_offer_size_) {
// User has rejected the feature and has started typing a password. // User has rejected the feature and has started typing a password.
GenerationRejectedByTyping(); GenerationRejectedByTyping();
} else { } else {
// Password isn't generated and there are fewer than kMaximumOfferSize // Password isn't generated and there are fewer than maximum_offer_size_
// characters typed, so keep offering the password. Note this function // characters typed, so keep offering the password. Note this function
// will just keep the previous popup if one is already showing. // will just keep the previous popup if one is already showing.
MaybeOfferAutomaticGeneration(); MaybeOfferAutomaticGeneration();
......
...@@ -68,7 +68,13 @@ class PasswordGenerationAgent : public content::RenderFrameObserver, ...@@ -68,7 +68,13 @@ class PasswordGenerationAgent : public content::RenderFrameObserver,
void OnFieldAutofilled(const blink::WebInputElement& password_element); void OnFieldAutofilled(const blink::WebInputElement& password_element);
// The length that a password can be before the UI is hidden. // The length that a password can be before the UI is hidden.
static const size_t kMaximumOfferSize = 5; size_t maximum_offer_size() const { return maximum_offer_size_; }
#if defined(UNIT_TEST)
void set_maximum_offer_size_for_testing(size_t maximum_offer_size) {
maximum_offer_size_ = maximum_offer_size;
}
#endif
protected: protected:
// Returns true if the document for |render_frame()| is one that we should // Returns true if the document for |render_frame()| is one that we should
...@@ -227,6 +233,9 @@ class PasswordGenerationAgent : public content::RenderFrameObserver, ...@@ -227,6 +233,9 @@ class PasswordGenerationAgent : public content::RenderFrameObserver,
mojo::Binding<mojom::PasswordGenerationAgent> binding_; mojo::Binding<mojom::PasswordGenerationAgent> binding_;
// The length that a password can be before the UI is hidden.
size_t maximum_offer_size_;
DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgent); DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgent);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment