1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/autofill/content/renderer/password_generation_agent.h"
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "components/autofill/content/common/autofill_messages.h"
11 #include "components/autofill/content/renderer/form_autofill_util.h"
12 #include "components/autofill/content/renderer/password_autofill_agent.h"
13 #include "components/autofill/content/renderer/password_form_conversion_utils.h"
14 #include "components/autofill/core/common/autofill_switches.h"
15 #include "components/autofill/core/common/form_data.h"
16 #include "components/autofill/core/common/password_form.h"
17 #include "components/autofill/core/common/password_generation_util.h"
18 #include "content/public/renderer/render_frame.h"
19 #include "content/public/renderer/render_view.h"
20 #include "google_apis/gaia/gaia_urls.h"
21 #include "third_party/WebKit/public/platform/WebVector.h"
22 #include "third_party/WebKit/public/web/WebDocument.h"
23 #include "third_party/WebKit/public/web/WebFormElement.h"
24 #include "third_party/WebKit/public/web/WebInputElement.h"
25 #include "third_party/WebKit/public/web/WebLocalFrame.h"
26 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
27 #include "third_party/WebKit/public/web/WebView.h"
28 #include "ui/gfx/geometry/rect.h"
34 // Returns true if we think that this form is for account creation. |passwords|
35 // is filled with the password field(s) in the form.
36 bool GetAccountCreationPasswordFields(
37 const blink::WebFormElement
& form
,
38 std::vector
<blink::WebInputElement
>* passwords
) {
39 // Grab all of the passwords for the form.
40 blink::WebVector
<blink::WebFormControlElement
> control_elements
;
41 form
.getFormControlElements(control_elements
);
43 size_t num_input_elements
= 0;
44 for (size_t i
= 0; i
< control_elements
.size(); i
++) {
45 blink::WebInputElement
* input_element
=
46 toWebInputElement(&control_elements
[i
]);
47 if (input_element
&& input_element
->isTextField()) {
49 if (input_element
->isPasswordField())
50 passwords
->push_back(*input_element
);
54 // This may be too lenient, but we assume that any form with at least three
55 // input elements where at least one of them is a password is an account
57 if (!passwords
->empty() && num_input_elements
>= 3) {
58 // We trim |passwords| because occasionally there are forms where the
59 // security question answers are put in password fields and we don't want
61 if (passwords
->size() > 2)
70 bool ContainsURL(const std::vector
<GURL
>& urls
, const GURL
& url
) {
71 return std::find(urls
.begin(), urls
.end(), url
) != urls
.end();
74 bool ContainsForm(const std::vector
<autofill::FormData
>& forms
,
75 const PasswordForm
& form
) {
76 for (std::vector
<autofill::FormData
>::const_iterator it
=
77 forms
.begin(); it
!= forms
.end(); ++it
) {
78 if (it
->SameFormAs(form
.form_data
))
84 void CopyElementValueToOtherInputElements(
85 const blink::WebInputElement
* element
,
86 std::vector
<blink::WebInputElement
>* elements
) {
87 for (std::vector
<blink::WebInputElement
>::iterator it
= elements
->begin();
88 it
!= elements
->end(); ++it
) {
89 if (*element
!= *it
) {
90 it
->setValue(element
->value(), true /* sendEvents */);
95 bool AutocompleteAttributesSetForGeneration(const PasswordForm
& form
) {
96 return form
.username_marked_by_site
&& form
.new_password_marked_by_site
;
101 PasswordGenerationAgent::AccountCreationFormData::AccountCreationFormData(
102 linked_ptr
<PasswordForm
> password_form
,
103 std::vector
<blink::WebInputElement
> passwords
)
104 : form(password_form
),
105 password_elements(passwords
) {}
107 PasswordGenerationAgent::AccountCreationFormData::~AccountCreationFormData() {}
109 PasswordGenerationAgent::PasswordGenerationAgent(
110 content::RenderFrame
* render_frame
,
111 PasswordAutofillAgent
* password_agent
)
112 : content::RenderFrameObserver(render_frame
),
113 password_is_generated_(false),
114 password_edited_(false),
115 generation_popup_shown_(false),
116 editing_popup_shown_(false),
117 enabled_(password_generation::IsPasswordGenerationEnabled()),
118 password_agent_(password_agent
) {
119 VLOG(2) << "Password Generation is " << (enabled_
? "Enabled" : "Disabled");
121 PasswordGenerationAgent::~PasswordGenerationAgent() {}
123 void PasswordGenerationAgent::DidFinishDocumentLoad() {
124 // Update stats for main frame navigation.
125 if (!render_frame()->GetWebFrame()->parent()) {
126 // In every navigation, the IPC message sent by the password autofill
127 // manager to query whether the current form is blacklisted or not happens
128 // when the document load finishes, so we need to clear previous states
129 // here before we hear back from the browser. We only clear this state on
130 // main frame load as we don't want subframe loads to clear state that we
131 // have received from the main frame. Note that we assume there is only one
132 // account creation form, but there could be multiple password forms in
134 not_blacklisted_password_form_origins_
.clear();
135 generation_enabled_forms_
.clear();
136 generation_element_
.reset();
137 possible_account_creation_forms_
.clear();
139 // Log statistics after navigation so that we only log once per page.
140 if (generation_form_data_
&&
141 generation_form_data_
->password_elements
.empty()) {
142 password_generation::LogPasswordGenerationEvent(
143 password_generation::NO_SIGN_UP_DETECTED
);
145 password_generation::LogPasswordGenerationEvent(
146 password_generation::SIGN_UP_DETECTED
);
148 generation_form_data_
.reset();
149 password_is_generated_
= false;
150 if (password_edited_
) {
151 password_generation::LogPasswordGenerationEvent(
152 password_generation::PASSWORD_EDITED
);
154 password_edited_
= false;
156 if (generation_popup_shown_
) {
157 password_generation::LogPasswordGenerationEvent(
158 password_generation::GENERATION_POPUP_SHOWN
);
160 generation_popup_shown_
= false;
162 if (editing_popup_shown_
) {
163 password_generation::LogPasswordGenerationEvent(
164 password_generation::EDITING_POPUP_SHOWN
);
166 editing_popup_shown_
= false;
169 FindPossibleGenerationForm();
172 void PasswordGenerationAgent::OnDynamicFormsSeen() {
173 FindPossibleGenerationForm();
176 void PasswordGenerationAgent::FindPossibleGenerationForm() {
180 // We don't want to generate passwords if the browser won't store or sync
182 if (!ShouldAnalyzeDocument())
185 // If we have already found a signup form for this page, no need to continue.
186 if (generation_form_data_
)
189 blink::WebVector
<blink::WebFormElement
> forms
;
190 render_frame()->GetWebFrame()->document().forms(forms
);
191 for (size_t i
= 0; i
< forms
.size(); ++i
) {
192 if (forms
[i
].isNull())
195 // If we can't get a valid PasswordForm, we skip this form because the
196 // the password won't get saved even if we generate it.
197 scoped_ptr
<PasswordForm
> password_form(
198 CreatePasswordFormFromWebForm(forms
[i
], nullptr, nullptr));
199 if (!password_form
.get()) {
200 VLOG(2) << "Skipping form as it would not be saved";
204 // Do not generate password for GAIA since it is used to retrieve the
205 // generated paswords.
206 GURL
realm(password_form
->signon_realm
);
207 if (realm
== GaiaUrls::GetInstance()->gaia_login_form_realm())
210 std::vector
<blink::WebInputElement
> passwords
;
211 if (GetAccountCreationPasswordFields(forms
[i
], &passwords
)) {
212 AccountCreationFormData
ac_form_data(
213 make_linked_ptr(password_form
.release()), passwords
);
214 possible_account_creation_forms_
.push_back(ac_form_data
);
218 if (!possible_account_creation_forms_
.empty()) {
219 VLOG(2) << possible_account_creation_forms_
.size()
220 << " possible account creation forms deteceted";
221 DetermineGenerationElement();
225 bool PasswordGenerationAgent::ShouldAnalyzeDocument() const {
226 // Make sure that this security origin is allowed to use password manager.
227 // Generating a password that can't be saved is a bad idea.
228 blink::WebSecurityOrigin origin
=
229 render_frame()->GetWebFrame()->document().securityOrigin();
230 if (!origin
.canAccessPasswordManager()) {
231 VLOG(1) << "No PasswordManager access";
238 bool PasswordGenerationAgent::OnMessageReceived(const IPC::Message
& message
) {
240 IPC_BEGIN_MESSAGE_MAP(PasswordGenerationAgent
, message
)
241 IPC_MESSAGE_HANDLER(AutofillMsg_FormNotBlacklisted
,
242 OnFormNotBlacklisted
)
243 IPC_MESSAGE_HANDLER(AutofillMsg_GeneratedPasswordAccepted
,
245 IPC_MESSAGE_HANDLER(AutofillMsg_AccountCreationFormsDetected
,
246 OnAccountCreationFormsDetected
)
247 IPC_MESSAGE_UNHANDLED(handled
= false)
248 IPC_END_MESSAGE_MAP()
252 void PasswordGenerationAgent::OnFormNotBlacklisted(const PasswordForm
& form
) {
253 not_blacklisted_password_form_origins_
.push_back(form
.origin
);
254 DetermineGenerationElement();
257 void PasswordGenerationAgent::OnPasswordAccepted(
258 const base::string16
& password
) {
259 password_is_generated_
= true;
260 password_generation::LogPasswordGenerationEvent(
261 password_generation::PASSWORD_ACCEPTED
);
262 for (auto& password_element
: generation_form_data_
->password_elements
) {
263 password_element
.setValue(password
, true /* sendEvents */);
264 password_element
.setAutofilled(true);
265 // Needed to notify password_autofill_agent that the content of the field
266 // has changed. Without this we will overwrite the generated
267 // password with an Autofilled password when saving.
268 // https://crbug.com/493455
269 password_agent_
->UpdateStateForTextChange(password_element
);
270 // Advance focus to the next input field. We assume password fields in
271 // an account creation form are always adjacent.
272 render_frame()->GetRenderView()->GetWebView()->advanceFocus(false);
276 void PasswordGenerationAgent::OnAccountCreationFormsDetected(
277 const std::vector
<autofill::FormData
>& forms
) {
278 generation_enabled_forms_
.insert(
279 generation_enabled_forms_
.end(), forms
.begin(), forms
.end());
280 DetermineGenerationElement();
283 void PasswordGenerationAgent::DetermineGenerationElement() {
284 if (generation_form_data_
) {
285 VLOG(2) << "Account creation form already found";
289 // Make sure local heuristics have identified a possible account creation
291 if (possible_account_creation_forms_
.empty()) {
292 VLOG(2) << "Local hueristics have not detected a possible account "
297 // Note that no messages will be sent if this feature is disabled
298 // (e.g. password saving is disabled).
299 for (auto& possible_form_data
: possible_account_creation_forms_
) {
300 PasswordForm
* possible_password_form
= possible_form_data
.form
.get();
301 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
302 switches::kLocalHeuristicsOnlyForPasswordGeneration
)) {
303 VLOG(2) << "Bypassing additional checks.";
304 } else if (!ContainsURL(not_blacklisted_password_form_origins_
,
305 possible_password_form
->origin
)) {
306 VLOG(2) << "Have not received confirmation that password form isn't "
309 } else if (!ContainsForm(generation_enabled_forms_
,
310 *possible_password_form
)) {
311 if (AutocompleteAttributesSetForGeneration(*possible_password_form
)) {
312 VLOG(2) << "Ignoring lack of Autofill signal due to Autocomplete "
314 password_generation::LogPasswordGenerationEvent(
315 password_generation::AUTOCOMPLETE_ATTRIBUTES_ENABLED_GENERATION
);
317 VLOG(2) << "Have not received confirmation from Autofill that form is "
318 << "used for account creation";
323 VLOG(2) << "Password generation eligible form found";
324 generation_form_data_
.reset(
325 new AccountCreationFormData(possible_form_data
.form
,
326 possible_form_data
.password_elements
));
327 generation_element_
= generation_form_data_
->password_elements
[0];
328 generation_element_
.setAttribute("aria-autocomplete", "list");
329 password_generation::LogPasswordGenerationEvent(
330 password_generation::GENERATION_AVAILABLE
);
331 possible_account_creation_forms_
.clear();
332 Send(new AutofillHostMsg_GenerationAvailableForForm(
334 *generation_form_data_
->form
));
339 bool PasswordGenerationAgent::FocusedNodeHasChanged(
340 const blink::WebNode
& node
) {
341 if (!generation_element_
.isNull())
342 generation_element_
.setShouldRevealPassword(false);
344 if (node
.isNull() || !node
.isElementNode())
347 const blink::WebElement web_element
= node
.toConst
<blink::WebElement
>();
348 if (!web_element
.document().frame())
351 const blink::WebInputElement
* element
= toWebInputElement(&web_element
);
352 if (!element
|| *element
!= generation_element_
)
355 if (password_is_generated_
) {
356 generation_element_
.setShouldRevealPassword(true);
361 // Assume that if the password field has less than kMaximumOfferSize
362 // characters then the user is not finished typing their password and display
363 // the password suggestion.
364 if (!element
->isReadOnly() &&
365 element
->isEnabled() &&
366 element
->value().length() <= kMaximumOfferSize
) {
367 ShowGenerationPopup();
374 bool PasswordGenerationAgent::TextDidChangeInTextField(
375 const blink::WebInputElement
& element
) {
376 if (element
!= generation_element_
)
379 if (element
.value().isEmpty()) {
380 if (password_is_generated_
) {
381 // User generated a password and then deleted it.
382 password_generation::LogPasswordGenerationEvent(
383 password_generation::PASSWORD_DELETED
);
384 CopyElementValueToOtherInputElements(&element
,
385 &generation_form_data_
->password_elements
);
386 Send(new AutofillHostMsg_PasswordNoLongerGenerated(
388 *generation_form_data_
->form
));
391 // Do not treat the password as generated, either here or in the browser.
392 password_is_generated_
= false;
393 generation_element_
.setShouldRevealPassword(false);
395 // Offer generation again.
396 ShowGenerationPopup();
397 } else if (password_is_generated_
) {
398 password_edited_
= true;
399 // Mirror edits to any confirmation password fields.
400 CopyElementValueToOtherInputElements(&element
,
401 &generation_form_data_
->password_elements
);
402 } else if (element
.value().length() > kMaximumOfferSize
) {
403 // User has rejected the feature and has started typing a password.
406 // Password isn't generated and there are fewer than kMaximumOfferSize
407 // characters typed, so keep offering the password. Note this function
408 // will just keep the previous popup if one is already showing.
409 ShowGenerationPopup();
415 void PasswordGenerationAgent::ShowGenerationPopup() {
416 gfx::RectF bounding_box_scaled
= GetScaledBoundingBox(
417 render_frame()->GetRenderView()->GetWebView()->pageScaleFactor(),
418 &generation_element_
);
420 Send(new AutofillHostMsg_ShowPasswordGenerationPopup(
423 generation_element_
.maxLength(),
424 *generation_form_data_
->form
));
426 generation_popup_shown_
= true;
429 void PasswordGenerationAgent::ShowEditingPopup() {
430 gfx::RectF bounding_box_scaled
= GetScaledBoundingBox(
431 render_frame()->GetRenderView()->GetWebView()->pageScaleFactor(),
432 &generation_element_
);
434 Send(new AutofillHostMsg_ShowPasswordEditingPopup(
437 *generation_form_data_
->form
));
439 editing_popup_shown_
= true;
442 void PasswordGenerationAgent::HidePopup() {
443 Send(new AutofillHostMsg_HidePasswordGenerationPopup(routing_id()));
446 } // namespace autofill