| // Copyright 2013 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 "components/autofill/content/renderer/password_generation_agent.h" |
| |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "components/autofill/content/renderer/password_form_conversion_utils.h" |
| #include "components/autofill/core/common/autofill_messages.h" |
| #include "components/autofill/core/common/autofill_switches.h" |
| #include "components/autofill/core/common/form_data.h" |
| #include "components/autofill/core/common/password_form.h" |
| #include "components/autofill/core/common/password_generation_util.h" |
| #include "content/public/renderer/render_view.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "third_party/WebKit/public/platform/WebCString.h" |
| #include "third_party/WebKit/public/platform/WebRect.h" |
| #include "third_party/WebKit/public/platform/WebVector.h" |
| #include "third_party/WebKit/public/web/WebDocument.h" |
| #include "third_party/WebKit/public/web/WebFormElement.h" |
| #include "third_party/WebKit/public/web/WebFrame.h" |
| #include "third_party/WebKit/public/web/WebInputElement.h" |
| #include "third_party/WebKit/public/web/WebSecurityOrigin.h" |
| #include "third_party/WebKit/public/web/WebView.h" |
| #include "ui/gfx/rect.h" |
| |
| namespace autofill { |
| |
| namespace { |
| |
| // Returns true if we think that this form is for account creation. |passwords| |
| // is filled with the password field(s) in the form. |
| bool GetAccountCreationPasswordFields( |
| const WebKit::WebFormElement& form, |
| std::vector<WebKit::WebInputElement>* passwords) { |
| // Grab all of the passwords for the form. |
| WebKit::WebVector<WebKit::WebFormControlElement> control_elements; |
| form.getFormControlElements(control_elements); |
| |
| size_t num_input_elements = 0; |
| for (size_t i = 0; i < control_elements.size(); i++) { |
| WebKit::WebInputElement* input_element = |
| toWebInputElement(&control_elements[i]); |
| // Only pay attention to visible password fields. |
| if (input_element && |
| input_element->isTextField() && |
| input_element->hasNonEmptyBoundingBox()) { |
| num_input_elements++; |
| if (input_element->isPasswordField()) |
| passwords->push_back(*input_element); |
| } |
| } |
| |
| // This may be too lenient, but we assume that any form with at least three |
| // input elements where at least one of them is a password is an account |
| // creation form. |
| if (!passwords->empty() && num_input_elements >= 3) { |
| // We trim |passwords| because occasionally there are forms where the |
| // security question answers are put in password fields and we don't want |
| // to fill those. |
| if (passwords->size() > 2) |
| passwords->resize(2); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ContainsURL(const std::vector<GURL>& urls, const GURL& url) { |
| return std::find(urls.begin(), urls.end(), url) != urls.end(); |
| } |
| |
| // Returns true if the |form1| is essentially equal to |form2|. |
| bool FormEquals(const autofill::FormData& form1, |
| const PasswordForm& form2) { |
| // TODO(zysxqn): use more signals than just origin to compare. |
| return form1.origin == form2.origin; |
| } |
| |
| bool ContainsForm(const std::vector<autofill::FormData>& forms, |
| const PasswordForm& form) { |
| for (std::vector<autofill::FormData>::const_iterator it = |
| forms.begin(); it != forms.end(); ++it) { |
| if (FormEquals(*it, form)) |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| PasswordGenerationAgent::PasswordGenerationAgent( |
| content::RenderView* render_view) |
| : content::RenderViewObserver(render_view), |
| render_view_(render_view) { |
| render_view_->GetWebView()->setPasswordGeneratorClient(this); |
| } |
| PasswordGenerationAgent::~PasswordGenerationAgent() {} |
| |
| void PasswordGenerationAgent::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. We only clear this state on main frame load |
| // as we don't want subframe loads to clear state that we have recieved from |
| // the main frame. Note that we assume there is only one account creation |
| // form, but there could be multiple password forms in each frame. |
| // |
| // TODO(zysxqn): Add stat when local heuristic fires but we don't show the |
| // password generation icon. |
| if (!frame->parent()) { |
| not_blacklisted_password_form_origins_.clear(); |
| generation_enabled_forms_.clear(); |
| possible_account_creation_form_.reset(new PasswordForm()); |
| passwords_.clear(); |
| } |
| } |
| |
| void PasswordGenerationAgent::DidFinishLoad(WebKit::WebFrame* frame) { |
| // We don't want to generate passwords if the browser won't store or sync |
| // them. |
| if (!ShouldAnalyzeDocument(frame->document())) |
| return; |
| |
| WebKit::WebVector<WebKit::WebFormElement> forms; |
| frame->document().forms(forms); |
| for (size_t i = 0; i < forms.size(); ++i) { |
| if (forms[i].isNull()) |
| 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<PasswordForm> password_form( |
| CreatePasswordForm(forms[i])); |
| if (!password_form.get()) { |
| DVLOG(2) << "Skipping form as it would not be saved"; |
| continue; |
| } |
| |
| // Do not generate password for GAIA since it is used to retrieve the |
| // generated paswords. |
| GURL realm(password_form->signon_realm); |
| if (realm == GaiaUrls::GetInstance()->gaia_login_form_realm()) |
| continue; |
| |
| std::vector<WebKit::WebInputElement> passwords; |
| if (GetAccountCreationPasswordFields(forms[i], &passwords)) { |
| DVLOG(2) << "Account creation form detected"; |
| password_generation::LogPasswordGenerationEvent( |
| password_generation::SIGN_UP_DETECTED); |
| passwords_ = passwords; |
| possible_account_creation_form_.swap(password_form); |
| MaybeShowIcon(); |
| // We assume that there is only one account creation field per URL. |
| return; |
| } |
| } |
| password_generation::LogPasswordGenerationEvent( |
| password_generation::NO_SIGN_UP_DETECTED); |
| } |
| |
| bool PasswordGenerationAgent::ShouldAnalyzeDocument( |
| const WebKit::WebDocument& document) const { |
| // Make sure that this security origin is allowed to use password manager. |
| // Generating a password that can't be saved is a bad idea. |
| WebKit::WebSecurityOrigin origin = document.securityOrigin(); |
| if (!origin.canAccessPasswordManager()) { |
| DVLOG(1) << "No PasswordManager access"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void PasswordGenerationAgent::openPasswordGenerator( |
| WebKit::WebInputElement& element) { |
| WebKit::WebElement button(element.passwordGeneratorButtonElement()); |
| gfx::Rect rect(button.boundsInViewportSpace()); |
| scoped_ptr<PasswordForm> password_form( |
| CreatePasswordForm(element.form())); |
| // We should not have shown the icon we can't create a valid PasswordForm. |
| DCHECK(password_form.get()); |
| |
| Send(new AutofillHostMsg_ShowPasswordGenerationPopup(routing_id(), |
| rect, |
| element.maxLength(), |
| *password_form)); |
| password_generation::LogPasswordGenerationEvent( |
| password_generation::BUBBLE_SHOWN); |
| } |
| |
| bool PasswordGenerationAgent::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(PasswordGenerationAgent, message) |
| IPC_MESSAGE_HANDLER(AutofillMsg_FormNotBlacklisted, |
| OnFormNotBlacklisted) |
| IPC_MESSAGE_HANDLER(AutofillMsg_GeneratedPasswordAccepted, |
| OnPasswordAccepted) |
| IPC_MESSAGE_HANDLER(AutofillMsg_AccountCreationFormsDetected, |
| OnAccountCreationFormsDetected) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| return handled; |
| } |
| |
| void PasswordGenerationAgent::OnFormNotBlacklisted(const PasswordForm& form) { |
| not_blacklisted_password_form_origins_.push_back(form.origin); |
| MaybeShowIcon(); |
| } |
| |
| void PasswordGenerationAgent::OnPasswordAccepted( |
| const base::string16& password) { |
| for (std::vector<WebKit::WebInputElement>::iterator it = passwords_.begin(); |
| it != passwords_.end(); ++it) { |
| it->setValue(password); |
| it->setAutofilled(true); |
| // Advance focus to the next input field. We assume password fields in |
| // an account creation form are always adjacent. |
| render_view_->GetWebView()->advanceFocus(false); |
| } |
| } |
| |
| void PasswordGenerationAgent::OnAccountCreationFormsDetected( |
| const std::vector<autofill::FormData>& forms) { |
| generation_enabled_forms_.insert( |
| generation_enabled_forms_.end(), forms.begin(), forms.end()); |
| MaybeShowIcon(); |
| } |
| |
| void PasswordGenerationAgent::MaybeShowIcon() { |
| // Make sure local heuristics have identified a possible account creation |
| // form. |
| if (!possible_account_creation_form_.get() || passwords_.empty()) { |
| DVLOG(2) << "Local hueristics have not detected a possible account " |
| << "creation form"; |
| return; |
| } |
| |
| // Verify that it's not blacklisted. |
| if (not_blacklisted_password_form_origins_.empty() || |
| !ContainsURL(not_blacklisted_password_form_origins_, |
| possible_account_creation_form_->origin)) { |
| DVLOG(2) << "Have not recieved confirmation that password form isn't " |
| << "blacklisted"; |
| return; |
| } |
| |
| // Ensure that we get a ping from Autofill saying that this form is used for |
| // account creation. Note that this message will not be set if this feature |
| // is not enabled. If kNoAutofillNecessaryForPasswordGeneration is set, |
| // skip this check. This switch should only be used in testing environments. |
| if (!CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kNoAutofillNecessaryForPasswordGeneration) && |
| (generation_enabled_forms_.empty() || |
| !ContainsForm(generation_enabled_forms_, |
| *possible_account_creation_form_))) { |
| DVLOG(2) << "Have not recieved confirmation from Autofill that form is used" |
| << " for account creation"; |
| return; |
| } |
| |
| passwords_[0].passwordGeneratorButtonElement().setAttribute("style", |
| "display:block"); |
| password_generation::LogPasswordGenerationEvent( |
| password_generation::ICON_SHOWN); |
| } |
| |
| } // namespace autofill |