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_form_conversion_utils.h"
13 #include "components/autofill/core/common/autofill_switches.h"
14 #include "components/autofill/core/common/form_data.h"
15 #include "components/autofill/core/common/password_form.h"
16 #include "components/autofill/core/common/password_generation_util.h"
17 #include "content/public/renderer/render_frame.h"
18 #include "content/public/renderer/render_view.h"
19 #include "google_apis/gaia/gaia_urls.h"
20 #include "third_party/WebKit/public/platform/WebVector.h"
21 #include "third_party/WebKit/public/web/WebDocument.h"
22 #include "third_party/WebKit/public/web/WebFormElement.h"
23 #include "third_party/WebKit/public/web/WebInputElement.h"
24 #include "third_party/WebKit/public/web/WebLocalFrame.h"
25 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
26 #include "third_party/WebKit/public/web/WebView.h"
27 #include "ui/gfx/geometry/rect.h"
33 // Returns true if we think that this form is for account creation. |passwords|
34 // is filled with the password field(s) in the form.
35 bool GetAccountCreationPasswordFields(
36 const blink::WebFormElement
& form
,
37 std::vector
<blink::WebInputElement
>* passwords
) {
38 // Grab all of the passwords for the form.
39 blink::WebVector
<blink::WebFormControlElement
> control_elements
;
40 form
.getFormControlElements(control_elements
);
42 size_t num_input_elements
= 0;
43 for (size_t i
= 0; i
< control_elements
.size(); i
++) {
44 blink::WebInputElement
* input_element
=
45 toWebInputElement(&control_elements
[i
]);
46 if (input_element
&& input_element
->isTextField()) {
48 if (input_element
->isPasswordField())
49 passwords
->push_back(*input_element
);
53 // This may be too lenient, but we assume that any form with at least three
54 // input elements where at least one of them is a password is an account
56 if (!passwords
->empty() && num_input_elements
>= 3) {
57 // We trim |passwords| because occasionally there are forms where the
58 // security question answers are put in password fields and we don't want
60 if (passwords
->size() > 2)
69 bool ContainsURL(const std::vector
<GURL
>& urls
, const GURL
& url
) {
70 return std::find(urls
.begin(), urls
.end(), url
) != urls
.end();
73 bool ContainsForm(const std::vector
<autofill::FormData
>& forms
,
74 const PasswordForm
& form
) {
75 for (std::vector
<autofill::FormData
>::const_iterator it
=
76 forms
.begin(); it
!= forms
.end(); ++it
) {
77 if (it
->SameFormAs(form
.form_data
))
83 void CopyValueToAllInputElements(
84 const blink::WebString value
,
85 std::vector
<blink::WebInputElement
>* elements
) {
86 for (std::vector
<blink::WebInputElement
>::iterator it
= elements
->begin();
87 it
!= elements
->end(); ++it
) {
88 it
->setValue(value
, true /* sendEvents */);
94 PasswordGenerationAgent::AccountCreationFormData::AccountCreationFormData(
95 linked_ptr
<PasswordForm
> password_form
,
96 std::vector
<blink::WebInputElement
> passwords
)
97 : form(password_form
),
98 password_elements(passwords
) {}
100 PasswordGenerationAgent::AccountCreationFormData::~AccountCreationFormData() {}
102 PasswordGenerationAgent::PasswordGenerationAgent(
103 content::RenderFrame
* render_frame
)
104 : content::RenderFrameObserver(render_frame
),
105 password_is_generated_(false),
106 password_edited_(false),
107 generation_popup_shown_(false),
108 editing_popup_shown_(false),
109 enabled_(password_generation::IsPasswordGenerationEnabled()) {
110 VLOG(2) << "Password Generation is " << (enabled_
? "Enabled" : "Disabled");
112 PasswordGenerationAgent::~PasswordGenerationAgent() {}
114 void PasswordGenerationAgent::DidFinishDocumentLoad() {
115 if (render_frame()->GetWebFrame()->parent())
118 // In every navigation, the IPC message sent by the password autofill manager
119 // to query whether the current form is blacklisted or not happens when the
120 // document load finishes, so we need to clear previous states here before we
121 // hear back from the browser. We only clear this state on main frame load
122 // as we don't want subframe loads to clear state that we have received from
123 // the main frame. Note that we assume there is only one account creation
124 // form, but there could be multiple password forms in each frame.
125 not_blacklisted_password_form_origins_
.clear();
126 generation_enabled_forms_
.clear();
127 generation_element_
.reset();
128 possible_account_creation_forms_
.clear();
130 // Log statistics after navigation so that we only log once per page.
131 if (generation_form_data_
&&
132 generation_form_data_
->password_elements
.empty()) {
133 password_generation::LogPasswordGenerationEvent(
134 password_generation::NO_SIGN_UP_DETECTED
);
136 password_generation::LogPasswordGenerationEvent(
137 password_generation::SIGN_UP_DETECTED
);
139 generation_form_data_
.reset();
140 password_is_generated_
= false;
141 if (password_edited_
) {
142 password_generation::LogPasswordGenerationEvent(
143 password_generation::PASSWORD_EDITED
);
145 password_edited_
= false;
147 if (generation_popup_shown_
) {
148 password_generation::LogPasswordGenerationEvent(
149 password_generation::GENERATION_POPUP_SHOWN
);
151 generation_popup_shown_
= false;
153 if (editing_popup_shown_
) {
154 password_generation::LogPasswordGenerationEvent(
155 password_generation::EDITING_POPUP_SHOWN
);
157 editing_popup_shown_
= false;
159 FindPossibleGenerationForm();
162 void PasswordGenerationAgent::OnDynamicFormsSeen() {
163 FindPossibleGenerationForm();
166 void PasswordGenerationAgent::FindPossibleGenerationForm() {
170 // We don't want to generate passwords if the browser won't store or sync
172 if (!ShouldAnalyzeDocument())
175 // If we have already found a signup form for this page, no need to continue.
176 if (generation_form_data_
)
179 blink::WebVector
<blink::WebFormElement
> forms
;
180 render_frame()->GetWebFrame()->document().forms(forms
);
181 for (size_t i
= 0; i
< forms
.size(); ++i
) {
182 if (forms
[i
].isNull())
185 // If we can't get a valid PasswordForm, we skip this form because the
186 // the password won't get saved even if we generate it.
187 scoped_ptr
<PasswordForm
> password_form(
188 CreatePasswordForm(forms
[i
], nullptr));
189 if (!password_form
.get()) {
190 VLOG(2) << "Skipping form as it would not be saved";
194 // Do not generate password for GAIA since it is used to retrieve the
195 // generated paswords.
196 GURL
realm(password_form
->signon_realm
);
197 if (realm
== GaiaUrls::GetInstance()->gaia_login_form_realm())
200 std::vector
<blink::WebInputElement
> passwords
;
201 if (GetAccountCreationPasswordFields(forms
[i
], &passwords
)) {
202 AccountCreationFormData
ac_form_data(
203 make_linked_ptr(password_form
.release()), passwords
);
204 possible_account_creation_forms_
.push_back(ac_form_data
);
208 if (!possible_account_creation_forms_
.empty()) {
209 VLOG(2) << possible_account_creation_forms_
.size()
210 << " possible account creation forms deteceted";
211 DetermineGenerationElement();
215 bool PasswordGenerationAgent::ShouldAnalyzeDocument() const {
216 // Make sure that this security origin is allowed to use password manager.
217 // Generating a password that can't be saved is a bad idea.
218 blink::WebSecurityOrigin origin
=
219 render_frame()->GetWebFrame()->document().securityOrigin();
220 if (!origin
.canAccessPasswordManager()) {
221 VLOG(1) << "No PasswordManager access";
228 bool PasswordGenerationAgent::OnMessageReceived(const IPC::Message
& message
) {
230 IPC_BEGIN_MESSAGE_MAP(PasswordGenerationAgent
, message
)
231 IPC_MESSAGE_HANDLER(AutofillMsg_FormNotBlacklisted
,
232 OnFormNotBlacklisted
)
233 IPC_MESSAGE_HANDLER(AutofillMsg_GeneratedPasswordAccepted
,
235 IPC_MESSAGE_HANDLER(AutofillMsg_AccountCreationFormsDetected
,
236 OnAccountCreationFormsDetected
)
237 IPC_MESSAGE_UNHANDLED(handled
= false)
238 IPC_END_MESSAGE_MAP()
242 void PasswordGenerationAgent::OnFormNotBlacklisted(const PasswordForm
& form
) {
243 not_blacklisted_password_form_origins_
.push_back(form
.origin
);
244 DetermineGenerationElement();
247 void PasswordGenerationAgent::OnPasswordAccepted(
248 const base::string16
& password
) {
249 password_is_generated_
= true;
250 password_generation::LogPasswordGenerationEvent(
251 password_generation::PASSWORD_ACCEPTED
);
252 for (auto& password_element
: generation_form_data_
->password_elements
) {
253 password_element
.setValue(password
, true /* sendEvents */);
254 password_element
.setAutofilled(true);
255 // Advance focus to the next input field. We assume password fields in
256 // an account creation form are always adjacent.
257 render_frame()->GetRenderView()->GetWebView()->advanceFocus(false);
261 void PasswordGenerationAgent::OnAccountCreationFormsDetected(
262 const std::vector
<autofill::FormData
>& forms
) {
263 generation_enabled_forms_
.insert(
264 generation_enabled_forms_
.end(), forms
.begin(), forms
.end());
265 DetermineGenerationElement();
268 void PasswordGenerationAgent::DetermineGenerationElement() {
269 if (generation_form_data_
) {
270 VLOG(2) << "Account creation form already found";
274 // Make sure local heuristics have identified a possible account creation
276 if (possible_account_creation_forms_
.empty()) {
277 VLOG(2) << "Local hueristics have not detected a possible account "
282 for (auto& possible_form_data
: possible_account_creation_forms_
) {
283 PasswordForm
* possible_password_form
= possible_form_data
.form
.get();
284 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
285 switches::kLocalHeuristicsOnlyForPasswordGeneration
)) {
286 VLOG(2) << "Bypassing additional checks.";
287 } else if (!ContainsURL(not_blacklisted_password_form_origins_
,
288 possible_password_form
->origin
)) {
289 VLOG(2) << "Have not received confirmation that password form isn't "
292 } else if (!ContainsForm(generation_enabled_forms_
,
293 *possible_password_form
)) {
294 // Note that this message will never be sent if this feature is disabled
295 // (e.g. Password saving is disabled).
296 VLOG(2) << "Have not received confirmation from Autofill that form is "
297 << "used for account creation";
301 VLOG(2) << "Password generation eligible form found";
302 generation_form_data_
.reset(
303 new AccountCreationFormData(possible_form_data
.form
,
304 possible_form_data
.password_elements
));
305 generation_element_
= generation_form_data_
->password_elements
[0];
306 generation_element_
.setAttribute("aria-autocomplete", "list");
307 password_generation::LogPasswordGenerationEvent(
308 password_generation::GENERATION_AVAILABLE
);
309 possible_account_creation_forms_
.clear();
314 bool PasswordGenerationAgent::FocusedNodeHasChanged(
315 const blink::WebNode
& node
) {
316 if (!generation_element_
.isNull())
317 generation_element_
.setShouldRevealPassword(false);
319 if (node
.isNull() || !node
.isElementNode())
322 const blink::WebElement web_element
= node
.toConst
<blink::WebElement
>();
323 if (!web_element
.document().frame())
326 const blink::WebInputElement
* element
= toWebInputElement(&web_element
);
327 if (!element
|| *element
!= generation_element_
)
330 if (password_is_generated_
) {
331 generation_element_
.setShouldRevealPassword(true);
336 // Assume that if the password field has less than kMaximumOfferSize
337 // characters then the user is not finished typing their password and display
338 // the password suggestion.
339 if (!element
->isReadOnly() &&
340 element
->isEnabled() &&
341 element
->value().length() <= kMaximumOfferSize
) {
342 ShowGenerationPopup();
349 bool PasswordGenerationAgent::TextDidChangeInTextField(
350 const blink::WebInputElement
& element
) {
351 if (element
!= generation_element_
)
354 if (element
.value().isEmpty()) {
355 if (password_is_generated_
) {
356 // User generated a password and then deleted it.
357 password_generation::LogPasswordGenerationEvent(
358 password_generation::PASSWORD_DELETED
);
359 CopyValueToAllInputElements(element
.value(),
360 &generation_form_data_
->password_elements
);
363 // Do not treat the password as generated.
364 // TODO(gcasto): Set PasswordForm::type in the browser to TYPE_NORMAL.
365 password_is_generated_
= false;
366 generation_element_
.setShouldRevealPassword(false);
368 // Offer generation again.
369 ShowGenerationPopup();
370 } else if (password_is_generated_
) {
371 password_edited_
= true;
372 // Mirror edits to any confirmation password fields.
373 CopyValueToAllInputElements(element
.value(),
374 &generation_form_data_
->password_elements
);
375 } else if (element
.value().length() > kMaximumOfferSize
) {
376 // User has rejected the feature and has started typing a password.
379 // Password isn't generated and there are fewer than kMaximumOfferSize
380 // characters typed, so keep offering the password. Note this function
381 // will just keep the previous popup if one is already showing.
382 ShowGenerationPopup();
388 void PasswordGenerationAgent::ShowGenerationPopup() {
389 gfx::RectF bounding_box_scaled
= GetScaledBoundingBox(
390 render_frame()->GetRenderView()->GetWebView()->pageScaleFactor(),
391 &generation_element_
);
393 Send(new AutofillHostMsg_ShowPasswordGenerationPopup(
396 generation_element_
.maxLength(),
397 *generation_form_data_
->form
));
399 generation_popup_shown_
= true;
402 void PasswordGenerationAgent::ShowEditingPopup() {
403 gfx::RectF bounding_box_scaled
= GetScaledBoundingBox(
404 render_frame()->GetRenderView()->GetWebView()->pageScaleFactor(),
405 &generation_element_
);
407 Send(new AutofillHostMsg_ShowPasswordEditingPopup(
410 *generation_form_data_
->form
));
412 editing_popup_shown_
= true;
415 void PasswordGenerationAgent::HidePopup() {
416 Send(new AutofillHostMsg_HidePasswordGenerationPopup(routing_id()));
419 } // namespace autofill