Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(191)

Side by Side Diff: ios/chrome/browser/autofill/autofill_agent.mm

Issue 2580363002: Upstream Chrome on iOS source code [1/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 #import "ios/chrome/browser/autofill/autofill_agent.h"
6
7 #include <memory>
8 #include <string>
9
10 #include "base/format_macros.h"
11 #include "base/guid.h"
12 #include "base/json/json_reader.h"
13 #include "base/json/json_writer.h"
14 #include "base/mac/foundation_util.h"
15 #include "base/mac/scoped_block.h"
16 #include "base/metrics/field_trial.h"
17 #include "base/strings/string16.h"
18 #include "base/strings/sys_string_conversions.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "components/autofill/core/browser/autofill_manager.h"
22 #include "components/autofill/core/browser/autofill_metrics.h"
23 #include "components/autofill/core/browser/autofill_profile.h"
24 #include "components/autofill/core/browser/credit_card.h"
25 #include "components/autofill/core/browser/keyboard_accessory_metrics_logger.h"
26 #include "components/autofill/core/browser/popup_item_ids.h"
27 #include "components/autofill/core/common/autofill_constants.h"
28 #include "components/autofill/core/common/autofill_pref_names.h"
29 #include "components/autofill/core/common/autofill_util.h"
30 #include "components/autofill/core/common/form_data.h"
31 #include "components/autofill/core/common/form_field_data.h"
32 #include "components/autofill/ios/browser/autofill_driver_ios.h"
33 #import "components/autofill/ios/browser/form_suggestion.h"
34 #import "components/autofill/ios/browser/js_autofill_manager.h"
35 #include "components/prefs/pref_service.h"
36 #include "ios/chrome/browser/application_context.h"
37 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
38 #include "ios/chrome/browser/pref_names.h"
39 #include "ios/web/public/url_scheme_util.h"
40 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
41 #include "ios/web/public/web_state/url_verification_constants.h"
42 #import "ios/web/public/web_state/web_state.h"
43 #include "ui/gfx/geometry/rect.h"
44 #include "url/gurl.h"
45
46 #if !defined(__has_feature) || !__has_feature(objc_arc)
47 #error "This file requires ARC support."
48 #endif
49
50 namespace {
51
52 using FormDataVector = std::vector<autofill::FormData>;
53
54 // The type of the completion handler block for
55 // |fetchFormsWithName:minimumRequiredFieldsCount:pageURL:completionHandler|
56 typedef void (^FetchFormsCompletionHandler)(BOOL, const FormDataVector&);
57
58 // Gets the first form and field specified by |fieldName| from |forms|,
59 // modifying the returned field so that input elements are also handled.
60 void GetFormAndField(autofill::FormData* form,
61 autofill::FormFieldData* field,
62 const FormDataVector& forms,
63 const std::string& fieldName,
64 const std::string& type) {
65 DCHECK_GE(forms.size(), 1U);
66 *form = forms[0];
67 const base::string16 fieldName16 = base::UTF8ToUTF16(fieldName);
68 for (const auto& currentField : form->fields) {
69 if (currentField.name == fieldName16) {
70 *field = currentField;
71 break;
72 }
73 }
74 if (field->SameFieldAs(autofill::FormFieldData()))
75 return;
76
77 // Hack to get suggestions from select input elements.
78 if (field->form_control_type == "select-one") {
79 // Any value set will cause the AutofillManager to filter suggestions (only
80 // show suggestions that begin the same as the current value) with the
81 // effect that one only suggestion would be returned; the value itself.
82 field->value = base::string16();
83 }
84 }
85
86 } // namespace
87
88 @interface AutofillAgent ()<CRWWebStateObserver>
89
90 // Notifies the autofill manager when forms are detected on a page.
91 - (void)notifyAutofillManager:(autofill::AutofillManager*)autofillManager
92 ofFormsSeen:(const FormDataVector&)forms;
93
94 // Notifies the autofill manager when forms are submitted.
95 - (void)notifyAutofillManager:(autofill::AutofillManager*)autofillManager
96 ofFormsSubmitted:(const FormDataVector&)forms
97 userInitiated:(BOOL)userInitiated;
98
99 // Invokes the form extraction script and loads the output into the format
100 // expected by the AutofillManager.
101 // If |formName| is non-empty, only a form of that name is extracted.
102 // Only forms with at least |requiredFieldsCount| fields are extracted.
103 // Calls |completionHandler| with a success BOOL of YES and the form data that
104 // was extracted.
105 // Calls |completionHandler| with NO if the forms could not be extracted.
106 // |completionHandler| cannot be nil.
107 - (void)fetchFormsWithName:(const base::string16&)formName
108 minimumRequiredFieldsCount:(NSUInteger)requiredFieldsCount
109 pageURL:(const GURL&)pageURL
110 completionHandler:(FetchFormsCompletionHandler)completionHandler;
111
112 // Processes the JSON form data extracted from the page into the format expected
113 // by AutofillManager and fills it in |formsData|.
114 // |formsData| cannot be nil.
115 // Returns a BOOL indicating the success value and the vector of form data.
116 - (BOOL)getExtractedFormsData:(FormDataVector*)formsData
117 fromJSON:(NSString*)formJSON
118 formName:(const base::string16&)formName
119 pageURL:(const GURL&)pageURL;
120
121 // Processes the JSON form data extracted from the page when form activity is
122 // detected and informs the AutofillManager.
123 - (void)processFormActivityExtractedData:(const FormDataVector&)forms
124 fieldName:(const std::string&)fieldName
125 type:(const std::string&)type
126 webState:(web::WebState*)webState;
127
128 // Sends a request to AutofillManager to retrieve suggestions for the specified
129 // form and field.
130 - (void)queryAutofillWithForms:(const FormDataVector&)forms
131 field:(NSString*)fieldName
132 type:(NSString*)type
133 typedValue:(NSString*)typedValue
134 webState:(web::WebState*)webState
135 completionHandler:(SuggestionsAvailableCompletion)completion;
136
137 // Rearranges and filters the suggestions to move profile or credit card
138 // suggestions to the front if the user has selected one recently and remove
139 // key/value suggestions if the user hasn't started typing.
140 - (NSArray*)processSuggestions:(NSArray*)suggestions;
141
142 @end
143
144 @implementation AutofillAgent {
145 // Timestamp of the first time forms are seen.
146 base::TimeTicks formsSeenTimestamp_;
147
148 // Bridge to observe the web state from Objective-C.
149 std::unique_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
150
151 // The browser state for which this agent was created.
152 ios::ChromeBrowserState* browserState_;
153
154 // Manager for Autofill JavaScripts.
155 JsAutofillManager* jsAutofillManager_;
156
157 // The name of the most recent autocomplete field; tracks the currently-
158 // focused form element in order to force filling of the currently selected
159 // form element, even if it's non-empty.
160 base::string16 pendingAutocompleteField_;
161 // The identifier of the most recent suggestion accepted by the user. Only
162 // used to reorder future suggestion lists, placing matching suggestions first
163 // in the list.
164 NSInteger mostRecentSelectedIdentifier_;
165
166 // Suggestions state:
167 // The most recent form suggestions.
168 NSArray* mostRecentSuggestions_;
169 // The completion to inform FormSuggestionController that a user selection
170 // has been handled.
171 SuggestionHandledCompletion suggestionHandledCompletion_;
172 // The completion to inform FormSuggestionController that suggestions are
173 // available for a given form and field.
174 SuggestionsAvailableCompletion suggestionsAvailableCompletion_;
175 // The text entered by the user into the active field.
176 NSString* typedValue_;
177 // Popup delegate for the most recent suggestions.
178 // The reference is weak because a weak pointer is sent to our
179 // AutofillManagerDelegate.
180 base::WeakPtr<autofill::AutofillPopupDelegate> popupDelegate_;
181 }
182
183 @synthesize browserState = browserState_;
184
185 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
186 webState:(web::WebState*)webState {
187 DCHECK(browserState);
188 DCHECK(webState);
189 self = [super init];
190 if (self) {
191 browserState_ = browserState;
192 _webStateObserverBridge.reset(
193 new web::WebStateObserverBridge(webState, self));
194 jsAutofillManager_ = base::mac::ObjCCastStrict<JsAutofillManager>(
195 [webState->GetJSInjectionReceiver()
196 instanceOfClass:[JsAutofillManager class]]);
197 }
198 return self;
199 }
200
201 - (instancetype)init {
202 NOTREACHED();
203 return nil;
204 }
205
206 - (void)detachFromWebState {
207 [[NSNotificationCenter defaultCenter] removeObserver:self];
208 _webStateObserverBridge.reset();
209 }
210
211 #pragma mark -
212 #pragma mark Private
213
214 // Returns the autofill manager associated with a web::WebState instance.
215 // Returns nullptr if there is no autofill manager associated anymore, this can
216 // happen when |close| has been called on the |webState|. Also returns nullptr
217 // if detachFromWebState has been called.
218 - (autofill::AutofillManager*)autofillManagerFromWebState:
219 (web::WebState*)webState {
220 if (!webState || !_webStateObserverBridge)
221 return nullptr;
222 return autofill::AutofillDriverIOS::FromWebState(webState)
223 ->autofill_manager();
224 }
225
226 // Extracts a single form field from the JSON dictionary into a FormFieldData
227 // object.
228 // Returns NO if the field could not be extracted.
229 - (BOOL)extractFormField:(const base::DictionaryValue&)field
230 asFieldData:(autofill::FormFieldData*)fieldData {
231 if (!field.GetString("name", &fieldData->name) ||
232 !field.GetString("form_control_type", &fieldData->form_control_type)) {
233 return NO;
234 }
235
236 // Optional fields.
237 field.GetString("label", &fieldData->label);
238 field.GetString("value", &fieldData->value);
239 field.GetString("autocomplete_attribute", &fieldData->autocomplete_attribute);
240
241 int maxLength;
242 if (field.GetInteger("max_length", &maxLength))
243 fieldData->max_length = maxLength;
244
245 field.GetBoolean("is_autofilled", &fieldData->is_autofilled);
246
247 // TODO(crbug.com/427614): Extract |is_checked|.
248 bool isCheckable = false;
249 field.GetBoolean("is_checkable", &isCheckable);
250 autofill::SetCheckStatus(fieldData, isCheckable, false);
251
252 field.GetBoolean("is_focusable", &fieldData->is_focusable);
253 field.GetBoolean("should_autocomplete", &fieldData->should_autocomplete);
254
255 // ROLE_ATTRIBUTE_OTHER is the default value. The only other value as of this
256 // writing is ROLE_ATTRIBUTE_PRESENTATION.
257 int role;
258 if (field.GetInteger("role", &role) &&
259 role == autofill::AutofillField::ROLE_ATTRIBUTE_PRESENTATION) {
260 fieldData->role = autofill::AutofillField::ROLE_ATTRIBUTE_PRESENTATION;
261 }
262
263 // TODO(crbug.com/427614): Extract |text_direction|.
264
265 // Load option values where present.
266 const base::ListValue* optionValues;
267 if (field.GetList("option_values", &optionValues)) {
268 for (const auto& optionValue : *optionValues) {
269 base::string16 value;
270 if (optionValue->GetAsString(&value))
271 fieldData->option_values.push_back(value);
272 }
273 }
274
275 // Load option contents where present.
276 const base::ListValue* optionContents;
277 if (field.GetList("option_contents", &optionContents)) {
278 for (const auto& optionContent : *optionContents) {
279 base::string16 content;
280 if (optionContent->GetAsString(&content))
281 fieldData->option_contents.push_back(content);
282 }
283 }
284
285 if (fieldData->option_values.size() != fieldData->option_contents.size())
286 return NO; // Option values and contents lists should match 1-1.
287
288 return YES;
289 }
290
291 - (void)notifyAutofillManager:(autofill::AutofillManager*)autofillManager
292 ofFormsSeen:(const FormDataVector&)forms {
293 DCHECK(autofillManager);
294 DCHECK(!forms.empty());
295 autofillManager->Reset();
296 autofillManager->OnFormsSeen(forms, formsSeenTimestamp_);
297 }
298
299 - (void)notifyAutofillManager:(autofill::AutofillManager*)autofillManager
300 ofFormsSubmitted:(const FormDataVector&)forms
301 userInitiated:(BOOL)userInitiated {
302 DCHECK(autofillManager);
303 // Exactly one form should be extracted.
304 DCHECK_EQ(1U, forms.size());
305 autofill::FormData form = forms[0];
306
307 // iOS doesn't get a separate "will submit form" notification so call
308 // OnWillSubmitForm() here.
309 autofillManager->OnWillSubmitForm(form, base::TimeTicks::Now());
310 autofillManager->OnFormSubmitted(form);
311 autofill::KeyboardAccessoryMetricsLogger::OnFormSubmitted();
312 }
313
314 - (void)fetchFormsWithName:(const base::string16&)formName
315 minimumRequiredFieldsCount:(NSUInteger)requiredFieldsCount
316 pageURL:(const GURL&)pageURL
317 completionHandler:(FetchFormsCompletionHandler)completionHandler {
318 DCHECK(completionHandler);
319 // Necessary so the values can be used inside a block.
320 base::string16 formNameCopy = formName;
321 GURL pageURLCopy = pageURL;
322 __weak AutofillAgent* weakSelf = self;
323 [jsAutofillManager_
324 fetchFormsWithMinimumRequiredFieldsCount:requiredFieldsCount
325 completionHandler:^(NSString* formJSON) {
326 std::vector<autofill::FormData> formData;
327 BOOL success =
328 [weakSelf getExtractedFormsData:&formData
329 fromJSON:formJSON
330 formName:formNameCopy
331 pageURL:pageURLCopy];
332 completionHandler(success, formData);
333 }];
334 }
335
336 - (BOOL)getExtractedFormsData:(FormDataVector*)formsData
337 fromJSON:(NSString*)formJSON
338 formName:(const base::string16&)formName
339 pageURL:(const GURL&)pageURL {
340 DCHECK(formsData);
341 // Convert JSON string to JSON object |dataJson|.
342 int errorCode = 0;
343 std::string errorMessage;
344 std::unique_ptr<base::Value> dataJson(base::JSONReader::ReadAndReturnError(
345 base::SysNSStringToUTF8(formJSON), base::JSON_PARSE_RFC, &errorCode,
346 &errorMessage));
347 if (errorCode) {
348 LOG(WARNING) << "JSON parse error in form extraction: "
349 << errorMessage.c_str();
350 return NO;
351 }
352
353 // Returned data should be a dictionary.
354 const base::DictionaryValue* data;
355 if (!dataJson->GetAsDictionary(&data))
356 return NO;
357
358 // Get the list of forms.
359 const base::ListValue* formsList;
360 if (!data->GetList("forms", &formsList))
361 return NO;
362
363 // Iterate through all the extracted forms and copy the data from JSON into
364 // AutofillManager structures.
365 for (const auto& formDict : *formsList) {
366 // Each form list entry should be a JSON dictionary.
367 const base::DictionaryValue* formData;
368 if (!formDict->GetAsDictionary(&formData))
369 return NO;
370
371 // Form data is copied into a FormData object field-by-field.
372 autofill::FormData form;
373 if (!formData->GetString("name", &form.name))
374 return NO;
375 if (!formName.empty() && formName != form.name)
376 continue;
377
378 // Origin is mandatory.
379 base::string16 origin;
380 if (!formData->GetString("origin", &origin))
381 return NO;
382
383 // Use GURL object to verify origin of host page URL.
384 form.origin = GURL(origin);
385 if (form.origin.GetOrigin() != pageURL.GetOrigin()) {
386 LOG(WARNING) << "Form extraction aborted due to same origin policy";
387 return NO;
388 }
389
390 // Action is optional.
391 base::string16 action;
392 formData->GetString("action", &action);
393 form.action = GURL(action);
394
395 // Is form tag is optional.
396 bool is_form_tag;
397 if (formData->GetBoolean("is_form_tag", &is_form_tag))
398 form.is_form_tag = is_form_tag;
399
400 // Field list (mandatory) is extracted.
401 const base::ListValue* fieldsList;
402 if (!formData->GetList("fields", &fieldsList))
403 return NO;
404 for (const auto& fieldDict : *fieldsList) {
405 const base::DictionaryValue* field;
406 autofill::FormFieldData fieldData;
407 if (fieldDict->GetAsDictionary(&field) &&
408 [self extractFormField:*field asFieldData:&fieldData]) {
409 form.fields.push_back(fieldData);
410 } else {
411 return NO;
412 }
413 }
414 formsData->push_back(form);
415 }
416 return YES;
417 }
418
419 - (NSArray*)processSuggestions:(NSArray*)suggestions {
420 // The suggestion array is cloned (to claim ownership) and to slightly
421 // reorder; a future improvement is to base order on text typed in other
422 // fields by users as well as accepted suggestions (crbug.com/245261).
423 NSMutableArray* suggestionsCopy = [suggestions mutableCopy];
424
425 // If the most recently selected suggestion was a profile or credit card
426 // suggestion, move it to the front of the suggestions.
427 if (mostRecentSelectedIdentifier_ > 0) {
428 NSUInteger idx = [suggestionsCopy
429 indexOfObjectPassingTest:^BOOL(id obj, NSUInteger, BOOL*) {
430 FormSuggestion* suggestion = obj;
431 return suggestion.identifier == mostRecentSelectedIdentifier_;
432 }];
433
434 if (idx != NSNotFound) {
435 FormSuggestion* suggestion = suggestionsCopy[idx];
436 [suggestionsCopy removeObjectAtIndex:idx];
437 [suggestionsCopy insertObject:suggestion atIndex:0];
438 }
439 }
440
441 // Filter out any key/value suggestions if the user hasn't typed yet.
442 if ([typedValue_ length] == 0) {
443 for (NSInteger idx = [suggestionsCopy count] - 1; idx >= 0; idx--) {
444 FormSuggestion* suggestion = suggestionsCopy[idx];
445 if (suggestion.identifier == autofill::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY) {
446 [suggestionsCopy removeObjectAtIndex:idx];
447 }
448 }
449 }
450
451 // If "clear form" entry exists then move it to the front of the suggestions.
452 for (NSInteger idx = [suggestionsCopy count] - 1; idx > 0; idx--) {
453 FormSuggestion* suggestion = suggestionsCopy[idx];
454 if (suggestion.identifier == autofill::POPUP_ITEM_ID_CLEAR_FORM) {
455 FormSuggestion* suggestionToMove = suggestionsCopy[idx];
456 [suggestionsCopy removeObjectAtIndex:idx];
457 [suggestionsCopy insertObject:suggestionToMove atIndex:0];
458 break;
459 }
460 }
461
462 return suggestionsCopy;
463 }
464
465 - (void)onSuggestionsReady:(NSArray*)suggestions
466 popupDelegate:
467 (const base::WeakPtr<autofill::AutofillPopupDelegate>&)
468 delegate {
469 popupDelegate_ = delegate;
470 mostRecentSuggestions_ = [[self processSuggestions:suggestions] copy];
471 if (suggestionsAvailableCompletion_)
472 suggestionsAvailableCompletion_([mostRecentSuggestions_ count] > 0);
473 suggestionsAvailableCompletion_ = nil;
474 }
475
476 #pragma mark -
477 #pragma mark FormSuggestionProvider
478
479 - (void)queryAutofillWithForms:(const FormDataVector&)forms
480 field:(NSString*)fieldName
481 type:(NSString*)type
482 typedValue:(NSString*)typedValue
483 webState:(web::WebState*)webState
484 completionHandler:(SuggestionsAvailableCompletion)completion {
485 autofill::AutofillManager* autofillManager =
486 [self autofillManagerFromWebState:webState];
487 if (!autofillManager)
488 return;
489
490 // Passed to delegates; we don't use it so it's set to zero.
491 int queryId = 0;
492
493 // Find the right form and field.
494 autofill::FormFieldData field;
495 autofill::FormData form;
496 GetFormAndField(&form, &field, forms, base::SysNSStringToUTF8(fieldName),
497 base::SysNSStringToUTF8(type));
498
499 // Save the completion and go look for suggestions.
500 suggestionsAvailableCompletion_ = [completion copy];
501 typedValue_ = [typedValue copy];
502
503 // Query the AutofillManager for suggestions. Results will arrive in
504 // [AutofillController showAutofillPopup].
505 autofillManager->OnQueryFormFieldAutofill(queryId, form, field, gfx::RectF());
506 }
507
508 - (void)checkIfSuggestionsAvailableForForm:(NSString*)formName
509 field:(NSString*)fieldName
510 type:(NSString*)type
511 typedValue:(NSString*)typedValue
512 webState:(web::WebState*)webState
513 completionHandler:
514 (SuggestionsAvailableCompletion)completion {
515 web::URLVerificationTrustLevel trustLevel;
516 const GURL pageURL(webState->GetCurrentURL(&trustLevel));
517 if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
518 DLOG(WARNING) << "Suggestions not offered on untrusted page";
519 completion(NO);
520 return;
521 }
522
523 // Once the active form and field are extracted, send a query to the
524 // AutofillManager for suggestions.
525 __weak AutofillAgent* weakSelf = self;
526 id completionHandler = ^(BOOL success, const FormDataVector& forms) {
527 if (success && forms.size() == 1) {
528 [weakSelf queryAutofillWithForms:forms
529 field:fieldName
530 type:type
531 typedValue:typedValue
532 webState:webState
533 completionHandler:completion];
534 }
535 };
536
537 // Re-extract the active form and field only. All forms with at least one
538 // input element are considered because key/value suggestions are offered
539 // even on short forms.
540 [self fetchFormsWithName:base::SysNSStringToUTF16(formName)
541 minimumRequiredFieldsCount:1
542 pageURL:pageURL
543 completionHandler:completionHandler];
544 }
545
546 - (void)retrieveSuggestionsForForm:(NSString*)formName
547 field:(NSString*)fieldName
548 type:(NSString*)type
549 typedValue:(NSString*)typedValue
550 webState:(web::WebState*)webState
551 completionHandler:(SuggestionsReadyCompletion)completion {
552 DCHECK(mostRecentSuggestions_)
553 << "Requestor should have called "
554 << "|checkIfSuggestionsAvailableForForm:field:type:completionHandler:| "
555 << "and waited for the result before calling "
556 << "|retrieveSuggestionsForForm:field:type:completionHandler:|.";
557 completion(mostRecentSuggestions_, self);
558 }
559
560 - (void)didSelectSuggestion:(FormSuggestion*)suggestion
561 forField:(NSString*)fieldName
562 form:(NSString*)formName
563 completionHandler:(SuggestionHandledCompletion)completion {
564 [[UIDevice currentDevice] playInputClick];
565 suggestionHandledCompletion_ = [completion copy];
566 mostRecentSelectedIdentifier_ = suggestion.identifier;
567
568 if (suggestion.identifier > 0) {
569 pendingAutocompleteField_ = base::SysNSStringToUTF16(fieldName);
570 if (popupDelegate_) {
571 popupDelegate_->DidAcceptSuggestion(
572 base::SysNSStringToUTF16(suggestion.value), suggestion.identifier, 0);
573 }
574 } else if (suggestion.identifier ==
575 autofill::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY) {
576 // FormSuggestion is a simple, single value that can be filled out now.
577 [self fillField:base::SysNSStringToUTF8(fieldName)
578 formName:base::SysNSStringToUTF8(formName)
579 value:base::SysNSStringToUTF16(suggestion.value)];
580 } else if (suggestion.identifier == autofill::POPUP_ITEM_ID_CLEAR_FORM) {
581 [jsAutofillManager_
582 clearAutofilledFieldsForFormNamed:formName
583 completionHandler:suggestionHandledCompletion_];
584 suggestionHandledCompletion_ = nil;
585 } else {
586 NOTREACHED() << "unknown identifier " << suggestion.identifier;
587 }
588 }
589
590 #pragma mark -
591 #pragma mark CRWWebStateObserver
592
593 - (void)webStateDestroyed:(web::WebState*)webState {
594 [self detachFromWebState];
595 }
596
597 - (void)webState:(web::WebState*)webState
598 didSubmitDocumentWithFormNamed:(const std::string&)formName
599 userInitiated:(BOOL)userInitiated {
600 if (!browserState_->GetPrefs()->GetBoolean(autofill::prefs::kAutofillEnabled))
601 return;
602
603 web::URLVerificationTrustLevel trustLevel;
604 const GURL pageURL(webState->GetCurrentURL(&trustLevel));
605 if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
606 DLOG(WARNING) << "Form submit not handled on untrusted page";
607 return;
608 }
609
610 __weak AutofillAgent* weakSelf = self;
611 id completionHandler = ^(BOOL success, const FormDataVector& forms) {
612 AutofillAgent* strongSelf = weakSelf;
613 if (!strongSelf || !success)
614 return;
615 autofill::AutofillManager* autofillManager =
616 [strongSelf autofillManagerFromWebState:webState];
617 if (!autofillManager || forms.empty())
618 return;
619 if (forms.size() > 1) {
620 DLOG(WARNING) << "Only one form should be extracted.";
621 return;
622 }
623 [strongSelf notifyAutofillManager:autofillManager
624 ofFormsSubmitted:forms
625 userInitiated:userInitiated];
626
627 };
628 // This code is racing against the new page loading and will not get the
629 // password form data if the page has changed. In most cases this code wins
630 // the race.
631 // TODO(crbug.com/418827): Fix this by passing in more data from the JS side.
632 [self fetchFormsWithName:base::UTF8ToUTF16(formName)
633 minimumRequiredFieldsCount:1
634 pageURL:pageURL
635 completionHandler:completionHandler];
636 }
637
638 - (void)webStateDidLoadPage:(web::WebState*)webState {
639 if (!browserState_->GetPrefs()->GetBoolean(
640 autofill::prefs::kAutofillEnabled) ||
641 !webState->ContentIsHTML()) {
642 return;
643 }
644 [self processPage:webState];
645 }
646
647 - (void)processPage:(web::WebState*)webState {
648 web::URLVerificationTrustLevel trustLevel;
649 const GURL pageURL(webState->GetCurrentURL(&trustLevel));
650 if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
651 DLOG(WARNING) << "Page load not handled on untrusted page";
652 return;
653 }
654
655 if (!web::UrlHasWebScheme(pageURL))
656 return;
657
658 // This process is only done once.
659 if ([jsAutofillManager_ hasBeenInjected])
660 return;
661
662 popupDelegate_.reset();
663 suggestionsAvailableCompletion_ = nil;
664 suggestionHandledCompletion_ = nil;
665 mostRecentSuggestions_ = nil;
666 typedValue_ = nil;
667
668 [jsAutofillManager_ inject];
669
670 __weak AutofillAgent* weakSelf = self;
671 id completionHandler = ^(BOOL success, const FormDataVector& forms) {
672 AutofillAgent* strongSelf = weakSelf;
673 if (!strongSelf || !success)
674 return;
675 autofill::AutofillManager* autofillManager =
676 [strongSelf autofillManagerFromWebState:webState];
677 if (!autofillManager || forms.empty())
678 return;
679 [strongSelf notifyAutofillManager:autofillManager ofFormsSeen:forms];
680 };
681 // The document has now been fully loaded. Scan for forms to be extracted.
682 // Because of the cost of communicating with the server, only forms that have
683 // enough forms to make them likely candidates for profile completion are
684 // extracted.
685 [self fetchFormsWithName:base::string16()
686 minimumRequiredFieldsCount:autofill::kRequiredFieldsForPredictionRoutines
687 pageURL:pageURL
688 completionHandler:completionHandler];
689 }
690
691 - (void)webState:(web::WebState*)webState
692 didRegisterFormActivityWithFormNamed:(const std::string&)formName
693 fieldName:(const std::string&)fieldName
694 type:(const std::string&)type
695 value:(const std::string&)value
696 keyCode:(int)keyCode
697 inputMissing:(BOOL)inputMissing {
698 if (!browserState_->GetPrefs()->GetBoolean(autofill::prefs::kAutofillEnabled))
699 return;
700 web::URLVerificationTrustLevel trustLevel;
701 const GURL pageURL(webState->GetCurrentURL(&trustLevel));
702 if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
703 DLOG(WARNING) << "Form activity not handled on untrusted page";
704 return;
705 }
706
707 // Autofill and suggestion scripts are only injected for web URLs.
708 if (!web::UrlHasWebScheme(pageURL) || !webState->ContentIsHTML())
709 return;
710
711 // Returns early and reset the suggestion state if an error occurs.
712 if (inputMissing)
713 return;
714
715 // Processing the page can be needed here if Autofill is enabled in settings
716 // when the page is already loaded, or if the user focuses a field before the
717 // page is fully loaded.
718 [self processPage:webState];
719
720 // Blur not handled; we don't reset the suggestion state because if the
721 // keyboard is about to be dismissed there's no point. If not it means the
722 // next focus event will update the suggestion state within milliseconds, so
723 // if we do it now a flicker will be seen.
724 if (type.compare("blur") == 0)
725 return;
726
727 // Necessary so the strings can be used inside a block.
728 std::string fieldNameCopy = fieldName;
729 std::string typeCopy = type;
730
731 __weak AutofillAgent* weakSelf = self;
732 id completionHandler = ^(BOOL success, const FormDataVector& forms) {
733 if (success && forms.size() == 1) {
734 [weakSelf processFormActivityExtractedData:forms
735 fieldName:fieldNameCopy
736 type:typeCopy
737 webState:webState];
738 }
739 };
740
741 // Re-extract the active form and field only. There is no minimum field
742 // requirement because key/value suggestions are offered event on short forms.
743 [self fetchFormsWithName:base::UTF8ToUTF16(formName)
744 minimumRequiredFieldsCount:1
745 pageURL:pageURL
746 completionHandler:completionHandler];
747 }
748
749 - (void)processFormActivityExtractedData:(const FormDataVector&)forms
750 fieldName:(const std::string&)fieldName
751 type:(const std::string&)type
752 webState:(web::WebState*)webState {
753 autofill::AutofillManager* autofillManager =
754 [self autofillManagerFromWebState:webState];
755 if (!autofillManager)
756 return;
757
758 autofill::FormFieldData field;
759 autofill::FormData form;
760 GetFormAndField(&form, &field, forms, fieldName, type);
761
762 // Tell the manager about the form activity (for metrics).
763 if (type.compare("input") == 0 && (field.form_control_type == "text" ||
764 field.form_control_type == "password")) {
765 autofillManager->OnTextFieldDidChange(form, field, base::TimeTicks::Now());
766 }
767 }
768
769 #pragma mark -
770 #pragma mark AutofillViewClient
771
772 // Complete a field named |fieldName| on the form named |formName| using |value|
773 // then move the cursor.
774 // TODO(crbug.com/661621): |dataString| ends up at fillFormField() in
775 // autofill_controller.js. fillFormField() expects an AutofillFormFieldData
776 // object, which |dataString| is not because 'form' is not a specified member of
777 // AutofillFormFieldData. fillFormField() also expects members 'max_length' and
778 // 'is_checked' to exist.
779 - (void)fillField:(const std::string&)fieldName
780 formName:(const std::string&)formName
781 value:(const base::string16)value {
782 base::DictionaryValue data;
783 data.SetString("name", fieldName);
784 data.SetString("form", formName);
785 data.SetString("value", value);
786 std::string dataString;
787 base::JSONWriter::Write(data, &dataString);
788
789 DCHECK(suggestionHandledCompletion_);
790 [jsAutofillManager_ fillActiveFormField:base::SysUTF8ToNSString(dataString)
791 completionHandler:suggestionHandledCompletion_];
792 suggestionHandledCompletion_ = nil;
793 }
794
795 - (void)onFormDataFilled:(const autofill::FormData&)form {
796 std::unique_ptr<base::DictionaryValue> formData(new base::DictionaryValue);
797 formData->SetString("formName", base::UTF16ToUTF8(form.name));
798 // Note: Destruction of all child base::Value types is handled by the root
799 // formData object on its own destruction.
800 base::DictionaryValue* fieldsData = new base::DictionaryValue;
801
802 const std::vector<autofill::FormFieldData>& fields = form.fields;
803 for (const auto& fieldData : fields) {
804 fieldsData->SetStringWithoutPathExpansion(base::UTF16ToUTF8(fieldData.name),
805 fieldData.value);
806 }
807 formData->Set("fields", fieldsData);
808
809 // Stringify the JSON data and send it to the UIWebView-side fillForm method.
810 std::string dataString;
811 base::JSONWriter::Write(*formData.get(), &dataString);
812
813 // It is possible that the fill was not initiated by selecting a suggestion.
814 // In this case we provide an empty callback.
815 if (!suggestionHandledCompletion_)
816 suggestionHandledCompletion_ = [^{
817 } copy];
818 [jsAutofillManager_
819 fillForm:base::SysUTF8ToNSString(dataString)
820 forceFillFieldName:base::SysUTF16ToNSString(pendingAutocompleteField_)
821 completionHandler:suggestionHandledCompletion_];
822 suggestionHandledCompletion_ = nil;
823 }
824
825 // Returns the first value from the array of possible values that has a match in
826 // the list of accepted values in the AutofillField, or nil if there is no
827 // match. If AutofillField does not specify valid values, the first value is
828 // returned from the list.
829 + (NSString*)selectFrom:(NSArray*)values for:(autofill::AutofillField*)field {
830 if (field->option_values.empty())
831 return values[0];
832
833 for (NSString* value in values) {
834 std::string valueString = base::SysNSStringToUTF8(value);
835 for (size_t i = 0; i < field->option_values.size(); ++i) {
836 if (base::UTF16ToUTF8(field->option_values[i]) == valueString)
837 return value;
838 }
839 for (size_t i = 0; i < field->option_contents.size(); ++i) {
840 if (base::UTF16ToUTF8(field->option_contents[i]) == valueString)
841 return value;
842 }
843 }
844
845 return nil;
846 }
847
848 - (void)renderAutofillTypePredictions:
849 (const std::vector<autofill::FormStructure*>&)structure {
850 base::DictionaryValue predictionData;
851 for (autofill::FormStructure* form : structure) {
852 // |predictionData| will take ownership below.
853 base::DictionaryValue* formJSONData = new base::DictionaryValue;
854 autofill::FormData formData = form->ToFormData();
855 for (const autofill::AutofillField* field : *form) {
856 autofill::AutofillType type(field->Type());
857 if (type.IsUnknown())
858 continue;
859 formJSONData->SetStringWithoutPathExpansion(
860 base::UTF16ToUTF8(field->name), type.ToString());
861 }
862 predictionData.SetWithoutPathExpansion(base::UTF16ToUTF8(formData.name),
863 formJSONData);
864 }
865 std::string dataString;
866 base::JSONWriter::Write(predictionData, &dataString);
867 [jsAutofillManager_ fillPredictionData:base::SysUTF8ToNSString(dataString)];
868 }
869
870 @end
OLDNEW
« no previous file with comments | « ios/chrome/browser/autofill/autofill_agent.h ('k') | ios/chrome/browser/autofill/autofill_controller.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698