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:^(id result, NSError*) {
completionHandler([result isEqual:@YES]); 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()));
......
...@@ -2,45 +2,48 @@ ...@@ -2,45 +2,48 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// This file adheres to closure-compiler conventions in order to enable /**
// compilation with ADVANCED_OPTIMIZATIONS. See http://goo.gl/FwOgy * @fileoverview Installs Passwords management functions on the __gCrWeb object.
// *
// Installs password management functions on the |__gCrWeb| object. * It scans the DOM, extracting and storing password forms and returns a JSON
// * string representing an array of objects, each of which represents an Passord
// Finds all password forms in the current document and extracts * form with information about a form to be filled and/or submitted and it can
// their attributes and elements using the same logic as * be translated to struct FormData for further processing.
// third_party/WebKit/Source/WebCore/html/HTMLFormElement.cpp */
//
// Returns a JSON string representing an array of objects, goog.provide('__crWeb.passwords');
// where each object represents a password form with the discovered
// elements and their values. /* Beginning of anonymous object. */
// (function() {
// The search for password form fields follows the same algorithm
// as the WebKit implementation, see http://goo.gl/4hwh6
// Only install the password management functions once. /**
if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { * Namespace for this file. It depends on |__gCrWeb| having already been
/** * injected.
*/
__gCrWeb.passwords = {};
__gCrWeb['passwords'] = __gCrWeb.passwords;
/**
* Finds all password forms in the window and returns form data as a JSON * Finds all password forms in the window and returns form data as a JSON
* string. * string.
* @return {string} Form data as a JSON string. * @return {string} Form data as a JSON string.
*/ */
__gCrWeb['findPasswordForms'] = function() { __gCrWeb.passwords['findPasswordForms'] = function() {
var formDataList = []; var formDataList = [];
if (hasPasswordField_(window)) { if (hasPasswordField_(window)) {
getPasswordFormDataList_(formDataList, window); getPasswordFormDataList_(formDataList, window);
} }
return __gCrWeb.stringify(formDataList); return __gCrWeb.stringify(formDataList);
}; };
/** Returns true if the supplied window or any frames inside contain an input /** Returns true if the supplied window or any frames inside contain an input
* field of type 'password'. * field of type 'password'.
* @private * @private
* @param {Window} win Whether the supplied window or any frames inside * @param {Window} win Whether the supplied window or any frames inside
* contain an input field of type 'password'. * contain an input field of type 'password'.
* @return {boolean} * @return {boolean}
*/ */
var hasPasswordField_ = function(win) { var hasPasswordField_ = function(win) {
var doc = win.document; var doc = win.document;
// We may will not be allowed to read the 'document' property from a frame // We may will not be allowed to read the 'document' property from a frame
...@@ -54,15 +57,15 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { ...@@ -54,15 +57,15 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) {
} }
return getSameOriginFrames_(win).some(hasPasswordField_); return getSameOriginFrames_(win).some(hasPasswordField_);
}; };
/** /**
* Returns the contentWindow of all iframes that are from the the same origin * Returns the contentWindow of all iframes that are from the the same origin
* as the containing window. * as the containing window.
* @param {Window} win The window in which to look for frames. * @param {Window} win The window in which to look for frames.
* @return {Array.<Window>} Array of the same-origin frames found. * @return {Array.<Window>} Array of the same-origin frames found.
*/ */
var getSameOriginFrames_ = function(win) { var getSameOriginFrames_ = function(win) {
var frames = win.document.getElementsByTagName('iframe'); var frames = win.document.getElementsByTagName('iframe');
var result = []; var result = [];
for (var i = 0; i < frames.length; i++) { for (var i = 0; i < frames.length; i++) {
...@@ -72,102 +75,108 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { ...@@ -72,102 +75,108 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) {
} }
} }
return result; return result;
}; };
/** /**
* If |form| has no submit elements and exactly 1 button that button * If |form| has no submit elements and exactly 1 button that button
* is assumed to be a submit button. This function adds onSubmitButtonClick_ * is assumed to be a submit button. This function adds onSubmitButtonClick_
* as a handler for touchend event of this button. Touchend event is used as * as a handler for touchend event of this button. Touchend event is used as
* a proxy for onclick event because onclick handling might be prevented by * a proxy for onclick event because onclick handling might be prevented by
* the site JavaScript. * the site JavaScript.
*/ */
var addSubmitButtonTouchEndHandler_ = function(form) { var addSubmitButtonTouchEndHandler_ = function(form) {
if (form.querySelector('input[type=submit]')) return; if (form.querySelector('input[type=submit]'))
return;
// Try to find buttons of type submit at first. // Try to find buttons of type submit at first.
var buttons = form.querySelectorAll('button[type="submit"]'); var buttons = form.querySelectorAll('button[type="submit"]');
if (buttons.length == 0) { if (buttons.length == 0) {
// Try to check all buttons. If there is only one button, assume that this // Try to check all buttons. If there is only one button, assume that this
// is the submit button. // is the submit button.
buttons = form.querySelectorAll('button'); buttons = form.querySelectorAll('button');
if (buttons.length != 1) return; if (buttons.length != 1)
return;
} }
for (var i = 0; i < buttons.length; ++i) for (var i = 0; i < buttons.length; ++i)
buttons[0].addEventListener('touchend', onSubmitButtonTouchEnd_); buttons[0].addEventListener('touchend', onSubmitButtonTouchEnd_);
}; };
/** /**
* Click handler for the submit button. It sends to the host * Click handler for the submit button. It sends to the host
* form.submitButtonClick command. * form.submitButtonClick command.
*/ */
var onSubmitButtonTouchEnd_ = function(evt) { var onSubmitButtonTouchEnd_ = function(evt) {
var form = evt.currentTarget.form; var form = evt.currentTarget.form;
var formData = __gCrWeb.getPasswordFormData(form); var formData = __gCrWeb.passwords.getPasswordFormData(form);
if (!formData) return; if (!formData)
return;
formData['command'] = 'passwordForm.submitButtonClick'; formData['command'] = 'passwordForm.submitButtonClick';
__gCrWeb.message.invokeOnHost(formData); __gCrWeb.message.invokeOnHost(formData);
}; };
/** /**
* Returns the element from |inputs| which has the field identifier equal to * Returns the element from |inputs| which has the field identifier equal to
* |identifier| and null if there is no such element. * |identifier| and null if there is no such element.
* @param {Array.<HTMLInputElement>} inputs * @param {Array.<HTMLInputElement>} inputs
* @param {string} identifier * @param {string} identifier
* @return {HTMLInputElement} * @return {HTMLInputElement}
*/ */
var findInputByFieldIdentifier_ = function(inputs, identifier) { var findInputByFieldIdentifier_ = function(inputs, identifier) {
for (var i = 0; i < inputs.length; ++i) { for (var i = 0; i < inputs.length; ++i) {
if (identifier == __gCrWeb.form.getFieldIdentifier(inputs[i])) { if (identifier == __gCrWeb.form.getFieldIdentifier(inputs[i])) {
return inputs[i]; return inputs[i];
} }
} }
return null; return null;
}; };
/** /**
* Returns the password form with the given |identifier| as a JSON string * Returns the password form with the given |identifier| as a JSON string
* from the frame |win| and all its same-origin subframes. * from the frame |win| and all its same-origin subframes.
* @param {Window} win The window in which to look for forms. * @param {Window} win The window in which to look for forms.
* @param {string} identifier The name of the form to extract. * @param {string} identifier The name of the form to extract.
* @return {HTMLFormElement} The password form. * @return {HTMLFormElement} The password form.
*/ */
var getPasswordFormElement_ = function(win, identifier) { var getPasswordFormElement_ = function(win, identifier) {
var el = win.__gCrWeb.form.getFormElementFromIdentifier(identifier); var el = win.__gCrWeb.form.getFormElementFromIdentifier(identifier);
if (el) return el; if (el)
return el;
var frames = getSameOriginFrames_(win); var frames = getSameOriginFrames_(win);
for (var i = 0; i < frames.length; ++i) { for (var i = 0; i < frames.length; ++i) {
el = getPasswordFormElement_(frames[i], identifier); el = getPasswordFormElement_(frames[i], identifier);
if (el) return el; if (el)
return el;
} }
return null; return null;
}; };
/** /**
* Returns an array of input elements in a form. * Returns an array of input elements in a form.
* @param {HTMLFormElement} form A form element for which the input elements * @param {HTMLFormElement} form A form element for which the input elements
* are returned. * are returned.
* @return {Array<HTMLInputElement>} * @return {Array<HTMLInputElement>}
*/ */
var getFormInputElements_ = function(form) { var getFormInputElements_ = function(form) {
return __gCrWeb.form.getFormControlElements(form).filter(function( return __gCrWeb.form.getFormControlElements(form).filter(function(element) {
element) {
return element.tagName === 'INPUT'; return element.tagName === 'INPUT';
}); });
}; };
/** /**
* Returns the password form with the given |identifier| as a JSON string. * Returns the password form with the given |identifier| as a JSON string.
* @param {string} identifier The identifier of the form to extract. * @param {string} identifier The identifier of the form to extract.
* @return {string} The password form. * @return {string} The password form.
*/ */
__gCrWeb['getPasswordFormDataAsString'] = function(identifier) { __gCrWeb.passwords['getPasswordFormDataAsString'] = function(identifier) {
var el = getPasswordFormElement_(window, identifier); var el = getPasswordFormElement_(window, identifier);
if (!el) return '{}'; if (!el)
var formData = __gCrWeb.getPasswordFormData(el); return '{}';
if (!formData) return '{}'; var formData = __gCrWeb.passwords.getPasswordFormData(el);
if (!formData)
return '{}';
return __gCrWeb.stringify(formData); return __gCrWeb.stringify(formData);
}; };
/** /**
* Finds the form described by |formData| and fills in the * Finds the form described by |formData| and fills in the
* username and password values. * username and password values.
* *
...@@ -180,7 +189,7 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { ...@@ -180,7 +189,7 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) {
* @param {string=} opt_normalizedOrigin The origin URL to compare to. * @param {string=} opt_normalizedOrigin The origin URL to compare to.
* @return {boolean} Whether a form field has been filled. * @return {boolean} Whether a form field has been filled.
*/ */
__gCrWeb['fillPasswordForm'] = function( __gCrWeb.passwords['fillPasswordForm'] = function(
formData, username, password, opt_normalizedOrigin) { formData, username, password, opt_normalizedOrigin) {
var normalizedOrigin = opt_normalizedOrigin || var normalizedOrigin = opt_normalizedOrigin ||
__gCrWeb.common.removeQueryAndReferenceFromURL(window.location.href); __gCrWeb.common.removeQueryAndReferenceFromURL(window.location.href);
...@@ -190,9 +199,9 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { ...@@ -190,9 +199,9 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) {
} }
return fillPasswordFormWithData_( return fillPasswordFormWithData_(
formData, username, password, window, opt_normalizedOrigin); formData, username, password, window, opt_normalizedOrigin);
}; };
/** /**
* Given a description of a form (origin, action and input fields), * Given a description of a form (origin, action and input fields),
* finds that form on the page and fills in the specified username * finds that form on the page and fills in the specified username
* and password. * and password.
...@@ -204,7 +213,7 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { ...@@ -204,7 +213,7 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) {
* @param {string=} opt_normalizedOrigin The origin URL to compare to. * @param {string=} opt_normalizedOrigin The origin URL to compare to.
* @return {boolean} Whether a form field has been filled. * @return {boolean} Whether a form field has been filled.
*/ */
var fillPasswordFormWithData_ = function( var fillPasswordFormWithData_ = function(
formData, username, password, win, opt_normalizedOrigin) { formData, username, password, win, opt_normalizedOrigin) {
var doc = win.document; var doc = win.document;
var forms = doc.forms; var forms = doc.forms;
...@@ -212,14 +221,15 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { ...@@ -212,14 +221,15 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) {
for (var i = 0; i < forms.length; i++) { for (var i = 0; i < forms.length; i++) {
var form = forms[i]; var form = forms[i];
var normalizedFormAction = opt_normalizedOrigin || var normalizedFormAction =
__gCrWeb.fill.getCanonicalActionForForm(form); opt_normalizedOrigin || __gCrWeb.fill.getCanonicalActionForForm(form);
if (formData.action != normalizedFormAction) continue; if (formData.action != normalizedFormAction)
continue;
var inputs = getFormInputElements_(form); var inputs = getFormInputElements_(form);
var usernameInput = var usernameInput =
findInputByFieldIdentifier_(inputs, formData.fields[0].name); findInputByFieldIdentifier_(inputs, formData.fields[0].name);
if (usernameInput == null || if (usernameInput == null || !__gCrWeb.common.isTextField(usernameInput) ||
!__gCrWeb.common.isTextField(usernameInput) || usernameInput.disabled) usernameInput.disabled)
continue; continue;
var passwordInput = var passwordInput =
findInputByFieldIdentifier_(inputs, formData.fields[1].name); findInputByFieldIdentifier_(inputs, formData.fields[1].name);
...@@ -258,9 +268,9 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { ...@@ -258,9 +268,9 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) {
} }
return filled; return filled;
}; };
/** /**
* Finds all forms with passwords in the supplied window or frame and appends * Finds all forms with passwords in the supplied window or frame and appends
* JS objects containing the form data to |formDataList|. * JS objects containing the form data to |formDataList|.
* @param {!Array.<Object>} formDataList A list that this function populates * @param {!Array.<Object>} formDataList A list that this function populates
...@@ -268,11 +278,11 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { ...@@ -268,11 +278,11 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) {
* @param {Window} win A window (or frame) in which the function should * @param {Window} win A window (or frame) in which the function should
* look for password forms. * look for password forms.
*/ */
var getPasswordFormDataList_ = function(formDataList, win) { var getPasswordFormDataList_ = function(formDataList, win) {
var doc = win.document; var doc = win.document;
var forms = doc.forms; var forms = doc.forms;
for (var i = 0; i < forms.length; i++) { for (var i = 0; i < forms.length; i++) {
var formData = __gCrWeb.getPasswordFormData(forms[i]); var formData = __gCrWeb.passwords.getPasswordFormData(forms[i]);
if (formData) { if (formData) {
formDataList.push(formData); formDataList.push(formData);
addSubmitButtonTouchEndHandler_(forms[i]); addSubmitButtonTouchEndHandler_(forms[i]);
...@@ -284,19 +294,20 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { ...@@ -284,19 +294,20 @@ if (__gCrWeb && !__gCrWeb['fillPasswordForm']) {
for (var i = 0; i < frames.length; i++) { for (var i = 0; i < frames.length; i++) {
getPasswordFormDataList_(formDataList, frames[i]); getPasswordFormDataList_(formDataList, frames[i]);
} }
}; };
/** /**
* Returns a JS object containing the data from |formElement|. * Returns a JS object containing the data from |formElement|.
* @param {HTMLFormElement} formElement An HTML Form element. * @param {HTMLFormElement} formElement An HTML Form element.
* @return {Object} Object of data from formElement. * @return {Object} Object of data from formElement.
*/ */
__gCrWeb.getPasswordFormData = function(formElement) { __gCrWeb.passwords.getPasswordFormData = function(formElement) {
var extractMask = __gCrWeb.fill.EXTRACT_MASK_VALUE; var extractMask = __gCrWeb.fill.EXTRACT_MASK_VALUE;
var formData = {} var formData = {};
var ok = __gCrWeb.fill.webFormElementToFormData( var ok = __gCrWeb.fill.webFormElementToFormData(
window, formElement, null /* formControlElement */, window, formElement, null /* formControlElement */, extractMask, formData,
extractMask, formData, null /* field */); null /* field */);
return ok ? formData : null; return ok ? formData : null;
}; };
}
}()); // End of anonymous object
...@@ -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