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
<FormData
>& forms
,
75 const PasswordForm
& form
) {
76 for (const auto& form_it
: forms
) {
77 if (form_it
.SameFormAs(form
.form_data
))
83 void CopyElementValueToOtherInputElements(
84 const blink::WebInputElement
* element
,
85 std::vector
<blink::WebInputElement
>* elements
) {
86 for (std::vector
<blink::WebInputElement
>::iterator it
= elements
->begin();
87 it
!= elements
->end(); ++it
) {
88 if (*element
!= *it
) {
89 it
->setValue(element
->value(), true /* sendEvents */);
94 bool AutocompleteAttributesSetForGeneration(const PasswordForm
& form
) {
95 return form
.username_marked_by_site
&& form
.new_password_marked_by_site
;
100 PasswordGenerationAgent::AccountCreationFormData::AccountCreationFormData(
101 linked_ptr
<PasswordForm
> password_form
,
102 std::vector
<blink::WebInputElement
> passwords
)
103 : form(password_form
),
104 password_elements(passwords
) {}
106 PasswordGenerationAgent::AccountCreationFormData::~AccountCreationFormData() {}
108 PasswordGenerationAgent::PasswordGenerationAgent(
109 content::RenderFrame
* render_frame
,
110 PasswordAutofillAgent
* password_agent
)
111 : content::RenderFrameObserver(render_frame
),
112 password_is_generated_(false),
113 password_edited_(false),
114 generation_popup_shown_(false),
115 editing_popup_shown_(false),
116 enabled_(password_generation::IsPasswordGenerationEnabled()),
117 password_agent_(password_agent
) {
118 VLOG(2) << "Password Generation is " << (enabled_
? "Enabled" : "Disabled");
120 PasswordGenerationAgent::~PasswordGenerationAgent() {}
122 void PasswordGenerationAgent::DidFinishDocumentLoad() {
123 // Update stats for main frame navigation.
124 if (!render_frame()->GetWebFrame()->parent()) {
125 // In every navigation, the IPC message sent by the password autofill
126 // manager to query whether the current form is blacklisted or not happens
127 // when the document load finishes, so we need to clear previous states
128 // here before we hear back from the browser. We only clear this state on
129 // main frame load as we don't want subframe loads to clear state that we
130 // have received from the main frame. Note that we assume there is only one
131 // account creation form, but there could be multiple password forms in
133 not_blacklisted_password_form_origins_
.clear();
134 generation_enabled_forms_
.clear();
135 generation_element_
.reset();
136 possible_account_creation_forms_
.clear();
138 // Log statistics after navigation so that we only log once per page.
139 if (generation_form_data_
&&
140 generation_form_data_
->password_elements
.empty()) {
141 password_generation::LogPasswordGenerationEvent(
142 password_generation::NO_SIGN_UP_DETECTED
);
144 password_generation::LogPasswordGenerationEvent(
145 password_generation::SIGN_UP_DETECTED
);
147 generation_form_data_
.reset();
148 password_is_generated_
= false;
149 if (password_edited_
) {
150 password_generation::LogPasswordGenerationEvent(
151 password_generation::PASSWORD_EDITED
);
153 password_edited_
= false;
155 if (generation_popup_shown_
) {
156 password_generation::LogPasswordGenerationEvent(
157 password_generation::GENERATION_POPUP_SHOWN
);
159 generation_popup_shown_
= false;
161 if (editing_popup_shown_
) {
162 password_generation::LogPasswordGenerationEvent(
163 password_generation::EDITING_POPUP_SHOWN
);
165 editing_popup_shown_
= false;
168 FindPossibleGenerationForm();
171 void PasswordGenerationAgent::OnDynamicFormsSeen() {
172 FindPossibleGenerationForm();
175 void PasswordGenerationAgent::FindPossibleGenerationForm() {
179 // We don't want to generate passwords if the browser won't store or sync
181 if (!ShouldAnalyzeDocument())
184 // If we have already found a signup form for this page, no need to continue.
185 if (generation_form_data_
)
188 blink::WebVector
<blink::WebFormElement
> forms
;
189 render_frame()->GetWebFrame()->document().forms(forms
);
190 for (size_t i
= 0; i
< forms
.size(); ++i
) {
191 if (forms
[i
].isNull())
194 // If we can't get a valid PasswordForm, we skip this form because the
195 // the password won't get saved even if we generate it.
196 scoped_ptr
<PasswordForm
> password_form(
197 CreatePasswordFormFromWebForm(forms
[i
], nullptr, nullptr));
198 if (!password_form
.get()) {
199 VLOG(2) << "Skipping form as it would not be saved";
203 // Do not generate password for GAIA since it is used to retrieve the
204 // generated paswords.
205 GURL
realm(password_form
->signon_realm
);
206 if (realm
== GaiaUrls::GetInstance()->gaia_login_form_realm())
209 std::vector
<blink::WebInputElement
> passwords
;
210 if (GetAccountCreationPasswordFields(forms
[i
], &passwords
)) {
211 AccountCreationFormData
ac_form_data(
212 make_linked_ptr(password_form
.release()), passwords
);
213 possible_account_creation_forms_
.push_back(ac_form_data
);
217 if (!possible_account_creation_forms_
.empty()) {
218 VLOG(2) << possible_account_creation_forms_
.size()
219 << " possible account creation forms deteceted";
220 DetermineGenerationElement();
224 bool PasswordGenerationAgent::ShouldAnalyzeDocument() const {
225 // Make sure that this security origin is allowed to use password manager.
226 // Generating a password that can't be saved is a bad idea.
227 blink::WebSecurityOrigin origin
=
228 render_frame()->GetWebFrame()->document().securityOrigin();
229 if (!origin
.canAccessPasswordManager()) {
230 VLOG(1) << "No PasswordManager access";
237 bool PasswordGenerationAgent::OnMessageReceived(const IPC::Message
& message
) {
239 IPC_BEGIN_MESSAGE_MAP(PasswordGenerationAgent
, message
)
240 IPC_MESSAGE_HANDLER(AutofillMsg_FormNotBlacklisted
,
241 OnFormNotBlacklisted
)
242 IPC_MESSAGE_HANDLER(AutofillMsg_GeneratedPasswordAccepted
,
244 IPC_MESSAGE_HANDLER(AutofillMsg_AccountCreationFormsDetected
,
245 OnAccountCreationFormsDetected
)
246 IPC_MESSAGE_UNHANDLED(handled
= false)
247 IPC_END_MESSAGE_MAP()
251 void PasswordGenerationAgent::OnFormNotBlacklisted(const PasswordForm
& form
) {
252 not_blacklisted_password_form_origins_
.push_back(form
.origin
);
253 DetermineGenerationElement();
256 void PasswordGenerationAgent::OnPasswordAccepted(
257 const base::string16
& password
) {
258 password_is_generated_
= true;
259 password_generation::LogPasswordGenerationEvent(
260 password_generation::PASSWORD_ACCEPTED
);
261 for (auto& password_element
: generation_form_data_
->password_elements
) {
262 password_element
.setValue(password
, true /* sendEvents */);
263 password_element
.setAutofilled(true);
264 // Needed to notify password_autofill_agent that the content of the field
265 // has changed. Without this we will overwrite the generated
266 // password with an Autofilled password when saving.
267 // https://crbug.com/493455
268 password_agent_
->UpdateStateForTextChange(password_element
);
269 // Advance focus to the next input field. We assume password fields in
270 // an account creation form are always adjacent.
271 render_frame()->GetRenderView()->GetWebView()->advanceFocus(false);
275 void PasswordGenerationAgent::OnAccountCreationFormsDetected(
276 const std::vector
<FormData
>& forms
) {
277 generation_enabled_forms_
.insert(
278 generation_enabled_forms_
.end(), forms
.begin(), forms
.end());
279 DetermineGenerationElement();
282 void PasswordGenerationAgent::DetermineGenerationElement() {
283 if (generation_form_data_
) {
284 VLOG(2) << "Account creation form already found";
288 // Make sure local heuristics have identified a possible account creation
290 if (possible_account_creation_forms_
.empty()) {
291 VLOG(2) << "Local hueristics have not detected a possible account "
296 // Note that no messages will be sent if this feature is disabled
297 // (e.g. password saving is disabled).
298 for (auto& possible_form_data
: possible_account_creation_forms_
) {
299 PasswordForm
* possible_password_form
= possible_form_data
.form
.get();
300 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
301 switches::kLocalHeuristicsOnlyForPasswordGeneration
)) {
302 VLOG(2) << "Bypassing additional checks.";
303 } else if (!ContainsURL(not_blacklisted_password_form_origins_
,
304 possible_password_form
->origin
)) {
305 VLOG(2) << "Have not received confirmation that password form isn't "
308 } else if (!ContainsForm(generation_enabled_forms_
,
309 *possible_password_form
)) {
310 if (AutocompleteAttributesSetForGeneration(*possible_password_form
)) {
311 VLOG(2) << "Ignoring lack of Autofill signal due to Autocomplete "
313 password_generation::LogPasswordGenerationEvent(
314 password_generation::AUTOCOMPLETE_ATTRIBUTES_ENABLED_GENERATION
);
316 VLOG(2) << "Have not received confirmation from Autofill that form is "
317 << "used for account creation";
322 VLOG(2) << "Password generation eligible form found";
323 generation_form_data_
.reset(
324 new AccountCreationFormData(possible_form_data
.form
,
325 possible_form_data
.password_elements
));
326 generation_element_
= generation_form_data_
->password_elements
[0];
327 generation_element_
.setAttribute("aria-autocomplete", "list");
328 password_generation::LogPasswordGenerationEvent(
329 password_generation::GENERATION_AVAILABLE
);
330 possible_account_creation_forms_
.clear();
331 Send(new AutofillHostMsg_GenerationAvailableForForm(
333 *generation_form_data_
->form
));
338 bool PasswordGenerationAgent::FocusedNodeHasChanged(
339 const blink::WebNode
& node
) {
340 if (!generation_element_
.isNull())
341 generation_element_
.setShouldRevealPassword(false);
343 if (node
.isNull() || !node
.isElementNode())
346 const blink::WebElement web_element
= node
.toConst
<blink::WebElement
>();
347 if (!web_element
.document().frame())
350 const blink::WebInputElement
* element
= toWebInputElement(&web_element
);
351 if (!element
|| *element
!= generation_element_
)
354 if (password_is_generated_
) {
355 generation_element_
.setShouldRevealPassword(true);
360 // Assume that if the password field has less than kMaximumOfferSize
361 // characters then the user is not finished typing their password and display
362 // the password suggestion.
363 if (!element
->isReadOnly() &&
364 element
->isEnabled() &&
365 element
->value().length() <= kMaximumOfferSize
) {
366 ShowGenerationPopup();
373 bool PasswordGenerationAgent::TextDidChangeInTextField(
374 const blink::WebInputElement
& element
) {
375 if (element
!= generation_element_
)
378 if (element
.value().isEmpty()) {
379 if (password_is_generated_
) {
380 // User generated a password and then deleted it.
381 password_generation::LogPasswordGenerationEvent(
382 password_generation::PASSWORD_DELETED
);
383 CopyElementValueToOtherInputElements(&element
,
384 &generation_form_data_
->password_elements
);
385 Send(new AutofillHostMsg_PasswordNoLongerGenerated(
387 *generation_form_data_
->form
));
390 // Do not treat the password as generated, either here or in the browser.
391 password_is_generated_
= false;
392 generation_element_
.setShouldRevealPassword(false);
394 // Offer generation again.
395 ShowGenerationPopup();
396 } else if (password_is_generated_
) {
397 password_edited_
= true;
398 // Mirror edits to any confirmation password fields.
399 CopyElementValueToOtherInputElements(&element
,
400 &generation_form_data_
->password_elements
);
401 } else if (element
.value().length() > kMaximumOfferSize
) {
402 // User has rejected the feature and has started typing a password.
405 // Password isn't generated and there are fewer than kMaximumOfferSize
406 // characters typed, so keep offering the password. Note this function
407 // will just keep the previous popup if one is already showing.
408 ShowGenerationPopup();
414 void PasswordGenerationAgent::ShowGenerationPopup() {
415 gfx::RectF bounding_box_scaled
= GetScaledBoundingBox(
416 render_frame()->GetRenderView()->GetWebView()->pageScaleFactor(),
417 &generation_element_
);
419 Send(new AutofillHostMsg_ShowPasswordGenerationPopup(
422 generation_element_
.maxLength(),
423 *generation_form_data_
->form
));
425 generation_popup_shown_
= true;
428 void PasswordGenerationAgent::ShowEditingPopup() {
429 gfx::RectF bounding_box_scaled
= GetScaledBoundingBox(
430 render_frame()->GetRenderView()->GetWebView()->pageScaleFactor(),
431 &generation_element_
);
433 Send(new AutofillHostMsg_ShowPasswordEditingPopup(
436 *generation_form_data_
->form
));
438 editing_popup_shown_
= true;
441 void PasswordGenerationAgent::HidePopup() {
442 Send(new AutofillHostMsg_HidePasswordGenerationPopup(routing_id()));
445 } // namespace autofill