#include "components/autofill/content/common/autofill_messages.h"
#include "components/autofill/content/renderer/form_autofill_util.h"
#include "components/autofill/content/renderer/password_form_conversion_utils.h"
+#include "components/autofill/content/renderer/renderer_save_password_progress_logger.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/password_autofill_util.h"
#include "components/autofill/core/common/password_form.h"
#include "components/autofill/core/common/password_form_fill_data.h"
+#include "content/public/common/page_transition_types.h"
+#include "content/public/renderer/document_state.h"
+#include "content/public/renderer/navigation_state.h"
#include "content/public/renderer/render_view.h"
#include "third_party/WebKit/public/platform/WebVector.h"
#include "third_party/WebKit/public/web/WebAutofillClient.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebElement.h"
#include "third_party/WebKit/public/web/WebFormElement.h"
-#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebNode.h"
#include "third_party/WebKit/public/web/WebNodeList.h"
-#include "third_party/WebKit/public/web/WebPasswordFormData.h"
#include "third_party/WebKit/public/web/WebSecurityOrigin.h"
#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "ui/events/keycodes/keyboard_codes.h"
+#include "url/gurl.h"
namespace autofill {
namespace {
static const size_t kMaximumTextSizeForAutocomplete = 1000;
// Maps element names to the actual elements to simplify form filling.
-typedef std::map<base::string16, blink::WebInputElement>
- FormInputElementMap;
+typedef std::map<base::string16, blink::WebInputElement> FormInputElementMap;
+
+// Use the shorter name when referencing SavePasswordProgressLogger::StringID
+// values to spare line breaks. The code provides enough context for that
+// already.
+typedef SavePasswordProgressLogger Logger;
// Utility struct for form lookup and autofill. When we parse the DOM to look up
// a form, in addition to action and origin URL's we have to compare all
return element.isEnabled() && !element.isReadOnly();
}
-void SetElementAutofilled(blink::WebInputElement* element, bool autofilled) {
- if (element->isAutofilled() == autofilled)
- return;
- element->setAutofilled(autofilled);
- // Notify any changeEvent listeners.
- element->dispatchFormControlChangeEvent();
-}
-
bool DoUsernamesMatch(const base::string16& username1,
const base::string16& username2,
bool exact_match) {
return false;
}
+// Log a message including the name, method and action of |form|.
+void LogHTMLForm(SavePasswordProgressLogger* logger,
+ SavePasswordProgressLogger::StringID message_id,
+ const blink::WebFormElement& form) {
+ logger->LogHTMLForm(message_id,
+ form.name().utf8(),
+ form.method().utf8(),
+ GURL(form.action().utf8()));
+}
+
} // namespace
////////////////////////////////////////////////////////////////////////////////
: content::RenderViewObserver(render_view),
usernames_usage_(NOTHING_TO_AUTOFILL),
web_view_(render_view->GetWebView()),
+ logging_state_active_(false),
weak_ptr_factory_(this) {
}
-PasswordAutofillAgent::~PasswordAutofillAgent() {}
+PasswordAutofillAgent::~PasswordAutofillAgent() {
+}
PasswordAutofillAgent::PasswordValueGatekeeper::PasswordValueGatekeeper()
- : was_user_gesture_seen_(false) {}
+ : was_user_gesture_seen_(false) {
+}
-PasswordAutofillAgent::PasswordValueGatekeeper::~PasswordValueGatekeeper() {}
+PasswordAutofillAgent::PasswordValueGatekeeper::~PasswordValueGatekeeper() {
+}
void PasswordAutofillAgent::PasswordValueGatekeeper::RegisterElement(
blink::WebInputElement* element) {
if (iter == login_to_password_info_.end())
return false;
- const PasswordFormFillData& fill_data =
- iter->second.fill_data;
+ const PasswordFormFillData& fill_data = iter->second.fill_data;
// If wait_for_username is false, we should have filled when the text changed.
if (!fill_data.wait_for_username)
// Do not set selection when ending an editing session, otherwise it can
// mess with focus.
- FillUserNameAndPassword(&username, &password, fill_data,
+ FillUserNameAndPassword(&username,
+ &password,
+ fill_data,
true /* exact_username_match */,
false /* set_selection */);
return true;
// 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);
+
blink::WebInputElement password = iter->second.password_field;
- SetElementAutofilled(&username, false);
if (password.isAutofilled()) {
- password.setValue(base::string16());
- SetElementAutofilled(&password, false);
+ password.setValue(base::string16(), true);
+ password.setAutofilled(false);
}
// If wait_for_username is true we will fill when the username loses focus.
return true;
}
-bool PasswordAutofillAgent::DidAcceptAutofillSuggestion(
+bool PasswordAutofillAgent::AcceptSuggestion(
const blink::WebNode& node,
- const blink::WebString& username) {
- blink::WebInputElement input;
- PasswordInfo password;
- if (!FindLoginInfo(node, &input, &password))
+ const blink::WebString& username,
+ const blink::WebString& password) {
+ blink::WebInputElement username_element;
+ PasswordInfo password_info;
+
+ if (!FindLoginInfo(node, &username_element, &password_info) ||
+ !IsElementAutocompletable(username_element) ||
+ !IsElementAutocompletable(password_info.password_field)) {
return false;
+ }
+
+ base::string16 current_username = username_element.value();
+ 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);
- // Set the incoming |username| in the text field and |FillUserNameAndPassword|
- // will do the rest.
- input.setValue(username, true);
- return FillUserNameAndPassword(&input, &password.password_field,
- password.fill_data,
- true /* exact_username_match */,
- true /* set_selection */);
+ return true;
}
bool PasswordAutofillAgent::DidClearAutofillSelection(
SendPasswordForms(frame, false /* only_visible */);
}
+void PasswordAutofillAgent::FirstUserGestureObserved() {
+ gatekeeper_.OnUserGesture();
+}
+
void PasswordAutofillAgent::SendPasswordForms(blink::WebFrame* frame,
bool only_visible) {
+ scoped_ptr<RendererSavePasswordProgressLogger> logger;
+ // From the perspective of saving passwords, only calls with |only_visible|
+ // being true are important -- the decision whether to save the password is
+ // only made after visible forms are known, for failed login detection. Calls
+ // with |only_visible| false are important for password form autofill, which
+ // is currently not part of the logging.
+ if (only_visible && logging_state_active_) {
+ logger.reset(new RendererSavePasswordProgressLogger(this, routing_id()));
+ logger->LogMessage(Logger::STRING_SEND_PASSWORD_FORMS_METHOD);
+ }
+
// Make sure that this security origin is allowed to use password manager.
blink::WebSecurityOrigin origin = frame->document().securityOrigin();
- if (!OriginCanAccessPasswordManager(origin))
+ if (logger) {
+ logger->LogURL(Logger::STRING_SECURITY_ORIGIN,
+ GURL(origin.toString().utf8()));
+ }
+ if (!OriginCanAccessPasswordManager(origin)) {
+ if (logger) {
+ logger->LogMessage(Logger::STRING_SECURITY_ORIGIN_FAILURE);
+ logger->LogMessage(Logger::STRING_DECISION_DROP);
+ }
return;
+ }
// Checks whether the webpage is a redirect page or an empty page.
- if (IsWebpageEmpty(frame))
+ if (IsWebpageEmpty(frame)) {
+ if (logger) {
+ logger->LogMessage(Logger::STRING_WEBPAGE_EMPTY);
+ logger->LogMessage(Logger::STRING_DECISION_DROP);
+ }
return;
+ }
blink::WebVector<blink::WebFormElement> forms;
frame->document().forms(forms);
+ if (logger)
+ logger->LogNumber(Logger::STRING_NUMBER_OF_ALL_FORMS, forms.size());
std::vector<PasswordForm> password_forms;
for (size_t i = 0; i < forms.size(); ++i) {
const blink::WebFormElement& form = forms[i];
+ bool is_form_visible = IsWebNodeVisible(form);
+ if (logger) {
+ LogHTMLForm(logger.get(), Logger::STRING_FORM_FOUND_ON_PAGE, form);
+ logger->LogBoolean(Logger::STRING_FORM_IS_VISIBLE, is_form_visible);
+ }
// If requested, ignore non-rendered forms, e.g. those styled with
// display:none.
- if (only_visible && !IsWebNodeVisible(form))
+ if (only_visible && !is_form_visible)
continue;
scoped_ptr<PasswordForm> password_form(CreatePasswordForm(form));
- if (password_form.get())
+ if (password_form.get()) {
+ if (logger) {
+ logger->LogPasswordForm(Logger::STRING_FORM_IS_PASSWORD,
+ *password_form);
+ }
password_forms.push_back(*password_form);
+ }
}
if (password_forms.empty() && !only_visible) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(PasswordAutofillAgent, message)
IPC_MESSAGE_HANDLER(AutofillMsg_FillPasswordForm, OnFillPasswordForm)
+ IPC_MESSAGE_HANDLER(AutofillMsg_ChangeLoggingState, OnChangeLoggingState)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
void PasswordAutofillAgent::DidStartLoading() {
if (usernames_usage_ != NOTHING_TO_AUTOFILL) {
UMA_HISTOGRAM_ENUMERATION("PasswordManager.OtherPossibleUsernamesUsage",
- usernames_usage_, OTHER_POSSIBLE_USERNAMES_MAX);
+ usernames_usage_,
+ OTHER_POSSIBLE_USERNAMES_MAX);
usernames_usage_ = NOTHING_TO_AUTOFILL;
}
}
-void PasswordAutofillAgent::DidFinishDocumentLoad(blink::WebFrame* frame) {
+void PasswordAutofillAgent::DidFinishDocumentLoad(blink::WebLocalFrame* frame) {
// The |frame| contents have been parsed, but not yet rendered. Let the
// PasswordManager know that forms are loaded, even though we can't yet tell
// whether they're visible.
SendPasswordForms(frame, false);
}
-void PasswordAutofillAgent::DidFinishLoad(blink::WebFrame* frame) {
+void PasswordAutofillAgent::DidFinishLoad(blink::WebLocalFrame* frame) {
// The |frame| contents have been rendered. Let the PasswordManager know
// which of the loaded frames are actually visible to the user. This also
// triggers the "Save password?" infobar if the user just submitted a password
}
void PasswordAutofillAgent::WillSendSubmitEvent(
- blink::WebFrame* frame,
+ blink::WebLocalFrame* frame,
const blink::WebFormElement& form) {
// Some login forms have onSubmit handlers that put a hash of the password
// into a hidden field and then clear the password (http://crbug.com/28910).
provisionally_saved_forms_[frame].reset(password_form.release());
}
-void PasswordAutofillAgent::WillSubmitForm(blink::WebFrame* frame,
+void PasswordAutofillAgent::WillSubmitForm(blink::WebLocalFrame* frame,
const blink::WebFormElement& form) {
+ scoped_ptr<RendererSavePasswordProgressLogger> logger;
+ if (logging_state_active_) {
+ logger.reset(new RendererSavePasswordProgressLogger(this, routing_id()));
+ logger->LogMessage(Logger::STRING_WILL_SUBMIT_FORM_METHOD);
+ LogHTMLForm(logger.get(), Logger::STRING_HTML_FORM_FOR_SUBMIT, form);
+ }
+
scoped_ptr<PasswordForm> submitted_form = CreatePasswordForm(form);
// If there is a provisionally saved password, copy over the previous
// TODO(gcasto): Do we need to have this action equality check? Is it trying
// to prevent accidentally copying over passwords from a different form?
if (submitted_form) {
+ if (logger) {
+ logger->LogPasswordForm(Logger::STRING_CREATED_PASSWORD_FORM,
+ *submitted_form);
+ }
if (provisionally_saved_forms_[frame].get() &&
submitted_form->action == provisionally_saved_forms_[frame]->action) {
+ if (logger)
+ logger->LogMessage(Logger::STRING_SUBMITTED_PASSWORD_REPLACED);
submitted_form->password_value =
provisionally_saved_forms_[frame]->password_value;
}
*submitted_form));
// Remove reference since we have already submitted this form.
provisionally_saved_forms_.erase(frame);
+ } else if (logger) {
+ logger->LogMessage(Logger::STRING_DECISION_DROP);
}
}
-void PasswordAutofillAgent::WillProcessUserGesture() {
- gatekeeper_.OnUserGesture();
-}
-
blink::WebFrame* PasswordAutofillAgent::CurrentOrChildFrameWithSavedForms(
const blink::WebFrame* current_frame) {
for (FrameToPasswordFormMap::const_iterator it =
return NULL;
}
-void PasswordAutofillAgent::DidStartProvisionalLoad(blink::WebFrame* frame) {
+void PasswordAutofillAgent::DidStartProvisionalLoad(
+ blink::WebLocalFrame* frame) {
+ scoped_ptr<RendererSavePasswordProgressLogger> logger;
+ if (logging_state_active_) {
+ logger.reset(new RendererSavePasswordProgressLogger(this, routing_id()));
+ logger->LogMessage(Logger::STRING_DID_START_PROVISIONAL_LOAD_METHOD);
+ }
+
if (!frame->parent()) {
// If the navigation is not triggered by a user gesture, e.g. by some ajax
// callback, then inherit the submitted password form from the previous
// [http://crbug/43219]. Note that this still fails for sites that use
// synchonous XHR as isProcessingUserGesture() will return true.
blink::WebFrame* form_frame = CurrentOrChildFrameWithSavedForms(frame);
- if (!blink::WebUserGestureIndicator::isProcessingUserGesture()) {
+ if (logger) {
+ logger->LogBoolean(Logger::STRING_FORM_FRAME_EQ_FRAME,
+ form_frame == frame);
+ }
+ // Bug fix for crbug.com/368690. isProcessingUserGesture() is false when
+ // the user is performing actions outside the page (e.g. typed url,
+ // history navigation). We don't want to trigger saving in these cases.
+ content::DocumentState* document_state =
+ content::DocumentState::FromDataSource(
+ frame->provisionalDataSource());
+ content::NavigationState* navigation_state =
+ document_state->navigation_state();
+ if (content::PageTransitionIsWebTriggerable(
+ navigation_state->transition_type()) &&
+ !blink::WebUserGestureIndicator::isProcessingUserGesture()) {
// If onsubmit has been called, try and save that form.
if (provisionally_saved_forms_[form_frame].get()) {
+ if (logger) {
+ logger->LogPasswordForm(
+ Logger::STRING_PROVISIONALLY_SAVED_FORM_FOR_FRAME,
+ *provisionally_saved_forms_[form_frame]);
+ }
Send(new AutofillHostMsg_PasswordFormSubmitted(
- routing_id(),
- *provisionally_saved_forms_[form_frame]));
+ routing_id(), *provisionally_saved_forms_[form_frame]));
provisionally_saved_forms_.erase(form_frame);
} else {
// Loop through the forms on the page looking for one that has been
blink::WebVector<blink::WebFormElement> forms;
frame->document().forms(forms);
+ bool password_forms_found = false;
for (size_t i = 0; i < forms.size(); ++i) {
- blink::WebFormElement form_element= forms[i];
+ blink::WebFormElement form_element = forms[i];
+ if (logger) {
+ LogHTMLForm(
+ logger.get(), Logger::STRING_FORM_FOUND_ON_PAGE, form_element);
+ }
scoped_ptr<PasswordForm> password_form(
CreatePasswordForm(form_element));
- if (password_form.get() &&
- !password_form->username_value.empty() &&
+ if (password_form.get() && !password_form->username_value.empty() &&
!password_form->password_value.empty() &&
!PasswordValueIsDefault(*password_form, form_element)) {
- Send(new AutofillHostMsg_PasswordFormSubmitted(
- routing_id(), *password_form));
+ password_forms_found = true;
+ if (logger) {
+ logger->LogPasswordForm(
+ Logger::STRING_PASSWORD_FORM_FOUND_ON_PAGE, *password_form);
+ }
+ Send(new AutofillHostMsg_PasswordFormSubmitted(routing_id(),
+ *password_form));
}
}
+ if (!password_forms_found && logger) {
+ logger->LogMessage(Logger::STRING_DECISION_DROP);
+ }
}
}
// Clear the whole map during main frame navigation.
// This is a new navigation, so require a new user gesture before filling in
// passwords.
gatekeeper_.Reset();
+ } else {
+ if (logger)
+ logger->LogMessage(Logger::STRING_DECISION_DROP);
}
}
FindFormAndFieldForFormControlElement(
username_element, &form, &field, REQUIRE_NONE);
Send(new AutofillHostMsg_AddPasswordFormMapping(
- routing_id(),
- field,
- form_data));
+ routing_id(), field, form_data));
}
}
+void PasswordAutofillAgent::OnChangeLoggingState(bool active) {
+ logging_state_active_ = active;
+}
+
////////////////////////////////////////////////////////////////////////////////
// PasswordAutofillAgent, private:
for (PasswordFormFillData::LoginCollection::const_iterator iter =
fill_data.additional_logins.begin();
- iter != fill_data.additional_logins.end(); ++iter) {
+ iter != fill_data.additional_logins.end();
+ ++iter) {
if (StartsWith(iter->first, input, false)) {
suggestions->push_back(iter->first);
realms->push_back(base::UTF8ToUTF16(iter->second.realm));
for (PasswordFormFillData::UsernamesCollection::const_iterator iter =
fill_data.other_possible_usernames.begin();
- iter != fill_data.other_possible_usernames.end(); ++iter) {
+ 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)) {
usernames_usage_ = OTHER_POSSIBLE_USERNAME_SHOWN;
bounding_box.y() * scale,
bounding_box.width() * scale,
bounding_box.height() * scale);
- Send(new AutofillHostMsg_ShowPasswordSuggestions(routing_id(),
- field,
- bounding_box_scaled,
- suggestions,
- realms));
+ Send(new AutofillHostMsg_ShowPasswordSuggestions(
+ routing_id(), field, bounding_box_scaled, suggestions, realms));
return !suggestions.empty();
}
// 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_element,
+ fill_data,
true /* exact_username_match */,
false /* set_selection */);
}
base::string16 password;
// Look for any suitable matches to current field text.
- if (DoUsernamesMatch(fill_data.basic_data.fields[0].value, current_username,
+ if (DoUsernamesMatch(fill_data.basic_data.fields[0].value,
+ current_username,
exact_username_match)) {
username = fill_data.basic_data.fields[0].value;
password = fill_data.basic_data.fields[1].value;
// Scan additional logins for a match.
PasswordFormFillData::LoginCollection::const_iterator iter;
for (iter = fill_data.additional_logins.begin();
- iter != fill_data.additional_logins.end(); ++iter) {
- if (DoUsernamesMatch(iter->first, current_username,
- exact_username_match)) {
+ iter != fill_data.additional_logins.end();
+ ++iter) {
+ if (DoUsernamesMatch(
+ iter->first, current_username, exact_username_match)) {
username = iter->first;
password = iter->second.password;
break;
if (username.empty() && password.empty()) {
for (PasswordFormFillData::UsernamesCollection::const_iterator iter =
fill_data.other_possible_usernames.begin();
- iter != fill_data.other_possible_usernames.end(); ++iter) {
+ iter != fill_data.other_possible_usernames.end();
+ ++iter) {
for (size_t i = 0; i < iter->second.size(); ++i) {
- if (DoUsernamesMatch(iter->second[i], current_username,
- exact_username_match)) {
+ if (DoUsernamesMatch(
+ iter->second[i], current_username, exact_username_match)) {
usernames_usage_ = OTHER_POSSIBLE_USERNAME_SELECTED;
username = iter->second[i];
password = iter->first.password;
// Input matches the username, fill in required values.
if (IsElementAutocompletable(*username_element)) {
username_element->setValue(username, true);
- SetElementAutofilled(username_element, true);
+ username_element->setAutofilled(true);
if (set_selection) {
username_element->setSelectionRange(current_username.length(),
return false;
}
-// TODO(vabr): The "gatekeeper" feature is currently disabled on mobile.
-// http://crbug.com/345510#c13
-#if !defined(OS_ANDROID) || !defined(OS_IOS)
// 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);
-#else
- password_element->setValue(password);
-#endif
- // Note: Don't call SetElementAutofilled() here, as that dispatches an
- // onChange event in JavaScript, which is not appropriate for the password
- // element if a user gesture has not yet occured.
password_element->setAutofilled(true);
return true;
}
// Show the popup with the list of available usernames.
ShowSuggestionPopup(fill_data, username);
-
#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,
+ fill_data,
false /* exact_username_match */,
true /* set_selection */);
#endif