summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjww@chromium.org <jww@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-16 07:28:09 +0000
committerjww@chromium.org <jww@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-16 07:28:09 +0000
commit580c38058402787433d18f714e1336ec4a6d30de (patch)
tree3f7f414f04239047cc916085814f0afb3b162c6f
parent925e4e741ac318f3da29a23369df3327cf3ffb4a (diff)
downloadchromium_src-580c38058402787433d18f714e1336ec4a6d30de.zip
chromium_src-580c38058402787433d18f714e1336ec4a6d30de.tar.gz
chromium_src-580c38058402787433d18f714e1336ec4a6d30de.tar.bz2
Add password manager autocomplete suggestion when a username element in clicked.
This adds a full credential selection whenever a username element is clicked. Previously, if a username element was filled by either the user or the password manager, the user would have to erase the entire field to see the full list of credentials the user has. Now the user can click on the field at any point to get a full list of accounts. However, if the user starts typing again, then inline autocomplete takes over. BUG=341474 Review URL: https://codereview.chromium.org/166043006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@283383 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/renderer/autofill/password_autofill_agent_browsertest.cc180
-rw-r--r--components/autofill/content/renderer/autofill_agent.cc29
-rw-r--r--components/autofill/content/renderer/autofill_agent.h10
-rw-r--r--components/autofill/content/renderer/password_autofill_agent.cc25
-rw-r--r--components/autofill/content/renderer/password_autofill_agent.h12
5 files changed, 210 insertions, 46 deletions
diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
index 77d853e..d228ca2 100644
--- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -264,14 +264,17 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest {
void SimulateUsernameChangeForElement(const std::string& username,
bool move_caret_to_end,
WebFrame* input_frame,
- WebInputElement& username_input) {
- username_input.setValue(WebString::fromUTF8(username));
+ WebInputElement& username_input,
+ bool is_user_input) {
+ username_input.setValue(WebString::fromUTF8(username), is_user_input);
// The field must have focus or AutofillAgent will think the
// change should be ignored.
while (!username_input.focused())
input_frame->document().frame()->view()->advanceFocus(false);
if (move_caret_to_end)
username_input.setSelectionRange(username.length(), username.length());
+ if (is_user_input)
+ autofill_agent_->password_autofill_agent_->gatekeeper_.OnUserGesture();
autofill_agent_->textFieldDidChange(username_input);
// Processing is delayed because of a Blink bug:
// https://bugs.webkit.org/show_bug.cgi?id=16976
@@ -286,14 +289,32 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest {
base::MessageLoop::current()->RunUntilIdle();
}
+ void SimulateSuggestionChoice(WebInputElement& username_input) {
+ blink::WebString blink_username =
+ blink::WebString::fromUTF8(kAliceUsername);
+ blink::WebString blink_password =
+ blink::WebString::fromUTF8(kAlicePassword);
+
+ // This call is necessary to setup the autofill agent appropriate for the
+ // user selection; simulates the menu actually popping up.
+ render_thread_->sink().ClearMessages();
+ autofill_agent_->FormControlElementClicked(username_input, false);
+
+ autofill_agent_->OnFillPasswordSuggestion(blink_username, blink_password);
+ }
+
void LayoutMainFrame() {
GetMainFrame()->view()->layout();
}
void SimulateUsernameChange(const std::string& username,
- bool move_caret_to_end) {
- SimulateUsernameChangeForElement(username, move_caret_to_end,
- GetMainFrame(), username_element_);
+ bool move_caret_to_end,
+ bool is_user_input = false) {
+ SimulateUsernameChangeForElement(username,
+ move_caret_to_end,
+ GetMainFrame(),
+ username_element_,
+ is_user_input);
}
// Tests that no suggestion popup is generated when the username_element_ is
@@ -320,7 +341,7 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest {
// permanently ignored.
if (!ShouldIgnoreAutocompleteOffForPasswordFields()) {
EXPECT_TRUE(autofill_agent_->password_autofill_agent_->ShowSuggestions(
- username_element_));
+ username_element_, false));
EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching(
AutofillHostMsg_ShowPasswordSuggestions::ID));
@@ -340,7 +361,7 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest {
const WebInputElement& password_element,
const std::string& password,
bool password_autofilled,
- bool checkSuggestedValue = true) {
+ bool checkSuggestedValue) {
EXPECT_EQ(username,
static_cast<std::string>(username_element.value().utf8()));
EXPECT_EQ(username_autofilled, username_element.isAutofilled());
@@ -357,9 +378,13 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest {
bool username_autofilled,
const std::string& password,
bool password_autofilled) {
- CheckTextFieldsStateForElements(username_element_, username,
- username_autofilled, password_element_,
- password, password_autofilled);
+ CheckTextFieldsStateForElements(username_element_,
+ username,
+ username_autofilled,
+ password_element_,
+ password,
+ password_autofilled,
+ true);
}
// Checks the DOM-accessible value of the username element and the
@@ -382,6 +407,51 @@ class PasswordAutofillAgentTest : public ChromeRenderViewTest {
EXPECT_EQ(end, username_element_.selectionEnd());
}
+ void ExpectOneCredential(const base::string16& username) {
+ const IPC::Message* message =
+ render_thread_->sink().GetFirstMessageMatching(
+ AutofillHostMsg_ShowPasswordSuggestions::ID);
+ ASSERT_TRUE(message);
+ Tuple4<autofill::FormFieldData,
+ gfx::RectF,
+ std::vector<base::string16>,
+ std::vector<base::string16> > args;
+ AutofillHostMsg_ShowPasswordSuggestions::Read(message, &args);
+ ASSERT_EQ(1u, args.c.size());
+ EXPECT_TRUE(args.c[0] == username);
+ }
+
+ void ExpectAllCredentials() {
+ std::set<base::string16> usernames;
+ usernames.insert(username1_);
+ usernames.insert(username2_);
+ usernames.insert(username3_);
+ usernames.insert(alternate_username3_);
+
+ const IPC::Message* message =
+ render_thread_->sink().GetFirstMessageMatching(
+ AutofillHostMsg_ShowPasswordSuggestions::ID);
+ ASSERT_TRUE(message);
+ Tuple4<autofill::FormFieldData,
+ gfx::RectF,
+ std::vector<base::string16>,
+ std::vector<base::string16> > args;
+ AutofillHostMsg_ShowPasswordSuggestions::Read(message, &args);
+ ASSERT_EQ(4u, args.c.size());
+ std::set<base::string16>::iterator it;
+
+ for (int i = 0; i < 4; i++) {
+ it = usernames.find(args.c[i]);
+ EXPECT_TRUE(it != usernames.end());
+ if (it != usernames.end())
+ usernames.erase(it);
+ }
+
+ EXPECT_TRUE(usernames.empty());
+
+ render_thread_->sink().ClearMessages();
+ }
+
base::string16 username1_;
base::string16 username2_;
base::string16 username3_;
@@ -652,10 +722,10 @@ TEST_F(PasswordAutofillAgentTest, InlineAutocomplete) {
// Simulate the browser sending back the login info.
SimulateOnFillPasswordForm(fill_data_);
- // Clear the text fields to start fresh.
ClearUsernameAndPasswordFields();
- // Simulate the user typing in the first letter of 'alice', a stored username.
+ // Simulate the user typing in the first letter of 'alice', a stored
+ // username.
SimulateUsernameChange("a", true);
// Both the username and password text fields should reflect selection of the
// stored login.
@@ -829,16 +899,21 @@ TEST_F(PasswordAutofillAgentTest, IframeNoFillTest) {
WebInputElement password_input = password_element.to<WebInputElement>();
ASSERT_FALSE(username_element.isNull());
- CheckTextFieldsStateForElements(username_input, "", false,
- password_input, "", false);
+ CheckTextFieldsStateForElements(
+ username_input, "", false, password_input, "", false, false);
- // Simulate the user typing in the username in the iframe, which should cause
+ // Simulate the user typing in the username in the iframe which should cause
// an autofill.
- SimulateUsernameChangeForElement(kAliceUsername, true,
- iframe, username_input);
-
- CheckTextFieldsStateForElements(username_input, kAliceUsername, true,
- password_input, kAlicePassword, true);
+ SimulateUsernameChangeForElement(
+ kAliceUsername, true, iframe, username_input, true);
+
+ CheckTextFieldsStateForElements(username_input,
+ kAliceUsername,
+ true,
+ password_input,
+ kAlicePassword,
+ true,
+ false);
}
// Tests that a password will only be filled as a suggested and will not be
@@ -1000,9 +1075,10 @@ TEST_F(PasswordAutofillAgentTest,
// set directly.
SimulateElementClick(kUsernameName);
- // Simulate the user entering her username.
- username_element_.setValue(ASCIIToUTF16(kAliceUsername), true);
- autofill_agent_->textFieldDidEndEditing(username_element_);
+ // Simulate the user entering her username and selecting the matching autofill
+ // from the dropdown.
+ SimulateUsernameChange(kAliceUsername, true, true);
+ SimulateSuggestionChoice(username_element_);
// The username and password should now have been autocompleted.
CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
@@ -1340,4 +1416,62 @@ TEST_F(PasswordAutofillAgentTest, SendsLoggingStateUpdatePingOnConstruction) {
EXPECT_TRUE(message);
}
+// Tests that one user click on a username field is sufficient to bring up a
+// credential suggestion popup, and the user can autocomplete the password by
+// selecting the credential from the popup.
+TEST_F(PasswordAutofillAgentTest, ClickAndSelect) {
+ // SimulateElementClick() is called so that a user gesture is actually made
+ // and the password can be filled. However, SimulateElementClick() does not
+ // actually lead to the AutofillAgent's InputElementClicked() method being
+ // called, so SimulateSuggestionChoice has to manually call
+ // InputElementClicked().
+ ClearUsernameAndPasswordFields();
+ SimulateOnFillPasswordForm(fill_data_);
+ SimulateElementClick(kUsernameName);
+ SimulateSuggestionChoice(username_element_);
+ ExpectAllCredentials();
+
+ CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
+}
+
+// Tests the autosuggestions that are given when the element is clicked.
+// Specifically, tests when the user clicks on the username element after page
+// load and the element is autofilled, when the user clicks on an element that
+// has a non-matching username, and when the user clicks on an element that's
+// already been autofilled and they've already modified.
+TEST_F(PasswordAutofillAgentTest, CredentialsOnClick) {
+ // Simulate the browser sending back the login info.
+ SimulateOnFillPasswordForm(fill_data_);
+
+ // Clear the text fields to start fresh.
+ ClearUsernameAndPasswordFields();
+
+ // Call SimulateElementClick() to produce a user gesture on the page so
+ // autofill will actually fill.
+ SimulateElementClick(kUsernameName);
+
+ // Simulate a user clicking on the username element. This should produce a
+ // message with all the usernames.
+ render_thread_->sink().ClearMessages();
+ autofill_agent_->FormControlElementClicked(username_element_, false);
+ ExpectAllCredentials();
+
+ // Now simulate a user typing in an unrecognized username and then
+ // clicking on the username element. This should also produce a message with
+ // all the usernames.
+ SimulateUsernameChange("baz", true);
+ render_thread_->sink().ClearMessages();
+ autofill_agent_->FormControlElementClicked(username_element_, true);
+ ExpectAllCredentials();
+
+ // Now simulate a user typing in the first letter of the username and then
+ // clicking on the username element. While the typing of the first letter will
+ // inline autocomplete, clicking on the element should still produce a full
+ // suggestion list.
+ SimulateUsernameChange("a", true);
+ render_thread_->sink().ClearMessages();
+ autofill_agent_->FormControlElementClicked(username_element_, true);
+ ExpectAllCredentials();
+}
+
} // namespace autofill
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index a9b5bad..c21ef55 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -318,8 +318,15 @@ void AutofillAgent::FormControlElementClicked(
if (!input_element && !IsTextAreaElement(element))
return;
- if (was_focused)
- ShowSuggestions(element, true, false, true, false);
+ bool show_full_suggestion_list = element.isAutofilled() || was_focused;
+ bool show_password_suggestions_only = !was_focused;
+ ShowSuggestions(element,
+ true,
+ false,
+ true,
+ false,
+ show_full_suggestion_list,
+ show_password_suggestions_only);
}
void AutofillAgent::FormControlElementLostFocus() {
@@ -375,7 +382,7 @@ void AutofillAgent::TextFieldDidChangeImpl(
}
}
- ShowSuggestions(element, false, true, false, false);
+ ShowSuggestions(element, false, true, false, false, false, false);
FormData form;
FormFieldData field;
@@ -397,11 +404,11 @@ void AutofillAgent::textFieldDidReceiveKeyDown(const WebInputElement& element,
if (event.windowsKeyCode == ui::VKEY_DOWN ||
event.windowsKeyCode == ui::VKEY_UP)
- ShowSuggestions(element, true, true, true, false);
+ ShowSuggestions(element, true, true, true, false, false, false);
}
void AutofillAgent::openTextDataListChooser(const WebInputElement& element) {
- ShowSuggestions(element, true, false, false, true);
+ ShowSuggestions(element, true, false, false, true, false, false);
}
void AutofillAgent::firstUserGestureObserved() {
@@ -553,9 +560,13 @@ void AutofillAgent::ShowSuggestions(const WebFormControlElement& element,
bool autofill_on_empty_values,
bool requires_caret_at_end,
bool display_warning_if_disabled,
- bool datalist_only) {
+ bool datalist_only,
+ bool show_full_suggestion_list,
+ bool show_password_suggestions_only) {
if (!element.isEnabled() || element.isReadOnly())
return;
+ if (!datalist_only && !element.suggestedValue().isEmpty())
+ return;
const WebInputElement* input_element = toWebInputElement(&element);
if (input_element) {
@@ -584,8 +595,10 @@ void AutofillAgent::ShowSuggestions(const WebFormControlElement& element,
}
element_ = element;
- if (input_element &&
- password_autofill_agent_->ShowSuggestions(*input_element)) {
+ if (IsAutofillableInputElement(input_element) &&
+ (password_autofill_agent_->ShowSuggestions(*input_element,
+ show_full_suggestion_list) ||
+ show_password_suggestions_only)) {
is_popup_possibly_visible_ = true;
return;
}
diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h
index aa38413..8623ca0 100644
--- a/components/autofill/content/renderer/autofill_agent.h
+++ b/components/autofill/content/renderer/autofill_agent.h
@@ -141,11 +141,18 @@ class AutofillAgent : public content::RenderViewObserver,
// |datalist_only| specifies whether all of <datalist> suggestions and no
// autofill suggestions are shown. |autofill_on_empty_values| and
// |requires_caret_at_end| are ignored if |datalist_only| is true.
+ // |show_full_suggestion_list| specifies that all autofill suggestions should
+ // be shown and none should be elided because of the current value of
+ // |element| (relevant for inline autocomplete).
+ // |show_password_suggestions_only| specifies that only show a suggestions box
+ // if |element| is part of a password form, otherwise show no suggestions.
void ShowSuggestions(const blink::WebFormControlElement& element,
bool autofill_on_empty_values,
bool requires_caret_at_end,
bool display_warning_if_disabled,
- bool datalist_only);
+ bool datalist_only,
+ bool show_full_suggestion_list,
+ bool show_password_suggestions_only);
// Queries the browser for Autocomplete and Autofill suggestions for the given
// |element|.
@@ -245,6 +252,7 @@ class AutofillAgent : public content::RenderViewObserver,
FRIEND_TEST_ALL_PREFIXES(
PasswordAutofillAgentTest,
PasswordAutofillTriggersOnChangeEventsWaitForUsername);
+ FRIEND_TEST_ALL_PREFIXES(PasswordAutofillAgentTest, CredentialsOnClick);
FRIEND_TEST_ALL_PREFIXES(RequestAutocompleteRendererTest,
NoCancelOnMainFrameNavigateAfterDone);
FRIEND_TEST_ALL_PREFIXES(RequestAutocompleteRendererTest,
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index 276b976..629627d 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -337,7 +337,7 @@ bool PasswordAutofillAgent::TextDidChangeInTextField(
// But refresh the popup. Note, since this is ours, return true to signal
// no further processing is required.
if (iter->second.backspace_pressed_last) {
- ShowSuggestionPopup(iter->second.fill_data, username);
+ ShowSuggestionPopup(iter->second.fill_data, username, false);
return true;
}
@@ -435,7 +435,8 @@ bool PasswordAutofillAgent::DidClearAutofillSelection(
}
bool PasswordAutofillAgent::ShowSuggestions(
- const blink::WebInputElement& element) {
+ const blink::WebInputElement& element,
+ bool show_all) {
LoginToPasswordInfoMap::const_iterator iter =
login_to_password_info_.find(element);
if (iter == login_to_password_info_.end())
@@ -449,7 +450,7 @@ bool PasswordAutofillAgent::ShowSuggestions(
!IsElementAutocompletable(iter->second.password_field))
return true;
- return ShowSuggestionPopup(iter->second.fill_data, element);
+ return ShowSuggestionPopup(iter->second.fill_data, element, show_all);
}
bool PasswordAutofillAgent::OriginCanAccessPasswordManager(
@@ -810,8 +811,10 @@ void PasswordAutofillAgent::GetSuggestions(
const PasswordFormFillData& fill_data,
const base::string16& input,
std::vector<base::string16>* suggestions,
- std::vector<base::string16>* realms) {
- if (StartsWith(fill_data.basic_data.fields[0].value, input, false)) {
+ std::vector<base::string16>* realms,
+ bool show_all) {
+ if (show_all ||
+ StartsWith(fill_data.basic_data.fields[0].value, input, false)) {
suggestions->push_back(fill_data.basic_data.fields[0].value);
realms->push_back(base::UTF8ToUTF16(fill_data.preferred_realm));
}
@@ -820,7 +823,7 @@ void PasswordAutofillAgent::GetSuggestions(
fill_data.additional_logins.begin();
iter != fill_data.additional_logins.end();
++iter) {
- if (StartsWith(iter->first, input, false)) {
+ if (show_all || StartsWith(iter->first, input, false)) {
suggestions->push_back(iter->first);
realms->push_back(base::UTF8ToUTF16(iter->second.realm));
}
@@ -831,7 +834,7 @@ void PasswordAutofillAgent::GetSuggestions(
iter != fill_data.other_possible_usernames.end();
++iter) {
for (size_t i = 0; i < iter->second.size(); ++i) {
- if (StartsWith(iter->second[i], input, false)) {
+ if (show_all || StartsWith(iter->second[i], input, false)) {
usernames_usage_ = OTHER_POSSIBLE_USERNAME_SHOWN;
suggestions->push_back(iter->second[i]);
realms->push_back(base::UTF8ToUTF16(iter->first.realm));
@@ -842,7 +845,8 @@ void PasswordAutofillAgent::GetSuggestions(
bool PasswordAutofillAgent::ShowSuggestionPopup(
const PasswordFormFillData& fill_data,
- const blink::WebInputElement& user_input) {
+ const blink::WebInputElement& user_input,
+ bool show_all) {
blink::WebFrame* frame = user_input.document().frame();
if (!frame)
return false;
@@ -853,7 +857,8 @@ bool PasswordAutofillAgent::ShowSuggestionPopup(
std::vector<base::string16> suggestions;
std::vector<base::string16> realms;
- GetSuggestions(fill_data, user_input.value(), &suggestions, &realms);
+ GetSuggestions(
+ fill_data, user_input.value(), &suggestions, &realms, show_all);
DCHECK_EQ(suggestions.size(), realms.size());
FormData form;
@@ -1013,7 +1018,7 @@ void PasswordAutofillAgent::PerformInlineAutocomplete(
}
// Show the popup with the list of available usernames.
- ShowSuggestionPopup(fill_data, username);
+ ShowSuggestionPopup(fill_data, username, false);
#if !defined(OS_ANDROID)
// Fill the user and password field with the most relevant match. Android
diff --git a/components/autofill/content/renderer/password_autofill_agent.h b/components/autofill/content/renderer/password_autofill_agent.h
index 231f277..e7a1126 100644
--- a/components/autofill/content/renderer/password_autofill_agent.h
+++ b/components/autofill/content/renderer/password_autofill_agent.h
@@ -54,9 +54,11 @@ class PasswordAutofillAgent : public content::RenderViewObserver {
// their previous filled state. Return false if no login information was
// found for the form.
bool DidClearAutofillSelection(const blink::WebNode& node);
- // Shows an Autofill popup with username suggestions for |element|.
+ // Shows an Autofill popup with username suggestions for |element|. If
+ // |show_all| is |true|, will show all possible suggestions for that element,
+ // otherwise shows suggestions based on current value of |element|.
// Returns true if any suggestions were shown, false otherwise.
- bool ShowSuggestions(const blink::WebInputElement& element);
+ bool ShowSuggestions(const blink::WebInputElement& element, bool show_all);
// Called when new form controls are inserted.
void OnDynamicFormsSeen(blink::WebFrame* frame);
@@ -146,10 +148,12 @@ class PasswordAutofillAgent : public content::RenderViewObserver {
void GetSuggestions(const PasswordFormFillData& fill_data,
const base::string16& input,
std::vector<base::string16>* suggestions,
- std::vector<base::string16>* realms);
+ std::vector<base::string16>* realms,
+ bool show_all);
bool ShowSuggestionPopup(const PasswordFormFillData& fill_data,
- const blink::WebInputElement& user_input);
+ const blink::WebInputElement& user_input,
+ bool show_all);
// Attempts to fill |username_element| and |password_element| with the
// |fill_data|. Will use the data corresponding to the preferred username,