Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / autofill / content / renderer / password_generation_agent.cc
blob4cd032c5190e5ee737f5e4eea967d2bc3de99bde
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"
30 namespace autofill {
32 namespace {
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()) {
48 num_input_elements++;
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
56 // creation form.
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
60 // to fill those.
61 if (passwords->size() > 2)
62 passwords->resize(2);
64 return true;
67 return false;
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))
78 return true;
80 return false;
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;
98 } // namespace
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
132 // each frame.
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);
143 } else {
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() {
176 if (!enabled_)
177 return;
179 // We don't want to generate passwords if the browser won't store or sync
180 // them.
181 if (!ShouldAnalyzeDocument())
182 return;
184 // If we have already found a signup form for this page, no need to continue.
185 if (generation_form_data_)
186 return;
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())
192 continue;
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";
200 continue;
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())
207 continue;
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";
231 return false;
234 return true;
237 bool PasswordGenerationAgent::OnMessageReceived(const IPC::Message& message) {
238 bool handled = true;
239 IPC_BEGIN_MESSAGE_MAP(PasswordGenerationAgent, message)
240 IPC_MESSAGE_HANDLER(AutofillMsg_FormNotBlacklisted,
241 OnFormNotBlacklisted)
242 IPC_MESSAGE_HANDLER(AutofillMsg_GeneratedPasswordAccepted,
243 OnPasswordAccepted)
244 IPC_MESSAGE_HANDLER(AutofillMsg_AccountCreationFormsDetected,
245 OnAccountCreationFormsDetected)
246 IPC_MESSAGE_UNHANDLED(handled = false)
247 IPC_END_MESSAGE_MAP()
248 return handled;
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";
285 return;
288 // Make sure local heuristics have identified a possible account creation
289 // form.
290 if (possible_account_creation_forms_.empty()) {
291 VLOG(2) << "Local hueristics have not detected a possible account "
292 << "creation form";
293 return;
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 "
306 << "blacklisted";
307 continue;
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 "
312 << "attributes";
313 password_generation::LogPasswordGenerationEvent(
314 password_generation::AUTOCOMPLETE_ATTRIBUTES_ENABLED_GENERATION);
315 } else {
316 VLOG(2) << "Have not received confirmation from Autofill that form is "
317 << "used for account creation";
318 continue;
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(
332 routing_id(),
333 *generation_form_data_->form));
334 return;
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())
344 return false;
346 const blink::WebElement web_element = node.toConst<blink::WebElement>();
347 if (!web_element.document().frame())
348 return false;
350 const blink::WebInputElement* element = toWebInputElement(&web_element);
351 if (!element || *element != generation_element_)
352 return false;
354 if (password_is_generated_) {
355 generation_element_.setShouldRevealPassword(true);
356 ShowEditingPopup();
357 return 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();
367 return true;
370 return false;
373 bool PasswordGenerationAgent::TextDidChangeInTextField(
374 const blink::WebInputElement& element) {
375 if (element != generation_element_)
376 return false;
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(
386 routing_id(),
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.
403 HidePopup();
404 } else {
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();
411 return true;
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(
420 routing_id(),
421 bounding_box_scaled,
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(
434 routing_id(),
435 bounding_box_scaled,
436 *generation_form_data_->form));
438 editing_popup_shown_ = true;
441 void PasswordGenerationAgent::HidePopup() {
442 Send(new AutofillHostMsg_HidePasswordGenerationPopup(routing_id()));
445 } // namespace autofill