Commit 16da6daf authored by Vadym Doroshenko's avatar Vadym Doroshenko Committed by Commit Bot

Inject password_controller with WKWebView injection mechanism.

Now password_controller.js is injected by adding it to call of __gCrWeb.findPasswordForms().
It has performance drawbacks since __gCrWeb.findPasswordForms() could be called multiple times.
This CL makes injection of password_controller.js with WKWebView injection.

Also this CL updates the header comment in password_controller.js with up-to-date information.

Bug: 828824, 418827
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: I4036e3279e0c3a32301795b46630c49801679106
Reviewed-on: https://chromium-review.googlesource.com/995534
Commit-Queue: Vadym Doroshenko <dvadym@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#548496}
parent 55ada278
...@@ -6,16 +6,14 @@ ...@@ -6,16 +6,14 @@
#define IOS_CHROME_BROWSER_PASSWORDS_JS_PASSWORD_MANAGER_H_ #define IOS_CHROME_BROWSER_PASSWORDS_JS_PASSWORD_MANAGER_H_
#include "base/ios/block_types.h" #include "base/ios/block_types.h"
#import "ios/web/public/web_state/js/crw_js_injection_manager.h" #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
@class CRWJSInjectionReceiver;
// Loads the JavaScript file, password_controller.js, which contains password // Loads the JavaScript file, password_controller.js, which contains password
// form parsing and autofill functions. It will be evaluated on a page that // form parsing and autofill functions. It will be evaluated on a page that
// is known to have at least one password form (see hasPasswordField_ in // is known to have at least one password form (see hasPasswordField_ in
// password_controller.js) It returns contents of those password forms and also // password_controller.js) It returns contents of those password forms and also
// registers functions that are later used to autofill them. // registers functions that are later used to autofill them.
@interface JsPasswordManager : CRWJSInjectionManager @interface JsPasswordManager : NSObject
// Finds any password forms on the web page. // Finds any password forms on the web page.
// |completionHandler| is then called with the JSON string result (which can // |completionHandler| is then called with the JSON string result (which can
...@@ -48,6 +46,12 @@ ...@@ -48,6 +46,12 @@
password:(NSString*)password password:(NSString*)password
completionHandler:(void (^)(BOOL))completionHandler; completionHandler:(void (^)(BOOL))completionHandler;
// Designated initializer. |receiver| should not be nil.
- (instancetype)initWithReceiver:(CRWJSInjectionReceiver*)receiver
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@end @end
#endif // IOS_CHROME_BROWSER_PASSWORDS_JS_PASSWORD_MANAGER_H_ #endif // IOS_CHROME_BROWSER_PASSWORDS_JS_PASSWORD_MANAGER_H_
...@@ -22,32 +22,39 @@ NSString* JSONEscape(NSString* JSONString) { ...@@ -22,32 +22,39 @@ NSString* JSONEscape(NSString* JSONString) {
} }
} // namespace } // namespace
@interface JsPasswordManager () @implementation JsPasswordManager {
// Injects a script that does two things: // The injection receiver used to evaluate JavaScript.
// 1. Injects password controller JavaScript in the page. CRWJSInjectionReceiver* _receiver;
// 2. Extracts the _submitted_ password form data from the DOM on the page. }
// The result is returned in |completionHandler|.
// |completionHandler| cannot be nil.
- (void)evaluateExtraScript:(NSString*)script
completionHandler:(void (^)(NSString*))completionHandler;
@end
@implementation JsPasswordManager - (instancetype)initWithReceiver:(CRWJSInjectionReceiver*)receiver {
DCHECK(receiver);
self = [super init];
if (self) {
_receiver = receiver;
}
return self;
}
- (void)findPasswordFormsWithCompletionHandler: - (void)findPasswordFormsWithCompletionHandler:
(void (^)(NSString*))completionHandler { (void (^)(NSString*))completionHandler {
DCHECK(completionHandler); DCHECK(completionHandler);
[self evaluateExtraScript:@"__gCrWeb.findPasswordForms()" [_receiver executeJavaScript:@"__gCrWeb.passwords.findPasswordForms()"
completionHandler:completionHandler]; completionHandler:^(id result, NSError*) {
completionHandler(base::mac::ObjCCastStrict<NSString>(result));
}];
} }
- (void)extractForm:(NSString*)formName - (void)extractForm:(NSString*)formName
completionHandler:(void (^)(NSString*))completionHandler { completionHandler:(void (^)(NSString*))completionHandler {
DCHECK(completionHandler); DCHECK(completionHandler);
NSString* extra = NSString* extra = [NSString
[NSString stringWithFormat:@"__gCrWeb.getPasswordFormDataAsString(%@)", stringWithFormat:@"__gCrWeb.passwords.getPasswordFormDataAsString(%@)",
JSONEscape(formName)]; JSONEscape(formName)];
[self evaluateExtraScript:extra completionHandler:completionHandler]; [_receiver executeJavaScript:extra
completionHandler:^(id result, NSError*) {
completionHandler(base::mac::ObjCCastStrict<NSString>(result));
}];
} }
- (void)fillPasswordForm:(NSString*)JSONString - (void)fillPasswordForm:(NSString*)JSONString
...@@ -56,30 +63,13 @@ NSString* JSONEscape(NSString* JSONString) { ...@@ -56,30 +63,13 @@ NSString* JSONEscape(NSString* JSONString) {
completionHandler:(void (^)(BOOL))completionHandler { completionHandler:(void (^)(BOOL))completionHandler {
DCHECK(completionHandler); DCHECK(completionHandler);
NSString* script = [NSString NSString* script = [NSString
stringWithFormat:@"__gCrWeb.fillPasswordForm(%@, %@, %@)", JSONString, stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, %@, %@)",
JSONEscape(username), JSONEscape(password)]; JSONString, JSONEscape(username), JSONEscape(password)];
[self executeJavaScript:script completionHandler:^(id result, NSError*) { [_receiver executeJavaScript:script
completionHandler([result isEqual:@YES]); completionHandler:^(id result, NSError*) {
}]; completionHandler([result isEqual:@YES]);
}];
} }
#pragma mark -
#pragma mark ProtectedMethods
- (NSString*)scriptPath {
return @"password_controller";
}
#pragma mark -
#pragma mark Private
- (void)evaluateExtraScript:(NSString*)script
completionHandler:(void (^)(NSString*))completionHandler {
DCHECK(completionHandler);
NSString* JS = [[self injectionContent] stringByAppendingString:script];
[self executeJavaScript:JS completionHandler:^(id result, NSError*) {
completionHandler(base::mac::ObjCCastStrict<NSString>(result));
}];
}
@end @end
...@@ -268,7 +268,7 @@ bool GetPageURLAndCheckTrustLevel(web::WebState* web_state, GURL* page_url) { ...@@ -268,7 +268,7 @@ bool GetPageURLAndCheckTrustLevel(web::WebState* web_state, GURL* page_url) {
std::unique_ptr<PasswordManagerDriver> passwordManagerDriver_; std::unique_ptr<PasswordManagerDriver> passwordManagerDriver_;
std::unique_ptr<CredentialManager> credentialManager_; std::unique_ptr<CredentialManager> credentialManager_;
__weak JsPasswordManager* passwordJsManager_; JsPasswordManager* passwordJsManager_;
AccountSelectFillData fillData_; AccountSelectFillData fillData_;
...@@ -327,12 +327,11 @@ bool GetPageURLAndCheckTrustLevel(web::WebState* web_state, GURL* page_url) { ...@@ -327,12 +327,11 @@ bool GetPageURLAndCheckTrustLevel(web::WebState* web_state, GURL* page_url) {
passwordManager_.reset(new PasswordManager(passwordManagerClient_.get())); passwordManager_.reset(new PasswordManager(passwordManagerClient_.get()));
passwordManagerDriver_.reset(new IOSChromePasswordManagerDriver(self)); passwordManagerDriver_.reset(new IOSChromePasswordManagerDriver(self));
passwordJsManager_ = base::mac::ObjCCastStrict<JsPasswordManager>(
[webState_->GetJSInjectionReceiver()
instanceOfClass:[JsPasswordManager class]]);
webStateObserverBridge_ = webStateObserverBridge_ =
std::make_unique<web::WebStateObserverBridge>(self); std::make_unique<web::WebStateObserverBridge>(self);
webState_->AddObserver(webStateObserverBridge_.get()); webState_->AddObserver(webStateObserverBridge_.get());
passwordJsManager_ = [[JsPasswordManager alloc]
initWithReceiver:webState_->GetJSInjectionReceiver()];
sentRequestToStore_ = NO; sentRequestToStore_ = NO;
if (base::FeatureList::IsEnabled(features::kCredentialManager)) { if (base::FeatureList::IsEnabled(features::kCredentialManager)) {
......
...@@ -22,10 +22,8 @@ class PasswordControllerJsTest ...@@ -22,10 +22,8 @@ class PasswordControllerJsTest
: public web::WebJsTest<web::WebTestWithWebState> { : public web::WebJsTest<web::WebTestWithWebState> {
public: public:
PasswordControllerJsTest() PasswordControllerJsTest()
: web::WebJsTest<web::WebTestWithWebState>(@[ : web::WebJsTest<web::WebTestWithWebState>(
@"chrome_bundle_all_frames", @"chrome_bundle_main_frame", @[ @"chrome_bundle_all_frames", @"chrome_bundle_main_frame" ]) {}
@"password_controller"
]) {}
}; };
// IDs used in the Username and Password <input> elements. // IDs used in the Username and Password <input> elements.
...@@ -77,10 +75,11 @@ TEST_F(PasswordControllerJsTest, ...@@ -77,10 +75,11 @@ TEST_F(PasswordControllerJsTest,
NSString* const username = @"john.doe@gmail.com"; NSString* const username = @"john.doe@gmail.com";
NSString* const password = @"super!secret"; NSString* const password = @"super!secret";
LoadHtmlAndInject(GAIASignInForm(formAction, username, YES)); LoadHtmlAndInject(GAIASignInForm(formAction, username, YES));
EXPECT_NSEQ(@YES, ExecuteJavaScriptWithFormat( EXPECT_NSEQ(
@"__gCrWeb.fillPasswordForm(%@, '%@', '%@', '%@')", @YES,
GAIASignInFormData(formAction), username, password, ExecuteJavaScriptWithFormat(
formAction)); @"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '%@', '%@')",
GAIASignInFormData(formAction), username, password, formAction));
// Verifies that the sign-in form has been filled with username/password. // Verifies that the sign-in form has been filled with username/password.
ExecuteJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value", ExecuteJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value",
@[ kEmailInputID, kPasswordInputID ], @[ kEmailInputID, kPasswordInputID ],
...@@ -97,10 +96,11 @@ TEST_F(PasswordControllerJsTest, ...@@ -97,10 +96,11 @@ TEST_F(PasswordControllerJsTest,
NSString* const username2 = @"jean.dubois@gmail.com"; NSString* const username2 = @"jean.dubois@gmail.com";
NSString* const password = @"super!secret"; NSString* const password = @"super!secret";
LoadHtmlAndInject(GAIASignInForm(formAction, username1, YES)); LoadHtmlAndInject(GAIASignInForm(formAction, username1, YES));
EXPECT_NSEQ(@NO, ExecuteJavaScriptWithFormat( EXPECT_NSEQ(
@"__gCrWeb.fillPasswordForm(%@, '%@', '%@', '%@')", @NO,
GAIASignInFormData(formAction), username2, password, ExecuteJavaScriptWithFormat(
formAction)); @"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '%@', '%@')",
GAIASignInFormData(formAction), username2, password, formAction));
// Verifies that the sign-in form has not been filled. // Verifies that the sign-in form has not been filled.
ExecuteJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value", ExecuteJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value",
@[ kEmailInputID, kPasswordInputID ], @[ kEmailInputID, kPasswordInputID ],
...@@ -117,10 +117,11 @@ TEST_F(PasswordControllerJsTest, ...@@ -117,10 +117,11 @@ TEST_F(PasswordControllerJsTest,
NSString* const username2 = @"jane.doe@gmail.com"; NSString* const username2 = @"jane.doe@gmail.com";
NSString* const password = @"super!secret"; NSString* const password = @"super!secret";
LoadHtmlAndInject(GAIASignInForm(formAction, username1, NO)); LoadHtmlAndInject(GAIASignInForm(formAction, username1, NO));
EXPECT_NSEQ(@YES, ExecuteJavaScriptWithFormat( EXPECT_NSEQ(
@"__gCrWeb.fillPasswordForm(%@, '%@', '%@', '%@')", @YES,
GAIASignInFormData(formAction), username2, password, ExecuteJavaScriptWithFormat(
formAction)); @"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '%@', '%@')",
GAIASignInFormData(formAction), username2, password, formAction));
// Verifies that the sign-in form has been filled with the new username // Verifies that the sign-in form has been filled with the new username
// and password. // and password.
ExecuteJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value", ExecuteJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value",
...@@ -154,8 +155,8 @@ TEST_F(PasswordControllerJsTest, ...@@ -154,8 +155,8 @@ TEST_F(PasswordControllerJsTest,
@"\"max_length\":524288,\"is_checkable\":false,\"value\":\"\"," @"\"max_length\":524288,\"is_checkable\":false,\"value\":\"\","
@"\"label\":\"Password:\"}]}]", @"\"label\":\"Password:\"}]}]",
base_url.c_str()]; base_url.c_str()];
EXPECT_NSEQ(result, EXPECT_NSEQ(result, ExecuteJavaScriptWithFormat(
ExecuteJavaScriptWithFormat(@"__gCrWeb.findPasswordForms()")); @"__gCrWeb.passwords.findPasswordForms()"));
}; };
// Check that multiple password forms are identified and serialized correctly. // Check that multiple password forms are identified and serialized correctly.
...@@ -198,8 +199,8 @@ TEST_F(PasswordControllerJsTest, ...@@ -198,8 +199,8 @@ TEST_F(PasswordControllerJsTest,
@"\"label\":\"Password:\"}]}]", @"\"label\":\"Password:\"}]}]",
base_url.c_str(), base_url.c_str()]; base_url.c_str(), base_url.c_str()];
EXPECT_NSEQ(result, EXPECT_NSEQ(result, ExecuteJavaScriptWithFormat(
ExecuteJavaScriptWithFormat(@"__gCrWeb.findPasswordForms()")); @"__gCrWeb.passwords.findPasswordForms()"));
}; };
// Test serializing of password forms. // Test serializing of password forms.
...@@ -231,7 +232,8 @@ TEST_F(PasswordControllerJsTest, GetPasswordFormData) { ...@@ -231,7 +232,8 @@ TEST_F(PasswordControllerJsTest, GetPasswordFormData) {
EXPECT_NSEQ( EXPECT_NSEQ(
result, result,
ExecuteJavaScriptWithFormat( ExecuteJavaScriptWithFormat(
@"__gCrWeb.stringify(__gCrWeb.getPasswordFormData(%@))", parameter)); @"__gCrWeb.stringify(__gCrWeb.passwords.getPasswordFormData(%@))",
parameter));
}; };
// Check that if a form action is not set then the action is parsed to the // Check that if a form action is not set then the action is parsed to the
...@@ -258,8 +260,8 @@ TEST_F(PasswordControllerJsTest, FormActionIsNotSet) { ...@@ -258,8 +260,8 @@ TEST_F(PasswordControllerJsTest, FormActionIsNotSet) {
@"autocomplete\":true,\"is_focusable\":true,\"max_length\":524288," @"autocomplete\":true,\"is_focusable\":true,\"max_length\":524288,"
@"\"is_checkable\":false,\"value\":\"\",\"label\":\"Password:\"}]}]", @"\"is_checkable\":false,\"value\":\"\",\"label\":\"Password:\"}]}]",
base_url.c_str(), base_url.c_str()]; base_url.c_str(), base_url.c_str()];
EXPECT_NSEQ(result, EXPECT_NSEQ(result, ExecuteJavaScriptWithFormat(
ExecuteJavaScriptWithFormat(@"__gCrWeb.findPasswordForms()")); @"__gCrWeb.passwords.findPasswordForms()"));
}; };
// Checks that a touchend event from a button which contains in a password form // Checks that a touchend event from a button which contains in a password form
...@@ -274,9 +276,9 @@ TEST_F(PasswordControllerJsTest, TouchendAsSubmissionIndicator) { ...@@ -274,9 +276,9 @@ TEST_F(PasswordControllerJsTest, TouchendAsSubmissionIndicator) {
"</form>" "</form>"
"</body></html>"); "</body></html>");
// Call __gCrWeb.findPasswordForms in order to set an event handler on the // Call __gCrWeb.passwords.findPasswordForms in order to set an event handler
// button touchend event. // on the button touchend event.
ExecuteJavaScriptWithFormat(@"__gCrWeb.findPasswordForms()"); ExecuteJavaScriptWithFormat(@"__gCrWeb.passwords.findPasswordForms()");
// Replace __gCrWeb.message.invokeOnHost with mock method for checking of call // Replace __gCrWeb.message.invokeOnHost with mock method for checking of call
// arguments. // arguments.
...@@ -348,7 +350,7 @@ TEST_F(PasswordControllerJsTest, OriginsAreDifferentInPathes) { ...@@ -348,7 +350,7 @@ TEST_F(PasswordControllerJsTest, OriginsAreDifferentInPathes) {
page_origin.c_str(), form_fill_data_origin.c_str()]; page_origin.c_str(), form_fill_data_origin.c_str()];
EXPECT_NSEQ(@YES, EXPECT_NSEQ(@YES,
ExecuteJavaScriptWithFormat( ExecuteJavaScriptWithFormat(
@"__gCrWeb.fillPasswordForm(%@, '%@', '%@', '%s')", @"__gCrWeb.passwords.fillPasswordForm(%@, '%@', '%@', '%s')",
form_fill_data, username, password, page_origin.c_str())); form_fill_data, username, password, page_origin.c_str()));
// Verifies that the sign-in form has been filled with username/password. // Verifies that the sign-in form has been filled with username/password.
ExecuteJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value", ExecuteJavaScriptOnElementsAndCheck(@"document.getElementById('%@').value",
......
...@@ -1127,6 +1127,11 @@ TEST_F(PasswordControllerTestSimple, SaveOnNonHTMLLandingPage) { ...@@ -1127,6 +1127,11 @@ TEST_F(PasswordControllerTestSimple, SaveOnNonHTMLLandingPage) {
TestChromeBrowserState::Builder builder; TestChromeBrowserState::Builder builder;
std::unique_ptr<TestChromeBrowserState> browser_state(builder.Build()); std::unique_ptr<TestChromeBrowserState> browser_state(builder.Build());
MockWebState web_state; MockWebState web_state;
id mock_js_injection_receiver =
[OCMockObject mockForClass:[CRWJSInjectionReceiver class]];
[[mock_js_injection_receiver expect] executeJavaScript:[OCMArg any]
completionHandler:[OCMArg any]];
web_state.SetJSInjectionReceiver(mock_js_injection_receiver);
ON_CALL(web_state, GetBrowserState()) ON_CALL(web_state, GetBrowserState())
.WillByDefault(testing::Return(browser_state.get())); .WillByDefault(testing::Return(browser_state.get()));
......
...@@ -121,6 +121,7 @@ js_compile_bundle("chrome_bundle_main_frame") { ...@@ -121,6 +121,7 @@ js_compile_bundle("chrome_bundle_main_frame") {
sources = [ sources = [
"//components/autofill/ios/browser/resources/autofill_controller.js", "//components/autofill/ios/browser/resources/autofill_controller.js",
"//components/autofill/ios/fill/resources/fill.js", "//components/autofill/ios/fill/resources/fill.js",
"//ios/chrome/browser/passwords/resources/password_controller.js",
"resources/chrome_bundle_main_frame.js", "resources/chrome_bundle_main_frame.js",
"resources/print.js", "resources/print.js",
] ]
......
...@@ -7,4 +7,5 @@ goog.provide('__crWeb.chromeBundleMainFrame'); ...@@ -7,4 +7,5 @@ goog.provide('__crWeb.chromeBundleMainFrame');
goog.require('__crWeb.autofill'); goog.require('__crWeb.autofill');
goog.require('__crWeb.fill'); goog.require('__crWeb.fill');
goog.require('__crWeb.passwords');
goog.require('__crWeb.print'); goog.require('__crWeb.print');
...@@ -313,6 +313,7 @@ js_compile_bundle("web_view_bundle") { ...@@ -313,6 +313,7 @@ js_compile_bundle("web_view_bundle") {
"//components/autofill/ios/browser/resources/autofill_controller.js", "//components/autofill/ios/browser/resources/autofill_controller.js",
"//components/autofill/ios/fill/resources/fill.js", "//components/autofill/ios/fill/resources/fill.js",
"//components/autofill/ios/fill/resources/form.js", "//components/autofill/ios/fill/resources/form.js",
"//ios/chrome/browser/passwords/resources/password_controller.js",
"resources/web_view_bundle.js", "resources/web_view_bundle.js",
] ]
} }
......
...@@ -8,3 +8,4 @@ goog.provide('__crWeb.webViewBundle'); ...@@ -8,3 +8,4 @@ goog.provide('__crWeb.webViewBundle');
goog.require('__crWeb.autofill'); goog.require('__crWeb.autofill');
goog.require('__crWeb.fill'); goog.require('__crWeb.fill');
goog.require('__crWeb.form'); goog.require('__crWeb.form');
goog.require('__crWeb.passwords');
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