Revert "Fix broken channel icon in chrome://help on CrOS" and try again
[chromium-blink-merge.git] / components / autofill / content / renderer / password_generation_agent.cc
blob2555541b7aa05e0d13a87b2d515a29c7198851ae
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<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))
79 return true;
81 return false;
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;
99 } // namespace
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
133 // each frame.
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);
144 } else {
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() {
177 if (!enabled_)
178 return;
180 // We don't want to generate passwords if the browser won't store or sync
181 // them.
182 if (!ShouldAnalyzeDocument())
183 return;
185 // If we have already found a signup form for this page, no need to continue.
186 if (generation_form_data_)
187 return;
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())
193 continue;
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";
201 continue;
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())
208 continue;
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";
232 return false;
235 return true;
238 bool PasswordGenerationAgent::OnMessageReceived(const IPC::Message& message) {
239 bool handled = true;
240 IPC_BEGIN_MESSAGE_MAP(PasswordGenerationAgent, message)
241 IPC_MESSAGE_HANDLER(AutofillMsg_FormNotBlacklisted,
242 OnFormNotBlacklisted)
243 IPC_MESSAGE_HANDLER(AutofillMsg_GeneratedPasswordAccepted,
244 OnPasswordAccepted)
245 IPC_MESSAGE_HANDLER(AutofillMsg_AccountCreationFormsDetected,
246 OnAccountCreationFormsDetected)
247 IPC_MESSAGE_UNHANDLED(handled = false)
248 IPC_END_MESSAGE_MAP()
249 return handled;
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";
286 return;
289 // Make sure local heuristics have identified a possible account creation
290 // form.
291 if (possible_account_creation_forms_.empty()) {
292 VLOG(2) << "Local hueristics have not detected a possible account "
293 << "creation form";
294 return;
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 "
307 << "blacklisted";
308 continue;
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 "
313 << "attributes";
314 password_generation::LogPasswordGenerationEvent(
315 password_generation::AUTOCOMPLETE_ATTRIBUTES_ENABLED_GENERATION);
316 } else {
317 VLOG(2) << "Have not received confirmation from Autofill that form is "
318 << "used for account creation";
319 continue;
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(
333 routing_id(),
334 *generation_form_data_->form));
335 return;
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())
345 return false;
347 const blink::WebElement web_element = node.toConst<blink::WebElement>();
348 if (!web_element.document().frame())
349 return false;
351 const blink::WebInputElement* element = toWebInputElement(&web_element);
352 if (!element || *element != generation_element_)
353 return false;
355 if (password_is_generated_) {
356 generation_element_.setShouldRevealPassword(true);
357 ShowEditingPopup();
358 return 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();
368 return true;
371 return false;
374 bool PasswordGenerationAgent::TextDidChangeInTextField(
375 const blink::WebInputElement& element) {
376 if (element != generation_element_)
377 return false;
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(
387 routing_id(),
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.
404 HidePopup();
405 } else {
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();
412 return true;
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(
421 routing_id(),
422 bounding_box_scaled,
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(
435 routing_id(),
436 bounding_box_scaled,
437 *generation_form_data_->form));
439 editing_popup_shown_ = true;
442 void PasswordGenerationAgent::HidePopup() {
443 Send(new AutofillHostMsg_HidePasswordGenerationPopup(routing_id()));
446 } // namespace autofill