Chromium Code Reviews| Index: components/autofill/content/renderer/password_autofill_agent.cc |
| diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc |
| index d23bbf1242dfed1c24e2b018cfc50db8a070bba8..038892db22eb3ffd42adefdc9fbba8720d4f5598 100644 |
| --- a/components/autofill/content/renderer/password_autofill_agent.cc |
| +++ b/components/autofill/content/renderer/password_autofill_agent.cc |
| @@ -246,34 +246,42 @@ PasswordAutofillAgent::PasswordValueGatekeeper::PasswordValueGatekeeper() |
| PasswordAutofillAgent::PasswordValueGatekeeper::~PasswordValueGatekeeper() { |
| } |
| -void PasswordAutofillAgent::PasswordValueGatekeeper::RegisterElement( |
| - blink::WebInputElement* element) { |
| - if (was_user_gesture_seen_) |
| - ShowValue(element); |
| - else |
| - elements_.push_back(*element); |
| +void PasswordAutofillAgent::PasswordValueGatekeeper::RegisterElementInfo( |
| + PasswordInfo* element_info) { |
| + DCHECK(element_info); |
| + if (was_user_gesture_seen_) { |
| + ShowValue(&element_info->password_field); |
| + } else { |
| + elements_info_.insert(element_info); |
| + } |
| +} |
| + |
| +void PasswordAutofillAgent::PasswordValueGatekeeper::UnregisterElementInfo( |
| + PasswordInfo* element_info) { |
| + elements_info_.erase(element_info); |
| } |
| void PasswordAutofillAgent::PasswordValueGatekeeper::OnUserGesture() { |
| was_user_gesture_seen_ = true; |
| - for (std::vector<blink::WebInputElement>::iterator it = elements_.begin(); |
| - it != elements_.end(); |
| + for (std::set<PasswordInfo*>::iterator it = elements_info_.begin(); |
| + it != elements_info_.end(); |
| ++it) { |
| - ShowValue(&(*it)); |
| + if (!(*it)->wait_for_username_change) |
|
engedy
2014/07/31 17:14:46
I would suggest keeping this logic out of Password
vabr (Chromium)
2014/08/04 15:10:07
I agree. I also realised that the callers, which n
|
| + ShowValue(&(*it)->password_field); |
| } |
| - elements_.clear(); |
| + elements_info_.clear(); |
| } |
| void PasswordAutofillAgent::PasswordValueGatekeeper::Reset() { |
| was_user_gesture_seen_ = false; |
| - elements_.clear(); |
| + elements_info_.clear(); |
| } |
| void PasswordAutofillAgent::PasswordValueGatekeeper::ShowValue( |
| blink::WebInputElement* element) { |
| - if (!element->isNull() && !element->suggestedValue().isNull()) |
| + if (!element->isNull() && !element->suggestedValue().isEmpty()) |
| element->setValue(element->suggestedValue(), true); |
| } |
| @@ -284,14 +292,15 @@ bool PasswordAutofillAgent::TextFieldDidEndEditing( |
| if (iter == login_to_password_info_.end()) |
|
engedy
2014/07/31 17:14:46
nit: I would personally add an alias here and in T
vabr (Chromium)
2014/08/04 15:10:07
Done.
|
| return false; |
| - const PasswordFormFillData& fill_data = iter->second.fill_data; |
| + // Don't let autofill overwrite an explicit change made by the user. |
| + if (iter->second->wait_for_username_change) |
| + return false; |
| // If wait_for_username is false, we should have filled when the text changed. |
| - if (!fill_data.wait_for_username) |
| + if (!iter->second->fill_data.wait_for_username) |
| return false; |
| - blink::WebInputElement password = iter->second.password_field; |
| - if (!IsElementEditable(password)) |
| + if (!IsElementEditable(iter->second->password_field)) |
| return false; |
| blink::WebInputElement username = element; // We need a non-const. |
| @@ -299,8 +308,7 @@ bool PasswordAutofillAgent::TextFieldDidEndEditing( |
| // Do not set selection when ending an editing session, otherwise it can |
| // mess with focus. |
| FillUserNameAndPassword(&username, |
| - &password, |
| - fill_data, |
| + iter->second, |
| true /* exact_username_match */, |
| false /* set_selection */); |
| return true; |
| @@ -308,6 +316,9 @@ bool PasswordAutofillAgent::TextFieldDidEndEditing( |
| bool PasswordAutofillAgent::TextDidChangeInTextField( |
| const blink::WebInputElement& element) { |
| + // TODO(vabr): Get a mutable argument instead. http://crbug.com/397083 |
| + blink::WebInputElement mutable_element = element; // We need a non-const. |
| + |
| if (element.isPasswordField()) { |
| // Some login forms have event handlers that put a hash of the password into |
| // a hidden field and then clear the password (http://crbug.com/28910, |
| @@ -317,6 +328,15 @@ bool PasswordAutofillAgent::TextDidChangeInTextField( |
| // password will be saved here. |
| ProvisionallySavePassword( |
| element.document().frame(), element.form(), RESTRICTION_NONE); |
| + |
| + PasswordToLoginMap::iterator iter = password_to_username_.find(element); |
| + if (iter != password_to_username_.end()) { |
| + // The user changed the password after it was autofilled. Flipping the |
| + // flag below to true makes sure it is not overwritten by autofill |
| + // re-firing for the same username. |
| + login_to_password_info_[iter->second]->wait_for_username_change = true; |
|
engedy
2014/07/31 17:14:46
Perhaps this would be caught by tests, but we shou
vabr (Chromium)
2014/08/04 15:10:06
I'm reasonably certain that TextDidChangeInTextFie
|
| + mutable_element.setAutofilled(false); |
| + } |
| return false; |
| } |
| @@ -327,17 +347,17 @@ bool PasswordAutofillAgent::TextDidChangeInTextField( |
| // The input text is being changed, so any autofilled password is now |
| // outdated. |
| - blink::WebInputElement username = element; // We need a non-const. |
| - username.setAutofilled(false); |
| + mutable_element.setAutofilled(false); |
| + iter->second->wait_for_username_change = false; |
|
engedy
2014/07/31 17:14:46
Hmm, let us discuss this logic tomorrow in person,
vabr (Chromium)
2014/08/04 15:10:07
Acknowledged.
|
| - blink::WebInputElement password = iter->second.password_field; |
| + blink::WebInputElement password = iter->second->password_field; |
| if (password.isAutofilled()) { |
| password.setValue(base::string16(), true); |
| password.setAutofilled(false); |
| } |
| // If wait_for_username is true we will fill when the username loses focus. |
| - if (iter->second.fill_data.wait_for_username) |
| + if (iter->second->fill_data.wait_for_username) |
| return false; |
| if (!element.isText() || !IsElementAutocompletable(element) || |
| @@ -348,8 +368,8 @@ bool PasswordAutofillAgent::TextDidChangeInTextField( |
| // Don't inline autocomplete if the user is deleting, that would be confusing. |
| // 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, false); |
| + if (iter->second->backspace_pressed_last) { |
| + ShowSuggestionPopup(iter->second->fill_data, element, false); |
| return true; |
| } |
| @@ -362,7 +382,7 @@ bool PasswordAutofillAgent::TextDidChangeInTextField( |
| return false; |
| // The caret position should have already been updated. |
| - PerformInlineAutocomplete(element, password, iter->second.fill_data); |
| + PerformInlineAutocomplete(&mutable_element, iter->second); |
| return true; |
| } |
| @@ -378,7 +398,7 @@ bool PasswordAutofillAgent::TextFieldHandlingKeyDown( |
| return false; |
| int win_key_code = event.windowsKeyCode; |
| - iter->second.backspace_pressed_last = |
| + iter->second->backspace_pressed_last = |
| (win_key_code == ui::VKEY_BACK || win_key_code == ui::VKEY_DELETE); |
| return true; |
| } |
| @@ -388,21 +408,22 @@ bool PasswordAutofillAgent::FillSuggestion( |
| const blink::WebString& username, |
| const blink::WebString& password) { |
| blink::WebInputElement username_element; |
| - PasswordInfo password_info; |
| + PasswordInfo* password_info; |
| if (!FindLoginInfo(node, &username_element, &password_info) || |
| !IsElementAutocompletable(username_element) || |
| - !IsElementAutocompletable(password_info.password_field)) { |
| + !IsElementAutocompletable(password_info->password_field)) { |
| return false; |
| } |
| - base::string16 current_username = username_element.value(); |
| + if (username_element.value() != username_element.value()) |
| + password_info->wait_for_username_change = false; |
| username_element.setValue(username, true); |
| username_element.setAutofilled(true); |
| username_element.setSelectionRange(username.length(), username.length()); |
| - password_info.password_field.setValue(password, true); |
| - password_info.password_field.setAutofilled(true); |
| + password_info->password_field.setValue(password, true); |
| + password_info->password_field.setAutofilled(true); |
| return true; |
| } |
| @@ -412,11 +433,11 @@ bool PasswordAutofillAgent::PreviewSuggestion( |
| const blink::WebString& username, |
| const blink::WebString& password) { |
| blink::WebInputElement username_element; |
| - PasswordInfo password_info; |
| + PasswordInfo* password_info; |
| if (!FindLoginInfo(node, &username_element, &password_info) || |
| !IsElementAutocompletable(username_element) || |
| - !IsElementAutocompletable(password_info.password_field)) { |
| + !IsElementAutocompletable(password_info->password_field)) { |
| return false; |
| } |
| @@ -428,9 +449,9 @@ bool PasswordAutofillAgent::PreviewSuggestion( |
| username_selection_start_, |
| username_element.suggestedValue().length()); |
| - was_password_autofilled_ = password_info.password_field.isAutofilled(); |
| - password_info.password_field.setSuggestedValue(password); |
| - password_info.password_field.setAutofilled(true); |
| + was_password_autofilled_ = password_info->password_field.isAutofilled(); |
| + password_info->password_field.setSuggestedValue(password); |
| + password_info->password_field.setAutofilled(true); |
| return true; |
| } |
| @@ -438,11 +459,11 @@ bool PasswordAutofillAgent::PreviewSuggestion( |
| bool PasswordAutofillAgent::DidClearAutofillSelection( |
| const blink::WebNode& node) { |
| blink::WebInputElement username_element; |
| - PasswordInfo password_info; |
| + PasswordInfo* password_info; |
| if (!FindLoginInfo(node, &username_element, &password_info)) |
| return false; |
| - ClearPreview(&username_element, &password_info.password_field); |
| + ClearPreview(&username_element, &password_info->password_field); |
| return true; |
| } |
| @@ -459,10 +480,10 @@ bool PasswordAutofillAgent::ShowSuggestions( |
| // password form and that the request to show suggestions has been handled (as |
| // a no-op). |
| if (!IsElementAutocompletable(element) || |
| - !IsElementAutocompletable(iter->second.password_field)) |
| + !IsElementAutocompletable(iter->second->password_field)) |
| return true; |
| - return ShowSuggestionPopup(iter->second.fill_data, element, show_all); |
| + return ShowSuggestionPopup(iter->second->fill_data, element, show_all); |
| } |
| bool PasswordAutofillAgent::OriginCanAccessPasswordManager( |
| @@ -796,21 +817,23 @@ void PasswordAutofillAgent::OnFillPasswordForm( |
| blink::WebInputElement password_element = |
| form_elements->input_elements[form_data.basic_data.fields[1].name]; |
| - // If wait_for_username is true, we don't want to initially fill the form |
| - // until the user types in a valid username. |
| - if (!form_data.wait_for_username) |
| - FillFormOnPasswordRecieved(form_data, username_element, password_element); |
| - |
| // We might have already filled this form if there are two <form> elements |
| // with identical markup. |
| if (login_to_password_info_.find(username_element) != |
| login_to_password_info_.end()) |
| continue; |
| - PasswordInfo password_info; |
| - password_info.fill_data = form_data; |
| - password_info.password_field = password_element; |
| - login_to_password_info_[username_element] = password_info; |
| + scoped_ptr<PasswordInfo> password_info(new PasswordInfo); |
| + password_info->fill_data = form_data; |
| + password_info->password_field = password_element; |
| + login_to_password_info_[username_element] = password_info.get(); |
| + password_to_username_[password_element] = username_element; |
| + // If wait_for_username is true, we don't want to initially fill the form |
| + // until the user types in a valid username. |
| + if (!form_data.wait_for_username) |
| + FillFormOnPasswordRecieved(&username_element, password_info.get()); |
| + password_infos_[username_element.document().frame()].push_back( |
| + make_linked_ptr(password_info.release())); |
| FormData form; |
| FormFieldData field; |
| @@ -828,6 +851,10 @@ void PasswordAutofillAgent::OnSetLoggingState(bool active) { |
| //////////////////////////////////////////////////////////////////////////////// |
| // PasswordAutofillAgent, private: |
| +PasswordAutofillAgent::PasswordInfo::PasswordInfo() |
| + : backspace_pressed_last(false), wait_for_username_change(false) { |
| +} |
| + |
| void PasswordAutofillAgent::GetSuggestions( |
| const PasswordFormFillData& fill_data, |
| const base::string16& input, |
| @@ -901,45 +928,46 @@ bool PasswordAutofillAgent::ShowSuggestionPopup( |
| } |
| void PasswordAutofillAgent::FillFormOnPasswordRecieved( |
| - const PasswordFormFillData& fill_data, |
| - blink::WebInputElement username_element, |
| - blink::WebInputElement password_element) { |
| + blink::WebInputElement* username_element, |
| + PasswordInfo* password_info) { |
| + blink::WebInputElement* password_element = &password_info->password_field; |
| // Do not fill if the password field is in an iframe. |
| - DCHECK(password_element.document().frame()); |
| - if (password_element.document().frame()->parent()) |
| + DCHECK(password_element->document().frame()); |
| + if (password_element->document().frame()->parent()) |
| return; |
| if (!ShouldIgnoreAutocompleteOffForPasswordFields() && |
| - !username_element.form().autoComplete()) |
| + !username_element->form().autoComplete()) |
| return; |
| // If we can't modify the password, don't try to set the username |
| - if (!IsElementAutocompletable(password_element)) |
| + if (!IsElementAutocompletable(*password_element)) |
| return; |
| // Try to set the username to the preferred name, but only if the field |
| // can be set and isn't prefilled. |
| - if (IsElementAutocompletable(username_element) && |
| - username_element.value().isEmpty()) { |
| + if (IsElementAutocompletable(*username_element) && |
| + username_element->value().isEmpty()) { |
| // TODO(tkent): Check maxlength and pattern. |
| - username_element.setValue(fill_data.basic_data.fields[0].value, true); |
| + username_element->setValue( |
| + password_info->fill_data.basic_data.fields[0].value, true); |
| } |
| // Fill if we have an exact match for the username. Note that this sets |
| // username to autofilled. |
| - FillUserNameAndPassword(&username_element, |
| - &password_element, |
| - fill_data, |
| + FillUserNameAndPassword(username_element, |
| + password_info, |
| true /* exact_username_match */, |
| false /* set_selection */); |
| } |
| bool PasswordAutofillAgent::FillUserNameAndPassword( |
| blink::WebInputElement* username_element, |
| - blink::WebInputElement* password_element, |
| - const PasswordFormFillData& fill_data, |
| + PasswordInfo* password_info, |
| bool exact_username_match, |
| bool set_selection) { |
| + blink::WebInputElement password_element = password_info->password_field; |
| + const PasswordFormFillData& fill_data = password_info->fill_data; |
| base::string16 current_username = username_element->value(); |
| // username and password will contain the match found if any. |
| base::string16 username; |
| @@ -992,7 +1020,7 @@ bool PasswordAutofillAgent::FillUserNameAndPassword( |
| // fields. |
| // Don't fill username if password can't be set. |
| - if (!IsElementAutocompletable(*password_element)) { |
| + if (!IsElementAutocompletable(password_element)) { |
| return false; |
| } |
| @@ -1014,39 +1042,36 @@ bool PasswordAutofillAgent::FillUserNameAndPassword( |
| // Wait to fill in the password until a user gesture occurs. This is to make |
| // sure that we do not fill in the DOM with a password until we believe the |
| // user is intentionally interacting with the page. |
| - password_element->setSuggestedValue(password); |
| - gatekeeper_.RegisterElement(password_element); |
| + password_element.setSuggestedValue(password); |
| + gatekeeper_.RegisterElementInfo(password_info); |
| - password_element->setAutofilled(true); |
| + password_element.setAutofilled(true); |
| return true; |
| } |
| void PasswordAutofillAgent::PerformInlineAutocomplete( |
| - const blink::WebInputElement& username_input, |
| - const blink::WebInputElement& password_input, |
| - const PasswordFormFillData& fill_data) { |
| - DCHECK(!fill_data.wait_for_username); |
| + blink::WebInputElement* username, |
| + PasswordInfo* password_info) { |
| + DCHECK(!password_info->fill_data.wait_for_username); |
| - // We need non-const versions of the username and password inputs. |
| - blink::WebInputElement username = username_input; |
| - blink::WebInputElement password = password_input; |
| + blink::WebInputElement password = password_info->password_field; |
| // Don't inline autocomplete if the caret is not at the end. |
| // TODO(jcivelli): is there a better way to test the caret location? |
| - if (username.selectionStart() != username.selectionEnd() || |
| - username.selectionEnd() != static_cast<int>(username.value().length())) { |
| + if (username->selectionStart() != username->selectionEnd() || |
| + username->selectionEnd() != |
| + static_cast<int>(username->value().length())) { |
| return; |
| } |
| // Show the popup with the list of available usernames. |
| - ShowSuggestionPopup(fill_data, username, false); |
| + ShowSuggestionPopup(password_info->fill_data, *username, false); |
| #if !defined(OS_ANDROID) |
| // Fill the user and password field with the most relevant match. Android |
| // only fills in the fields after the user clicks on the suggestion popup. |
| - FillUserNameAndPassword(&username, |
| - &password, |
| - fill_data, |
| + FillUserNameAndPassword(username, |
| + password_info, |
| false /* exact_username_match */, |
| true /* set_selection */); |
| #endif |
| @@ -1055,11 +1080,15 @@ void PasswordAutofillAgent::PerformInlineAutocomplete( |
| void PasswordAutofillAgent::FrameClosing(const blink::WebFrame* frame) { |
| for (LoginToPasswordInfoMap::iterator iter = login_to_password_info_.begin(); |
| iter != login_to_password_info_.end();) { |
| - if (iter->first.document().frame() == frame) |
| + if (iter->first.document().frame() == frame) { |
| + password_to_username_.erase(iter->second->password_field); |
| + gatekeeper_.UnregisterElementInfo(iter->second); |
| login_to_password_info_.erase(iter++); |
| - else |
| + } else { |
| ++iter; |
| + } |
| } |
| + password_infos_.erase(frame); |
| for (FrameToPasswordFormMap::iterator iter = |
| provisionally_saved_forms_.begin(); |
| iter != provisionally_saved_forms_.end();) { |
| @@ -1072,7 +1101,7 @@ void PasswordAutofillAgent::FrameClosing(const blink::WebFrame* frame) { |
| bool PasswordAutofillAgent::FindLoginInfo(const blink::WebNode& node, |
| blink::WebInputElement* found_input, |
| - PasswordInfo* found_password) { |
| + PasswordInfo** found_password) { |
| if (!node.isElementNode()) |
| return false; |