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