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

Unified 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 side-by-side diff with in-line comments
Download patch
« 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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ios/chrome/browser/autofill/autofill_agent.mm
diff --git a/ios/chrome/browser/autofill/autofill_agent.mm b/ios/chrome/browser/autofill/autofill_agent.mm
new file mode 100644
index 0000000000000000000000000000000000000000..1b95bf5a80d07db638437b71745aeee2fb408d10
--- /dev/null
+++ b/ios/chrome/browser/autofill/autofill_agent.mm
@@ -0,0 +1,870 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/autofill/autofill_agent.h"
+
+#include <memory>
+#include <string>
+
+#include "base/format_macros.h"
+#include "base/guid.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_block.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string16.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "components/autofill/core/browser/autofill_manager.h"
+#include "components/autofill/core/browser/autofill_metrics.h"
+#include "components/autofill/core/browser/autofill_profile.h"
+#include "components/autofill/core/browser/credit_card.h"
+#include "components/autofill/core/browser/keyboard_accessory_metrics_logger.h"
+#include "components/autofill/core/browser/popup_item_ids.h"
+#include "components/autofill/core/common/autofill_constants.h"
+#include "components/autofill/core/common/autofill_pref_names.h"
+#include "components/autofill/core/common/autofill_util.h"
+#include "components/autofill/core/common/form_data.h"
+#include "components/autofill/core/common/form_field_data.h"
+#include "components/autofill/ios/browser/autofill_driver_ios.h"
+#import "components/autofill/ios/browser/form_suggestion.h"
+#import "components/autofill/ios/browser/js_autofill_manager.h"
+#include "components/prefs/pref_service.h"
+#include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/pref_names.h"
+#include "ios/web/public/url_scheme_util.h"
+#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
+#include "ios/web/public/web_state/url_verification_constants.h"
+#import "ios/web/public/web_state/web_state.h"
+#include "ui/gfx/geometry/rect.h"
+#include "url/gurl.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+using FormDataVector = std::vector<autofill::FormData>;
+
+// The type of the completion handler block for
+// |fetchFormsWithName:minimumRequiredFieldsCount:pageURL:completionHandler|
+typedef void (^FetchFormsCompletionHandler)(BOOL, const FormDataVector&);
+
+// Gets the first form and field specified by |fieldName| from |forms|,
+// modifying the returned field so that input elements are also handled.
+void GetFormAndField(autofill::FormData* form,
+ autofill::FormFieldData* field,
+ const FormDataVector& forms,
+ const std::string& fieldName,
+ const std::string& type) {
+ DCHECK_GE(forms.size(), 1U);
+ *form = forms[0];
+ const base::string16 fieldName16 = base::UTF8ToUTF16(fieldName);
+ for (const auto& currentField : form->fields) {
+ if (currentField.name == fieldName16) {
+ *field = currentField;
+ break;
+ }
+ }
+ if (field->SameFieldAs(autofill::FormFieldData()))
+ return;
+
+ // Hack to get suggestions from select input elements.
+ if (field->form_control_type == "select-one") {
+ // Any value set will cause the AutofillManager to filter suggestions (only
+ // show suggestions that begin the same as the current value) with the
+ // effect that one only suggestion would be returned; the value itself.
+ field->value = base::string16();
+ }
+}
+
+} // namespace
+
+@interface AutofillAgent ()<CRWWebStateObserver>
+
+// Notifies the autofill manager when forms are detected on a page.
+- (void)notifyAutofillManager:(autofill::AutofillManager*)autofillManager
+ ofFormsSeen:(const FormDataVector&)forms;
+
+// Notifies the autofill manager when forms are submitted.
+- (void)notifyAutofillManager:(autofill::AutofillManager*)autofillManager
+ ofFormsSubmitted:(const FormDataVector&)forms
+ userInitiated:(BOOL)userInitiated;
+
+// Invokes the form extraction script and loads the output into the format
+// expected by the AutofillManager.
+// If |formName| is non-empty, only a form of that name is extracted.
+// Only forms with at least |requiredFieldsCount| fields are extracted.
+// Calls |completionHandler| with a success BOOL of YES and the form data that
+// was extracted.
+// Calls |completionHandler| with NO if the forms could not be extracted.
+// |completionHandler| cannot be nil.
+- (void)fetchFormsWithName:(const base::string16&)formName
+ minimumRequiredFieldsCount:(NSUInteger)requiredFieldsCount
+ pageURL:(const GURL&)pageURL
+ completionHandler:(FetchFormsCompletionHandler)completionHandler;
+
+// Processes the JSON form data extracted from the page into the format expected
+// by AutofillManager and fills it in |formsData|.
+// |formsData| cannot be nil.
+// Returns a BOOL indicating the success value and the vector of form data.
+- (BOOL)getExtractedFormsData:(FormDataVector*)formsData
+ fromJSON:(NSString*)formJSON
+ formName:(const base::string16&)formName
+ pageURL:(const GURL&)pageURL;
+
+// Processes the JSON form data extracted from the page when form activity is
+// detected and informs the AutofillManager.
+- (void)processFormActivityExtractedData:(const FormDataVector&)forms
+ fieldName:(const std::string&)fieldName
+ type:(const std::string&)type
+ webState:(web::WebState*)webState;
+
+// Sends a request to AutofillManager to retrieve suggestions for the specified
+// form and field.
+- (void)queryAutofillWithForms:(const FormDataVector&)forms
+ field:(NSString*)fieldName
+ type:(NSString*)type
+ typedValue:(NSString*)typedValue
+ webState:(web::WebState*)webState
+ completionHandler:(SuggestionsAvailableCompletion)completion;
+
+// Rearranges and filters the suggestions to move profile or credit card
+// suggestions to the front if the user has selected one recently and remove
+// key/value suggestions if the user hasn't started typing.
+- (NSArray*)processSuggestions:(NSArray*)suggestions;
+
+@end
+
+@implementation AutofillAgent {
+ // Timestamp of the first time forms are seen.
+ base::TimeTicks formsSeenTimestamp_;
+
+ // Bridge to observe the web state from Objective-C.
+ std::unique_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
+
+ // The browser state for which this agent was created.
+ ios::ChromeBrowserState* browserState_;
+
+ // Manager for Autofill JavaScripts.
+ JsAutofillManager* jsAutofillManager_;
+
+ // The name of the most recent autocomplete field; tracks the currently-
+ // focused form element in order to force filling of the currently selected
+ // form element, even if it's non-empty.
+ base::string16 pendingAutocompleteField_;
+ // The identifier of the most recent suggestion accepted by the user. Only
+ // used to reorder future suggestion lists, placing matching suggestions first
+ // in the list.
+ NSInteger mostRecentSelectedIdentifier_;
+
+ // Suggestions state:
+ // The most recent form suggestions.
+ NSArray* mostRecentSuggestions_;
+ // The completion to inform FormSuggestionController that a user selection
+ // has been handled.
+ SuggestionHandledCompletion suggestionHandledCompletion_;
+ // The completion to inform FormSuggestionController that suggestions are
+ // available for a given form and field.
+ SuggestionsAvailableCompletion suggestionsAvailableCompletion_;
+ // The text entered by the user into the active field.
+ NSString* typedValue_;
+ // Popup delegate for the most recent suggestions.
+ // The reference is weak because a weak pointer is sent to our
+ // AutofillManagerDelegate.
+ base::WeakPtr<autofill::AutofillPopupDelegate> popupDelegate_;
+}
+
+@synthesize browserState = browserState_;
+
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
+ webState:(web::WebState*)webState {
+ DCHECK(browserState);
+ DCHECK(webState);
+ self = [super init];
+ if (self) {
+ browserState_ = browserState;
+ _webStateObserverBridge.reset(
+ new web::WebStateObserverBridge(webState, self));
+ jsAutofillManager_ = base::mac::ObjCCastStrict<JsAutofillManager>(
+ [webState->GetJSInjectionReceiver()
+ instanceOfClass:[JsAutofillManager class]]);
+ }
+ return self;
+}
+
+- (instancetype)init {
+ NOTREACHED();
+ return nil;
+}
+
+- (void)detachFromWebState {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ _webStateObserverBridge.reset();
+}
+
+#pragma mark -
+#pragma mark Private
+
+// Returns the autofill manager associated with a web::WebState instance.
+// Returns nullptr if there is no autofill manager associated anymore, this can
+// happen when |close| has been called on the |webState|. Also returns nullptr
+// if detachFromWebState has been called.
+- (autofill::AutofillManager*)autofillManagerFromWebState:
+ (web::WebState*)webState {
+ if (!webState || !_webStateObserverBridge)
+ return nullptr;
+ return autofill::AutofillDriverIOS::FromWebState(webState)
+ ->autofill_manager();
+}
+
+// Extracts a single form field from the JSON dictionary into a FormFieldData
+// object.
+// Returns NO if the field could not be extracted.
+- (BOOL)extractFormField:(const base::DictionaryValue&)field
+ asFieldData:(autofill::FormFieldData*)fieldData {
+ if (!field.GetString("name", &fieldData->name) ||
+ !field.GetString("form_control_type", &fieldData->form_control_type)) {
+ return NO;
+ }
+
+ // Optional fields.
+ field.GetString("label", &fieldData->label);
+ field.GetString("value", &fieldData->value);
+ field.GetString("autocomplete_attribute", &fieldData->autocomplete_attribute);
+
+ int maxLength;
+ if (field.GetInteger("max_length", &maxLength))
+ fieldData->max_length = maxLength;
+
+ field.GetBoolean("is_autofilled", &fieldData->is_autofilled);
+
+ // TODO(crbug.com/427614): Extract |is_checked|.
+ bool isCheckable = false;
+ field.GetBoolean("is_checkable", &isCheckable);
+ autofill::SetCheckStatus(fieldData, isCheckable, false);
+
+ field.GetBoolean("is_focusable", &fieldData->is_focusable);
+ field.GetBoolean("should_autocomplete", &fieldData->should_autocomplete);
+
+ // ROLE_ATTRIBUTE_OTHER is the default value. The only other value as of this
+ // writing is ROLE_ATTRIBUTE_PRESENTATION.
+ int role;
+ if (field.GetInteger("role", &role) &&
+ role == autofill::AutofillField::ROLE_ATTRIBUTE_PRESENTATION) {
+ fieldData->role = autofill::AutofillField::ROLE_ATTRIBUTE_PRESENTATION;
+ }
+
+ // TODO(crbug.com/427614): Extract |text_direction|.
+
+ // Load option values where present.
+ const base::ListValue* optionValues;
+ if (field.GetList("option_values", &optionValues)) {
+ for (const auto& optionValue : *optionValues) {
+ base::string16 value;
+ if (optionValue->GetAsString(&value))
+ fieldData->option_values.push_back(value);
+ }
+ }
+
+ // Load option contents where present.
+ const base::ListValue* optionContents;
+ if (field.GetList("option_contents", &optionContents)) {
+ for (const auto& optionContent : *optionContents) {
+ base::string16 content;
+ if (optionContent->GetAsString(&content))
+ fieldData->option_contents.push_back(content);
+ }
+ }
+
+ if (fieldData->option_values.size() != fieldData->option_contents.size())
+ return NO; // Option values and contents lists should match 1-1.
+
+ return YES;
+}
+
+- (void)notifyAutofillManager:(autofill::AutofillManager*)autofillManager
+ ofFormsSeen:(const FormDataVector&)forms {
+ DCHECK(autofillManager);
+ DCHECK(!forms.empty());
+ autofillManager->Reset();
+ autofillManager->OnFormsSeen(forms, formsSeenTimestamp_);
+}
+
+- (void)notifyAutofillManager:(autofill::AutofillManager*)autofillManager
+ ofFormsSubmitted:(const FormDataVector&)forms
+ userInitiated:(BOOL)userInitiated {
+ DCHECK(autofillManager);
+ // Exactly one form should be extracted.
+ DCHECK_EQ(1U, forms.size());
+ autofill::FormData form = forms[0];
+
+ // iOS doesn't get a separate "will submit form" notification so call
+ // OnWillSubmitForm() here.
+ autofillManager->OnWillSubmitForm(form, base::TimeTicks::Now());
+ autofillManager->OnFormSubmitted(form);
+ autofill::KeyboardAccessoryMetricsLogger::OnFormSubmitted();
+}
+
+- (void)fetchFormsWithName:(const base::string16&)formName
+ minimumRequiredFieldsCount:(NSUInteger)requiredFieldsCount
+ pageURL:(const GURL&)pageURL
+ completionHandler:(FetchFormsCompletionHandler)completionHandler {
+ DCHECK(completionHandler);
+ // Necessary so the values can be used inside a block.
+ base::string16 formNameCopy = formName;
+ GURL pageURLCopy = pageURL;
+ __weak AutofillAgent* weakSelf = self;
+ [jsAutofillManager_
+ fetchFormsWithMinimumRequiredFieldsCount:requiredFieldsCount
+ completionHandler:^(NSString* formJSON) {
+ std::vector<autofill::FormData> formData;
+ BOOL success =
+ [weakSelf getExtractedFormsData:&formData
+ fromJSON:formJSON
+ formName:formNameCopy
+ pageURL:pageURLCopy];
+ completionHandler(success, formData);
+ }];
+}
+
+- (BOOL)getExtractedFormsData:(FormDataVector*)formsData
+ fromJSON:(NSString*)formJSON
+ formName:(const base::string16&)formName
+ pageURL:(const GURL&)pageURL {
+ DCHECK(formsData);
+ // Convert JSON string to JSON object |dataJson|.
+ int errorCode = 0;
+ std::string errorMessage;
+ std::unique_ptr<base::Value> dataJson(base::JSONReader::ReadAndReturnError(
+ base::SysNSStringToUTF8(formJSON), base::JSON_PARSE_RFC, &errorCode,
+ &errorMessage));
+ if (errorCode) {
+ LOG(WARNING) << "JSON parse error in form extraction: "
+ << errorMessage.c_str();
+ return NO;
+ }
+
+ // Returned data should be a dictionary.
+ const base::DictionaryValue* data;
+ if (!dataJson->GetAsDictionary(&data))
+ return NO;
+
+ // Get the list of forms.
+ const base::ListValue* formsList;
+ if (!data->GetList("forms", &formsList))
+ return NO;
+
+ // Iterate through all the extracted forms and copy the data from JSON into
+ // AutofillManager structures.
+ for (const auto& formDict : *formsList) {
+ // Each form list entry should be a JSON dictionary.
+ const base::DictionaryValue* formData;
+ if (!formDict->GetAsDictionary(&formData))
+ return NO;
+
+ // Form data is copied into a FormData object field-by-field.
+ autofill::FormData form;
+ if (!formData->GetString("name", &form.name))
+ return NO;
+ if (!formName.empty() && formName != form.name)
+ continue;
+
+ // Origin is mandatory.
+ base::string16 origin;
+ if (!formData->GetString("origin", &origin))
+ return NO;
+
+ // Use GURL object to verify origin of host page URL.
+ form.origin = GURL(origin);
+ if (form.origin.GetOrigin() != pageURL.GetOrigin()) {
+ LOG(WARNING) << "Form extraction aborted due to same origin policy";
+ return NO;
+ }
+
+ // Action is optional.
+ base::string16 action;
+ formData->GetString("action", &action);
+ form.action = GURL(action);
+
+ // Is form tag is optional.
+ bool is_form_tag;
+ if (formData->GetBoolean("is_form_tag", &is_form_tag))
+ form.is_form_tag = is_form_tag;
+
+ // Field list (mandatory) is extracted.
+ const base::ListValue* fieldsList;
+ if (!formData->GetList("fields", &fieldsList))
+ return NO;
+ for (const auto& fieldDict : *fieldsList) {
+ const base::DictionaryValue* field;
+ autofill::FormFieldData fieldData;
+ if (fieldDict->GetAsDictionary(&field) &&
+ [self extractFormField:*field asFieldData:&fieldData]) {
+ form.fields.push_back(fieldData);
+ } else {
+ return NO;
+ }
+ }
+ formsData->push_back(form);
+ }
+ return YES;
+}
+
+- (NSArray*)processSuggestions:(NSArray*)suggestions {
+ // The suggestion array is cloned (to claim ownership) and to slightly
+ // reorder; a future improvement is to base order on text typed in other
+ // fields by users as well as accepted suggestions (crbug.com/245261).
+ NSMutableArray* suggestionsCopy = [suggestions mutableCopy];
+
+ // If the most recently selected suggestion was a profile or credit card
+ // suggestion, move it to the front of the suggestions.
+ if (mostRecentSelectedIdentifier_ > 0) {
+ NSUInteger idx = [suggestionsCopy
+ indexOfObjectPassingTest:^BOOL(id obj, NSUInteger, BOOL*) {
+ FormSuggestion* suggestion = obj;
+ return suggestion.identifier == mostRecentSelectedIdentifier_;
+ }];
+
+ if (idx != NSNotFound) {
+ FormSuggestion* suggestion = suggestionsCopy[idx];
+ [suggestionsCopy removeObjectAtIndex:idx];
+ [suggestionsCopy insertObject:suggestion atIndex:0];
+ }
+ }
+
+ // Filter out any key/value suggestions if the user hasn't typed yet.
+ if ([typedValue_ length] == 0) {
+ for (NSInteger idx = [suggestionsCopy count] - 1; idx >= 0; idx--) {
+ FormSuggestion* suggestion = suggestionsCopy[idx];
+ if (suggestion.identifier == autofill::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY) {
+ [suggestionsCopy removeObjectAtIndex:idx];
+ }
+ }
+ }
+
+ // If "clear form" entry exists then move it to the front of the suggestions.
+ for (NSInteger idx = [suggestionsCopy count] - 1; idx > 0; idx--) {
+ FormSuggestion* suggestion = suggestionsCopy[idx];
+ if (suggestion.identifier == autofill::POPUP_ITEM_ID_CLEAR_FORM) {
+ FormSuggestion* suggestionToMove = suggestionsCopy[idx];
+ [suggestionsCopy removeObjectAtIndex:idx];
+ [suggestionsCopy insertObject:suggestionToMove atIndex:0];
+ break;
+ }
+ }
+
+ return suggestionsCopy;
+}
+
+- (void)onSuggestionsReady:(NSArray*)suggestions
+ popupDelegate:
+ (const base::WeakPtr<autofill::AutofillPopupDelegate>&)
+ delegate {
+ popupDelegate_ = delegate;
+ mostRecentSuggestions_ = [[self processSuggestions:suggestions] copy];
+ if (suggestionsAvailableCompletion_)
+ suggestionsAvailableCompletion_([mostRecentSuggestions_ count] > 0);
+ suggestionsAvailableCompletion_ = nil;
+}
+
+#pragma mark -
+#pragma mark FormSuggestionProvider
+
+- (void)queryAutofillWithForms:(const FormDataVector&)forms
+ field:(NSString*)fieldName
+ type:(NSString*)type
+ typedValue:(NSString*)typedValue
+ webState:(web::WebState*)webState
+ completionHandler:(SuggestionsAvailableCompletion)completion {
+ autofill::AutofillManager* autofillManager =
+ [self autofillManagerFromWebState:webState];
+ if (!autofillManager)
+ return;
+
+ // Passed to delegates; we don't use it so it's set to zero.
+ int queryId = 0;
+
+ // Find the right form and field.
+ autofill::FormFieldData field;
+ autofill::FormData form;
+ GetFormAndField(&form, &field, forms, base::SysNSStringToUTF8(fieldName),
+ base::SysNSStringToUTF8(type));
+
+ // Save the completion and go look for suggestions.
+ suggestionsAvailableCompletion_ = [completion copy];
+ typedValue_ = [typedValue copy];
+
+ // Query the AutofillManager for suggestions. Results will arrive in
+ // [AutofillController showAutofillPopup].
+ autofillManager->OnQueryFormFieldAutofill(queryId, form, field, gfx::RectF());
+}
+
+- (void)checkIfSuggestionsAvailableForForm:(NSString*)formName
+ field:(NSString*)fieldName
+ type:(NSString*)type
+ typedValue:(NSString*)typedValue
+ webState:(web::WebState*)webState
+ completionHandler:
+ (SuggestionsAvailableCompletion)completion {
+ web::URLVerificationTrustLevel trustLevel;
+ const GURL pageURL(webState->GetCurrentURL(&trustLevel));
+ if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
+ DLOG(WARNING) << "Suggestions not offered on untrusted page";
+ completion(NO);
+ return;
+ }
+
+ // Once the active form and field are extracted, send a query to the
+ // AutofillManager for suggestions.
+ __weak AutofillAgent* weakSelf = self;
+ id completionHandler = ^(BOOL success, const FormDataVector& forms) {
+ if (success && forms.size() == 1) {
+ [weakSelf queryAutofillWithForms:forms
+ field:fieldName
+ type:type
+ typedValue:typedValue
+ webState:webState
+ completionHandler:completion];
+ }
+ };
+
+ // Re-extract the active form and field only. All forms with at least one
+ // input element are considered because key/value suggestions are offered
+ // even on short forms.
+ [self fetchFormsWithName:base::SysNSStringToUTF16(formName)
+ minimumRequiredFieldsCount:1
+ pageURL:pageURL
+ completionHandler:completionHandler];
+}
+
+- (void)retrieveSuggestionsForForm:(NSString*)formName
+ field:(NSString*)fieldName
+ type:(NSString*)type
+ typedValue:(NSString*)typedValue
+ webState:(web::WebState*)webState
+ completionHandler:(SuggestionsReadyCompletion)completion {
+ DCHECK(mostRecentSuggestions_)
+ << "Requestor should have called "
+ << "|checkIfSuggestionsAvailableForForm:field:type:completionHandler:| "
+ << "and waited for the result before calling "
+ << "|retrieveSuggestionsForForm:field:type:completionHandler:|.";
+ completion(mostRecentSuggestions_, self);
+}
+
+- (void)didSelectSuggestion:(FormSuggestion*)suggestion
+ forField:(NSString*)fieldName
+ form:(NSString*)formName
+ completionHandler:(SuggestionHandledCompletion)completion {
+ [[UIDevice currentDevice] playInputClick];
+ suggestionHandledCompletion_ = [completion copy];
+ mostRecentSelectedIdentifier_ = suggestion.identifier;
+
+ if (suggestion.identifier > 0) {
+ pendingAutocompleteField_ = base::SysNSStringToUTF16(fieldName);
+ if (popupDelegate_) {
+ popupDelegate_->DidAcceptSuggestion(
+ base::SysNSStringToUTF16(suggestion.value), suggestion.identifier, 0);
+ }
+ } else if (suggestion.identifier ==
+ autofill::POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY) {
+ // FormSuggestion is a simple, single value that can be filled out now.
+ [self fillField:base::SysNSStringToUTF8(fieldName)
+ formName:base::SysNSStringToUTF8(formName)
+ value:base::SysNSStringToUTF16(suggestion.value)];
+ } else if (suggestion.identifier == autofill::POPUP_ITEM_ID_CLEAR_FORM) {
+ [jsAutofillManager_
+ clearAutofilledFieldsForFormNamed:formName
+ completionHandler:suggestionHandledCompletion_];
+ suggestionHandledCompletion_ = nil;
+ } else {
+ NOTREACHED() << "unknown identifier " << suggestion.identifier;
+ }
+}
+
+#pragma mark -
+#pragma mark CRWWebStateObserver
+
+- (void)webStateDestroyed:(web::WebState*)webState {
+ [self detachFromWebState];
+}
+
+- (void)webState:(web::WebState*)webState
+ didSubmitDocumentWithFormNamed:(const std::string&)formName
+ userInitiated:(BOOL)userInitiated {
+ if (!browserState_->GetPrefs()->GetBoolean(autofill::prefs::kAutofillEnabled))
+ return;
+
+ web::URLVerificationTrustLevel trustLevel;
+ const GURL pageURL(webState->GetCurrentURL(&trustLevel));
+ if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
+ DLOG(WARNING) << "Form submit not handled on untrusted page";
+ return;
+ }
+
+ __weak AutofillAgent* weakSelf = self;
+ id completionHandler = ^(BOOL success, const FormDataVector& forms) {
+ AutofillAgent* strongSelf = weakSelf;
+ if (!strongSelf || !success)
+ return;
+ autofill::AutofillManager* autofillManager =
+ [strongSelf autofillManagerFromWebState:webState];
+ if (!autofillManager || forms.empty())
+ return;
+ if (forms.size() > 1) {
+ DLOG(WARNING) << "Only one form should be extracted.";
+ return;
+ }
+ [strongSelf notifyAutofillManager:autofillManager
+ ofFormsSubmitted:forms
+ userInitiated:userInitiated];
+
+ };
+ // This code is racing against the new page loading and will not get the
+ // password form data if the page has changed. In most cases this code wins
+ // the race.
+ // TODO(crbug.com/418827): Fix this by passing in more data from the JS side.
+ [self fetchFormsWithName:base::UTF8ToUTF16(formName)
+ minimumRequiredFieldsCount:1
+ pageURL:pageURL
+ completionHandler:completionHandler];
+}
+
+- (void)webStateDidLoadPage:(web::WebState*)webState {
+ if (!browserState_->GetPrefs()->GetBoolean(
+ autofill::prefs::kAutofillEnabled) ||
+ !webState->ContentIsHTML()) {
+ return;
+ }
+ [self processPage:webState];
+}
+
+- (void)processPage:(web::WebState*)webState {
+ web::URLVerificationTrustLevel trustLevel;
+ const GURL pageURL(webState->GetCurrentURL(&trustLevel));
+ if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
+ DLOG(WARNING) << "Page load not handled on untrusted page";
+ return;
+ }
+
+ if (!web::UrlHasWebScheme(pageURL))
+ return;
+
+ // This process is only done once.
+ if ([jsAutofillManager_ hasBeenInjected])
+ return;
+
+ popupDelegate_.reset();
+ suggestionsAvailableCompletion_ = nil;
+ suggestionHandledCompletion_ = nil;
+ mostRecentSuggestions_ = nil;
+ typedValue_ = nil;
+
+ [jsAutofillManager_ inject];
+
+ __weak AutofillAgent* weakSelf = self;
+ id completionHandler = ^(BOOL success, const FormDataVector& forms) {
+ AutofillAgent* strongSelf = weakSelf;
+ if (!strongSelf || !success)
+ return;
+ autofill::AutofillManager* autofillManager =
+ [strongSelf autofillManagerFromWebState:webState];
+ if (!autofillManager || forms.empty())
+ return;
+ [strongSelf notifyAutofillManager:autofillManager ofFormsSeen:forms];
+ };
+ // The document has now been fully loaded. Scan for forms to be extracted.
+ // Because of the cost of communicating with the server, only forms that have
+ // enough forms to make them likely candidates for profile completion are
+ // extracted.
+ [self fetchFormsWithName:base::string16()
+ minimumRequiredFieldsCount:autofill::kRequiredFieldsForPredictionRoutines
+ pageURL:pageURL
+ completionHandler:completionHandler];
+}
+
+- (void)webState:(web::WebState*)webState
+ didRegisterFormActivityWithFormNamed:(const std::string&)formName
+ fieldName:(const std::string&)fieldName
+ type:(const std::string&)type
+ value:(const std::string&)value
+ keyCode:(int)keyCode
+ inputMissing:(BOOL)inputMissing {
+ if (!browserState_->GetPrefs()->GetBoolean(autofill::prefs::kAutofillEnabled))
+ return;
+ web::URLVerificationTrustLevel trustLevel;
+ const GURL pageURL(webState->GetCurrentURL(&trustLevel));
+ if (trustLevel != web::URLVerificationTrustLevel::kAbsolute) {
+ DLOG(WARNING) << "Form activity not handled on untrusted page";
+ return;
+ }
+
+ // Autofill and suggestion scripts are only injected for web URLs.
+ if (!web::UrlHasWebScheme(pageURL) || !webState->ContentIsHTML())
+ return;
+
+ // Returns early and reset the suggestion state if an error occurs.
+ if (inputMissing)
+ return;
+
+ // Processing the page can be needed here if Autofill is enabled in settings
+ // when the page is already loaded, or if the user focuses a field before the
+ // page is fully loaded.
+ [self processPage:webState];
+
+ // Blur not handled; we don't reset the suggestion state because if the
+ // keyboard is about to be dismissed there's no point. If not it means the
+ // next focus event will update the suggestion state within milliseconds, so
+ // if we do it now a flicker will be seen.
+ if (type.compare("blur") == 0)
+ return;
+
+ // Necessary so the strings can be used inside a block.
+ std::string fieldNameCopy = fieldName;
+ std::string typeCopy = type;
+
+ __weak AutofillAgent* weakSelf = self;
+ id completionHandler = ^(BOOL success, const FormDataVector& forms) {
+ if (success && forms.size() == 1) {
+ [weakSelf processFormActivityExtractedData:forms
+ fieldName:fieldNameCopy
+ type:typeCopy
+ webState:webState];
+ }
+ };
+
+ // Re-extract the active form and field only. There is no minimum field
+ // requirement because key/value suggestions are offered event on short forms.
+ [self fetchFormsWithName:base::UTF8ToUTF16(formName)
+ minimumRequiredFieldsCount:1
+ pageURL:pageURL
+ completionHandler:completionHandler];
+}
+
+- (void)processFormActivityExtractedData:(const FormDataVector&)forms
+ fieldName:(const std::string&)fieldName
+ type:(const std::string&)type
+ webState:(web::WebState*)webState {
+ autofill::AutofillManager* autofillManager =
+ [self autofillManagerFromWebState:webState];
+ if (!autofillManager)
+ return;
+
+ autofill::FormFieldData field;
+ autofill::FormData form;
+ GetFormAndField(&form, &field, forms, fieldName, type);
+
+ // Tell the manager about the form activity (for metrics).
+ if (type.compare("input") == 0 && (field.form_control_type == "text" ||
+ field.form_control_type == "password")) {
+ autofillManager->OnTextFieldDidChange(form, field, base::TimeTicks::Now());
+ }
+}
+
+#pragma mark -
+#pragma mark AutofillViewClient
+
+// Complete a field named |fieldName| on the form named |formName| using |value|
+// then move the cursor.
+// TODO(crbug.com/661621): |dataString| ends up at fillFormField() in
+// autofill_controller.js. fillFormField() expects an AutofillFormFieldData
+// object, which |dataString| is not because 'form' is not a specified member of
+// AutofillFormFieldData. fillFormField() also expects members 'max_length' and
+// 'is_checked' to exist.
+- (void)fillField:(const std::string&)fieldName
+ formName:(const std::string&)formName
+ value:(const base::string16)value {
+ base::DictionaryValue data;
+ data.SetString("name", fieldName);
+ data.SetString("form", formName);
+ data.SetString("value", value);
+ std::string dataString;
+ base::JSONWriter::Write(data, &dataString);
+
+ DCHECK(suggestionHandledCompletion_);
+ [jsAutofillManager_ fillActiveFormField:base::SysUTF8ToNSString(dataString)
+ completionHandler:suggestionHandledCompletion_];
+ suggestionHandledCompletion_ = nil;
+}
+
+- (void)onFormDataFilled:(const autofill::FormData&)form {
+ std::unique_ptr<base::DictionaryValue> formData(new base::DictionaryValue);
+ formData->SetString("formName", base::UTF16ToUTF8(form.name));
+ // Note: Destruction of all child base::Value types is handled by the root
+ // formData object on its own destruction.
+ base::DictionaryValue* fieldsData = new base::DictionaryValue;
+
+ const std::vector<autofill::FormFieldData>& fields = form.fields;
+ for (const auto& fieldData : fields) {
+ fieldsData->SetStringWithoutPathExpansion(base::UTF16ToUTF8(fieldData.name),
+ fieldData.value);
+ }
+ formData->Set("fields", fieldsData);
+
+ // Stringify the JSON data and send it to the UIWebView-side fillForm method.
+ std::string dataString;
+ base::JSONWriter::Write(*formData.get(), &dataString);
+
+ // It is possible that the fill was not initiated by selecting a suggestion.
+ // In this case we provide an empty callback.
+ if (!suggestionHandledCompletion_)
+ suggestionHandledCompletion_ = [^{
+ } copy];
+ [jsAutofillManager_
+ fillForm:base::SysUTF8ToNSString(dataString)
+ forceFillFieldName:base::SysUTF16ToNSString(pendingAutocompleteField_)
+ completionHandler:suggestionHandledCompletion_];
+ suggestionHandledCompletion_ = nil;
+}
+
+// Returns the first value from the array of possible values that has a match in
+// the list of accepted values in the AutofillField, or nil if there is no
+// match. If AutofillField does not specify valid values, the first value is
+// returned from the list.
++ (NSString*)selectFrom:(NSArray*)values for:(autofill::AutofillField*)field {
+ if (field->option_values.empty())
+ return values[0];
+
+ for (NSString* value in values) {
+ std::string valueString = base::SysNSStringToUTF8(value);
+ for (size_t i = 0; i < field->option_values.size(); ++i) {
+ if (base::UTF16ToUTF8(field->option_values[i]) == valueString)
+ return value;
+ }
+ for (size_t i = 0; i < field->option_contents.size(); ++i) {
+ if (base::UTF16ToUTF8(field->option_contents[i]) == valueString)
+ return value;
+ }
+ }
+
+ return nil;
+}
+
+- (void)renderAutofillTypePredictions:
+ (const std::vector<autofill::FormStructure*>&)structure {
+ base::DictionaryValue predictionData;
+ for (autofill::FormStructure* form : structure) {
+ // |predictionData| will take ownership below.
+ base::DictionaryValue* formJSONData = new base::DictionaryValue;
+ autofill::FormData formData = form->ToFormData();
+ for (const autofill::AutofillField* field : *form) {
+ autofill::AutofillType type(field->Type());
+ if (type.IsUnknown())
+ continue;
+ formJSONData->SetStringWithoutPathExpansion(
+ base::UTF16ToUTF8(field->name), type.ToString());
+ }
+ predictionData.SetWithoutPathExpansion(base::UTF16ToUTF8(formData.name),
+ formJSONData);
+ }
+ std::string dataString;
+ base::JSONWriter::Write(predictionData, &dataString);
+ [jsAutofillManager_ fillPredictionData:base::SysUTF8ToNSString(dataString)];
+}
+
+@end
« 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