Commit 35738513 authored by Scott Wu's avatar Scott Wu Committed by Commit Bot

Create password controller helper for common logic.

This CL creates the skeleton of common password controller and moves FormActivityObserver related logic into it`.
The goal is to move common password autofill logics into components folder so that they can be shared by both Chrome iOS and //ios/web_view.

> Moved webState and jsPasswordManager into helper class.
> Moved FormActivityObserver logic into helper class.

Bug: 865114
Cq-Include-Trybots: luci.chromium.try:ios-simulator-full-configs;master.tryserver.chromium.mac:ios-simulator-cronet
Change-Id: I2dbfdb1cb4f0bdf22bf1d5c37451f46682addcac
Reviewed-on: https://chromium-review.googlesource.com/1156528Reviewed-by: default avatarMoe Ahmadi <mahmadi@chromium.org>
Reviewed-by: default avatarHiroshi Ichikawa <ichikawa@chromium.org>
Reviewed-by: default avatarJohn Wu <jzw@chromium.org>
Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Commit-Queue: Scott Wu <scottwu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#585356}
parent 94524f81
...@@ -8,6 +8,10 @@ component("ios") { ...@@ -8,6 +8,10 @@ component("ios") {
deps = [ deps = [
"//base", "//base",
"//components/autofill/core/common", "//components/autofill/core/common",
"//components/autofill/ios/browser",
"//components/autofill/ios/form_util",
"//components/password_manager/core/browser",
"//components/password_manager/core/browser/form_parsing",
"//ios/web/public", "//ios/web/public",
"//url", "//url",
] ]
...@@ -17,6 +21,8 @@ component("ios") { ...@@ -17,6 +21,8 @@ component("ios") {
"account_select_fill_data.h", "account_select_fill_data.h",
"js_password_manager.h", "js_password_manager.h",
"js_password_manager.mm", "js_password_manager.mm",
"password_controller_helper.h",
"password_controller_helper.mm",
] ]
} }
...@@ -39,6 +45,7 @@ source_set("unit_tests") { ...@@ -39,6 +45,7 @@ source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"account_select_fill_data_unittest.cc", "account_select_fill_data_unittest.cc",
"password_controller_helper_unittest.mm",
] ]
deps = [ deps = [
":ios", ":ios",
......
include_rules = [ include_rules = [
"+components/autofill/core/common", "+components/autofill/core/common",
"+components/autofill/ios/browser",
"+components/autofill/ios/form_util",
"+ios/web/public", "+ios/web/public",
] ]
// Copyright 2018 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.
#ifndef COMPONENTS_PASSWORD_MANAGER_IOS_PASSWORD_CONTROLLER_HELPER_H_
#define COMPONENTS_PASSWORD_MANAGER_IOS_PASSWORD_CONTROLLER_HELPER_H_
#import <Foundation/Foundation.h>
#import "components/autofill/ios/form_util/form_activity_observer_bridge.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h"
#include "url/gurl.h"
NS_ASSUME_NONNULL_BEGIN
@class JsPasswordManager;
@class PasswordControllerHelper;
namespace autofill {
struct PasswordForm;
} // namespace autofill
namespace password_manager {
// Returns true if the trust level for the current page URL of |web_state| is
// kAbsolute. If |page_url| is not null, fills it with the current page URL.
bool GetPageURLAndCheckTrustLevel(web::WebState* web_state,
GURL* __nullable page_url);
} // namespace password_manager
namespace web {
class WebState;
} // namespace web
// A protocol implemented by a delegate of PasswordControllerHelper.
@protocol PasswordControllerHelperDelegate
// Called when the password form is submitted.
- (void)helper:(PasswordControllerHelper*)helper
didSubmitForm:(const autofill::PasswordForm&)form
inMainFrame:(BOOL)inMainFrame;
@end
// Handles common logic of password controller for both ios/chrome and
// ios/web_view.
// TODO(crbug.com/865114): Rename to PasswordFormHelper.
@interface PasswordControllerHelper
: NSObject<FormActivityObserver, CRWWebStateObserver>
// The JsPasswordManager processing password form via javascript.
@property(nonatomic, readonly) JsPasswordManager* jsPasswordManager;
// Creates a instance with the given WebState, observer and delegate.
- (instancetype)initWithWebState:(web::WebState*)webState
delegate:(nullable id<PasswordControllerHelperDelegate>)
delegate NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
#endif // COMPONENTS_PASSWORD_MANAGER_IOS_PASSWORD_CONTROLLER_HELPER_H_
// Copyright 2018 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 "components/password_manager/ios/password_controller_helper.h"
#include <stddef.h>
#include "base/strings/sys_string_conversions.h"
#include "base/values.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/password_form.h"
#include "components/autofill/ios/browser/autofill_util.h"
#include "components/password_manager/core/browser/form_parsing/ios_form_parser.h"
#include "components/password_manager/ios/js_password_manager.h"
#import "ios/web/public/web_state/web_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using autofill::FormData;
using autofill::PasswordForm;
using password_manager::GetPageURLAndCheckTrustLevel;
namespace password_manager {
bool GetPageURLAndCheckTrustLevel(web::WebState* web_state,
GURL* __nullable page_url) {
auto trustLevel = web::URLVerificationTrustLevel::kNone;
GURL dummy;
if (!page_url) {
page_url = &dummy;
}
*page_url = web_state->GetCurrentURL(&trustLevel);
return trustLevel == web::URLVerificationTrustLevel::kAbsolute;
}
} // namespace password_manager
namespace {
// Script command prefix for form changes. Possible command to be sent from
// injected JS is 'passwordForm.submitButtonClick'.
constexpr char kCommandPrefix[] = "passwordForm";
} // namespace
@interface PasswordControllerHelper ()
@property(nonatomic, weak) id<PasswordControllerHelperDelegate> delegate;
// Handler for injected JavaScript callbacks.
- (BOOL)handleScriptCommand:(const base::DictionaryValue&)JSONCommand;
// Finds the currently submitted password form named |formName| and calls
// |completionHandler| with the populated data structure. |found| is YES if the
// current form was found successfully, NO otherwise. |completionHandler|
// cannot be nil.
- (void)extractSubmittedPasswordForm:(const std::string&)formName
completionHandler:
(void (^)(BOOL found,
const PasswordForm& form))completionHandler;
@end
@implementation PasswordControllerHelper {
// The WebState this instance is observing. Will be null after
// -webStateDestroyed: has been called.
web::WebState* _webState;
// Bridge to observe WebState from Objective-C.
std::unique_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
// Bridge to observe form activity in |_webState|.
std::unique_ptr<autofill::FormActivityObserverBridge>
_formActivityObserverBridge;
}
#pragma mark - Properties
@synthesize delegate = _delegate;
@synthesize jsPasswordManager = _jsPasswordManager;
#pragma mark - Initialization
- (instancetype)initWithWebState:(web::WebState*)webState
delegate:
(id<PasswordControllerHelperDelegate>)delegate {
self = [super init];
if (self) {
DCHECK(webState);
_webState = webState;
_delegate = delegate;
_webStateObserverBridge =
std::make_unique<web::WebStateObserverBridge>(self);
_webState->AddObserver(_webStateObserverBridge.get());
_formActivityObserverBridge =
std::make_unique<autofill::FormActivityObserverBridge>(_webState, self);
_jsPasswordManager = [[JsPasswordManager alloc]
initWithReceiver:_webState->GetJSInjectionReceiver()];
__weak PasswordControllerHelper* weakSelf = self;
auto callback = base::BindRepeating(
^bool(const base::DictionaryValue& JSON, const GURL& originURL,
bool interacting, bool isMainFrame) {
if (!isMainFrame) {
// Passwords is only supported on main frame.
return false;
}
// |originURL| and |interacting| aren't used.
return [weakSelf handleScriptCommand:JSON];
});
_webState->AddScriptCommandCallback(callback, kCommandPrefix);
}
return self;
}
#pragma mark - Dealloc
- (void)dealloc {
if (_webState) {
_webState->RemoveScriptCommandCallback(kCommandPrefix);
_webState->RemoveObserver(_webStateObserverBridge.get());
}
}
#pragma mark - CRWWebStateObserver
- (void)webStateDestroyed:(web::WebState*)webState {
DCHECK_EQ(_webState, webState);
if (_webState) {
_webState->RemoveScriptCommandCallback(kCommandPrefix);
_webState->RemoveObserver(_webStateObserverBridge.get());
_webState = nullptr;
}
_webStateObserverBridge.reset();
_formActivityObserverBridge.reset();
}
#pragma mark - FormActivityObserver
- (void)webState:(web::WebState*)webState
submittedDocumentWithFormNamed:(const std::string&)formName
hasUserGesture:(BOOL)hasUserGesture
formInMainFrame:(BOOL)formInMainFrame {
DCHECK_EQ(_webState, webState);
__weak PasswordControllerHelper* weakSelf = self;
// 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.
id completionHandler = ^(BOOL found, const autofill::PasswordForm& form) {
PasswordControllerHelper* strongSelf = weakSelf;
id<PasswordControllerHelperDelegate> strongDelegate = strongSelf.delegate;
if (!strongSelf || !strongSelf->_webState || !strongDelegate) {
return;
}
[strongDelegate helper:strongSelf
didSubmitForm:form
inMainFrame:formInMainFrame];
};
[self extractSubmittedPasswordForm:formName
completionHandler:completionHandler];
}
#pragma mark - Private methods
- (BOOL)handleScriptCommand:(const base::DictionaryValue&)JSONCommand {
std::string command;
if (!JSONCommand.GetString("command", &command)) {
return NO;
}
if (command != "passwordForm.submitButtonClick") {
return NO;
}
GURL pageURL;
if (!GetPageURLAndCheckTrustLevel(_webState, &pageURL)) {
return NO;
}
FormData formData;
if (!autofill::ExtractFormData(JSONCommand, false, base::string16(), pageURL,
&formData)) {
return NO;
}
std::unique_ptr<PasswordForm> form =
ParseFormData(formData, password_manager::FormParsingMode::SAVING);
if (!form) {
return NO;
}
if (_webState && self.delegate) {
[self.delegate helper:self didSubmitForm:*form inMainFrame:YES];
return YES;
}
return NO;
}
- (void)extractSubmittedPasswordForm:(const std::string&)formName
completionHandler:
(void (^)(BOOL found,
const PasswordForm& form))completionHandler {
DCHECK(completionHandler);
if (!_webState) {
return;
}
GURL pageURL;
if (!GetPageURLAndCheckTrustLevel(_webState, &pageURL)) {
completionHandler(NO, PasswordForm());
return;
}
id extractSubmittedFormCompletionHandler = ^(NSString* jsonString) {
std::unique_ptr<base::Value> formValue = autofill::ParseJson(jsonString);
if (!formValue) {
completionHandler(NO, PasswordForm());
return;
}
FormData formData;
if (!autofill::ExtractFormData(*formValue, false, base::string16(), pageURL,
&formData)) {
completionHandler(NO, PasswordForm());
return;
}
std::unique_ptr<PasswordForm> form =
ParseFormData(formData, password_manager::FormParsingMode::SAVING);
if (!form) {
completionHandler(NO, PasswordForm());
return;
}
completionHandler(YES, *form);
};
[self.jsPasswordManager extractForm:base::SysUTF8ToNSString(formName)
completionHandler:extractSubmittedFormCompletionHandler];
}
@end
// Copyright 2018 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 "components/password_manager/ios/password_controller_helper.h"
#include <stddef.h>
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
// TODO(crbug.com/865114): Add unit test in follow up CL.
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <memory> #include <memory>
#import "components/autofill/ios/browser/form_suggestion_provider.h" #import "components/autofill/ios/browser/form_suggestion_provider.h"
#import "components/password_manager/ios/password_controller_helper.h"
#import "ios/chrome/browser/passwords/ios_chrome_password_manager_client.h" #import "ios/chrome/browser/passwords/ios_chrome_password_manager_client.h"
#import "ios/chrome/browser/passwords/ios_chrome_password_manager_driver.h" #import "ios/chrome/browser/passwords/ios_chrome_password_manager_driver.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h" #import "ios/web/public/web_state/web_state_observer_bridge.h"
...@@ -21,7 +22,6 @@ ...@@ -21,7 +22,6 @@
namespace password_manager { namespace password_manager {
class PasswordManagerClient; class PasswordManagerClient;
class PasswordManagerDriver;
} // namespace password_manager } // namespace password_manager
// Delegate for registering view controller and displaying its view. Used to // Delegate for registering view controller and displaying its view. Used to
...@@ -41,7 +41,8 @@ class PasswordManagerDriver; ...@@ -41,7 +41,8 @@ class PasswordManagerDriver;
// Per-tab password controller. Handles password autofill and saving. // Per-tab password controller. Handles password autofill and saving.
@interface PasswordController : NSObject<CRWWebStateObserver, @interface PasswordController : NSObject<CRWWebStateObserver,
PasswordManagerClientDelegate, PasswordManagerClientDelegate,
PasswordManagerDriverDelegate> PasswordManagerDriverDelegate,
PasswordControllerHelperDelegate>
// An object that can provide suggestions from this PasswordController. // An object that can provide suggestions from this PasswordController.
@property(nonatomic, readonly) id<FormSuggestionProvider> suggestionProvider; @property(nonatomic, readonly) id<FormSuggestionProvider> suggestionProvider;
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "components/password_manager/core/browser/stub_password_manager_client.h" #include "components/password_manager/core/browser/stub_password_manager_client.h"
#include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/password_manager/core/common/password_manager_pref_names.h"
#import "components/password_manager/ios/js_password_manager.h" #import "components/password_manager/ios/js_password_manager.h"
#import "components/password_manager/ios/password_controller_helper.h"
#include "components/password_manager/ios/test_helpers.h" #include "components/password_manager/ios/test_helpers.h"
#include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h" #include "components/prefs/testing_pref_service.h"
...@@ -149,11 +150,6 @@ ACTION(InvokeEmptyConsumerWithForms) { ...@@ -149,11 +150,6 @@ ACTION(InvokeEmptyConsumerWithForms) {
- (void)findPasswordFormsWithCompletionHandler: - (void)findPasswordFormsWithCompletionHandler:
(void (^)(const std::vector<PasswordForm>&))completionHandler; (void (^)(const std::vector<PasswordForm>&))completionHandler;
- (void)extractSubmittedPasswordForm:(const std::string&)formName
completionHandler:
(void (^)(BOOL found,
const PasswordForm& form))completionHandler;
- (void)fillPasswordForm:(const PasswordFormFillData&)formData - (void)fillPasswordForm:(const PasswordFormFillData&)formData
completionHandler:(void (^)(BOOL))completionHandler; completionHandler:(void (^)(BOOL))completionHandler;
...@@ -163,11 +159,21 @@ ACTION(InvokeEmptyConsumerWithForms) { ...@@ -163,11 +159,21 @@ ACTION(InvokeEmptyConsumerWithForms) {
fromDictionary:(const base::DictionaryValue*)dictionary fromDictionary:(const base::DictionaryValue*)dictionary
pageURL:(const GURL&)pageLocation; pageURL:(const GURL&)pageLocation;
// Provides access to JavaScript Manager for testing with mocks. // Provides access to common helper logic for testing with mocks.
@property(readonly) JsPasswordManager* passwordJsManager; @property(readonly) PasswordControllerHelper* helper;
@end @end
@interface PasswordControllerHelper (Testing)
- (void)extractSubmittedPasswordForm:(const std::string&)formName
completionHandler:
(void (^)(BOOL found,
const PasswordForm& form))completionHandler;
// Provides access to JavaScript Manager for testing with mocks.
@property(readonly) JsPasswordManager* jsPasswordManager;
@end
// Real FormSuggestionController is wrapped to register the addition of // Real FormSuggestionController is wrapped to register the addition of
// suggestions. // suggestions.
@interface PasswordsTestSuggestionController : FormSuggestionController @interface PasswordsTestSuggestionController : FormSuggestionController
...@@ -247,7 +253,7 @@ class PasswordControllerTest : public ChromeWebTest { ...@@ -247,7 +253,7 @@ class PasswordControllerTest : public ChromeWebTest {
// |failure_count| reaches |target_failure_count|, stop the partial mock // |failure_count| reaches |target_failure_count|, stop the partial mock
// and let the original JavaScript manager execute. // and let the original JavaScript manager execute.
void SetFillPasswordFormFailureCount(int target_failure_count) { void SetFillPasswordFormFailureCount(int target_failure_count) {
id original_manager = [passwordController_ passwordJsManager]; id original_manager = passwordController_.helper.jsPasswordManager;
OCPartialMockObject* failing_manager = OCPartialMockObject* failing_manager =
[OCMockObject partialMockForObject:original_manager]; [OCMockObject partialMockForObject:original_manager];
__block int failure_count = 0; __block int failure_count = 0;
...@@ -482,7 +488,7 @@ TEST_F(PasswordControllerTest, FLAKY_GetSubmittedPasswordForm) { ...@@ -482,7 +488,7 @@ TEST_F(PasswordControllerTest, FLAKY_GetSubmittedPasswordForm) {
form.username_element); form.username_element);
} }
}; };
[passwordController_ [passwordController_.helper
extractSubmittedPasswordForm:FormName(data.number_of_forms_to_submit) extractSubmittedPasswordForm:FormName(data.number_of_forms_to_submit)
completionHandler:completion_handler]; completionHandler:completion_handler];
EXPECT_TRUE( EXPECT_TRUE(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment