Commit 6fa61eb9 authored by Maria Kazinova's avatar Maria Kazinova Committed by Commit Bot

[iOS] Submission detection on iframe detach.

Submitted forms in iframes can be removed from DOM by detaching parent
iframes. The moment if detachment is caught by the PasswordController
and the PasswordManager checks if it has any submitted
PasswordFormManagers related to this frame.

Bug: 1042196
Change-Id: Ia02fc946477202768c80b84532a08c20f00538f9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2237933
Commit-Queue: Maria Kazinova <kazinova@google.com>
Reviewed-by: default avatarVadym Doroshenko  <dvadym@chromium.org>
Cr-Commit-Position: refs/heads/master@{#779897}
parent 1658c432
......@@ -11,6 +11,7 @@
#include <vector>
#include "base/strings/string16.h"
#include "build/build_config.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
#include "components/autofill/core/common/renderer_id.h"
......@@ -114,6 +115,9 @@ struct FormData {
std::vector<FieldRendererId> username_predictions;
// True if this is a Gaia form which should be skipped on saving.
bool is_gaia_with_skip_save_password_form = false;
#if defined(OS_IOS)
std::string frame_id;
#endif
};
// For testing.
......
......@@ -138,6 +138,7 @@ bool ExtractFormData(const base::Value& form_value,
form_dictionary->GetBoolean("is_form_tag", &form_data->is_form_tag);
form_dictionary->GetBoolean("is_formless_checkout",
&form_data->is_formless_checkout);
form_dictionary->GetString("frame_id", &form_data->frame_id);
// Field list (mandatory) is extracted.
const base::ListValue* fields_list = nullptr;
......
......@@ -16,6 +16,8 @@
#import "ios/web/public/test/web_test_with_web_state.h"
#include "testing/platform_test.h"
using web::WebFrame;
class FormTestClient : public web::TestWebClient {
public:
NSString* GetDocumentStartScriptForAllFrames(
......@@ -61,19 +63,22 @@ TEST_F(FormActivityTabHelperTest, TestObserverDocumentSubmitted) {
ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);");
ASSERT_FALSE(observer_->submit_document_info());
const std::string kTestFormName("form-name");
const std::string kTestFormData(
"[{\"name\":\"form-name\",\"origin\":\"https://chromium.test/"
"\",\"action\":\"https://chromium.test/\","
"\"name_attribute\":\"form-name\",\"id_attribute\":\"\","
"\"unique_renderer_id\":\"0\"}]");
WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame();
std::string mainFrameID = main_frame->GetFrameId();
const std::string kTestFormData =
std::string("[{\"name\":\"form-name\",\"origin\":\"https://chromium.test/"
"\",\"action\":\"https://chromium.test/\","
"\"name_attribute\":\"form-name\",\"id_attribute\":\"\","
"\"unique_renderer_id\":\"0\",\"frame_id\":\"") +
mainFrameID + std::string("\"}]");
bool has_user_gesture = false;
bool form_in_main_frame = true;
EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForJSCompletionTimeout, ^bool {
return web_state()->GetWebFramesManager()->GetMainWebFrame() != nullptr;
}));
web::WebFrame* main_frame =
web_state()->GetWebFramesManager()->GetMainWebFrame();
ExecuteJavaScript(@"document.getElementById('submit').click();");
ASSERT_TRUE(observer_->submit_document_info());
......@@ -96,19 +101,22 @@ TEST_F(FormActivityTabHelperTest, TestFormSubmittedHook) {
ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);");
ASSERT_FALSE(observer_->submit_document_info());
const std::string kTestFormName("form-name");
const std::string kTestFormData(
"[{\"name\":\"form-name\",\"origin\":\"https://chromium.test/"
"\",\"action\":\"https://chromium.test/\","
"\"name_attribute\":\"form-name\",\"id_attribute\":\"form\","
"\"unique_renderer_id\":\"0\"}]");
WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame();
std::string mainFrameID = main_frame->GetFrameId();
const std::string kTestFormData =
std::string("[{\"name\":\"form-name\",\"origin\":\"https://chromium.test/"
"\",\"action\":\"https://chromium.test/\","
"\"name_attribute\":\"form-name\",\"id_attribute\":\"form\","
"\"unique_renderer_id\":\"0\",\"frame_id\":\"") +
mainFrameID + std::string("\"}]");
bool has_user_gesture = false;
bool form_in_main_frame = true;
EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForJSCompletionTimeout, ^bool {
return web_state()->GetWebFramesManager()->GetMainWebFrame() != nullptr;
}));
web::WebFrame* main_frame =
web_state()->GetWebFramesManager()->GetMainWebFrame();
ExecuteJavaScript(@"document.getElementById('form').submit();");
ASSERT_TRUE(observer_->submit_document_info());
......@@ -133,8 +141,7 @@ TEST_F(FormActivityTabHelperTest, TestObserverFormActivityFrameMessaging) {
base::test::ios::kWaitForJSCompletionTimeout, ^bool {
return web_state()->GetWebFramesManager()->GetMainWebFrame() != nullptr;
}));
web::WebFrame* main_frame =
web_state()->GetWebFramesManager()->GetMainWebFrame();
WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame();
ASSERT_FALSE(observer_->form_activity_info());
// First call will set document.activeElement (which is usually set by user
// action. Second call will trigger the message.
......
......@@ -34,6 +34,7 @@ let AutofillFormFieldData;
* origin: string,
* action: string,
* fields: Array<AutofillFormFieldData>
* frame_id: string
* }}
*/
let AutofillFormData;
......@@ -851,6 +852,8 @@ __gCrWeb.fill.webFormElementToFormData = function(
__gCrWeb.fill.setUniqueIDIfNeeded(formElement);
form['unique_renderer_id'] = __gCrWeb.fill.getUniqueID(formElement);
form['frame_id'] = frame.__gCrWeb.message.getFrameId();
// Note different from form_autofill_util.cc version of this method, which
// computes |form.action| using document.completeURL(form_element.action())
// and falls back to formElement.action() if the computed action is invalid,
......
......@@ -750,6 +750,14 @@ void PasswordManager::OnPasswordFormRemoved(PasswordManagerDriver* driver,
}
}
}
void PasswordManager::OnIframeDetach(const std::string& frame_id) {
PasswordFormManager* submitted_manager = GetSubmittedManager();
if (submitted_manager &&
submitted_manager->observed_form().frame_id == frame_id) {
OnLoginSuccessful();
}
}
#endif
bool PasswordManager::IsAutomaticSavePromptAvailable() {
......
......@@ -243,6 +243,10 @@ class PasswordManager : public FormSubmissionObserver {
void OnPasswordFormRemoved(PasswordManagerDriver* driver,
autofill::FormRendererId form_id);
// Checks if there is a submitted PasswordFormManager for a form from the
// detached frame.
void OnIframeDetach(const std::string& frame_id);
#endif
private:
......
......@@ -112,7 +112,7 @@ const addSubmitButtonTouchEndHandler = function(form) {
*/
const onSubmitButtonTouchEnd = function(evt) {
const form = evt.currentTarget.form;
const formData = __gCrWeb.passwords.getPasswordFormData(form);
const formData = __gCrWeb.passwords.getPasswordFormData(form, window);
if (!formData) {
return;
}
......@@ -181,7 +181,7 @@ __gCrWeb.passwords['getPasswordFormDataAsString'] = function(identifier) {
if (!el) {
return '{}';
}
const formData = __gCrWeb.passwords.getPasswordFormData(el);
const formData = __gCrWeb.passwords.getPasswordFormData(el, window);
if (!formData) {
return '{}';
}
......@@ -341,7 +341,7 @@ const getPasswordFormDataList = function(formDataList, win) {
const doc = win.document;
const forms = doc.forms;
for (let i = 0; i < forms.length; i++) {
const formData = __gCrWeb.passwords.getPasswordFormData(forms[i]);
const formData = __gCrWeb.passwords.getPasswordFormData(forms[i], win);
if (formData) {
formDataList.push(formData);
addSubmitButtonTouchEndHandler(forms[i]);
......@@ -384,13 +384,14 @@ function getPasswordFormDataFromUnownedElements_(formDataList, window) {
/**
* Returns a JS object containing the data from |formElement|.
* @param {HTMLFormElement} formElement An HTML Form element.
* @param {Window} win A window or a frame containing formData.
* @return {Object} Object of data from formElement.
*/
__gCrWeb.passwords.getPasswordFormData = function(formElement) {
__gCrWeb.passwords.getPasswordFormData = function(formElement, win) {
const extractMask = __gCrWeb.fill.EXTRACT_MASK_VALUE;
const formData = {};
const ok = __gCrWeb.fill.webFormElementToFormData(
window, formElement, null /* formControlElement */, extractMask, formData,
win, formElement, null /* formControlElement */, extractMask, formData,
null /* field */);
return ok ? formData : null;
};
......
......@@ -102,6 +102,8 @@ using password_manager::PasswordManager;
using password_manager::PasswordManagerClient;
using password_manager::PasswordManagerDriver;
using password_manager::SerializePasswordFormFillData;
using web::WebFrame;
using web::WebState;
namespace {
// Types of password infobars to display.
......@@ -184,7 +186,7 @@ NSString* const kSuggestionSuffix = @" ••••••••";
// The WebState this instance is observing. Will be null after
// -webStateDestroyed: has been called.
web::WebState* _webState;
WebState* _webState;
// Bridge to observe WebState from Objective-C.
std::unique_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
......@@ -206,12 +208,12 @@ NSString* const kSuggestionSuffix = @" ••••••••";
NSString* _lastTypedValue;
}
- (instancetype)initWithWebState:(web::WebState*)webState {
- (instancetype)initWithWebState:(WebState*)webState {
self = [self initWithWebState:webState client:nullptr];
return self;
}
- (instancetype)initWithWebState:(web::WebState*)webState
- (instancetype)initWithWebState:(WebState*)webState
client:(std::unique_ptr<PasswordManagerClient>)
passwordManagerClient {
self = [super init];
......@@ -286,7 +288,7 @@ NSString* const kSuggestionSuffix = @" ••••••••";
// If Tab was shown, and there is a pending PasswordForm, display autosign-in
// notification.
- (void)webStateWasShown:(web::WebState*)webState {
- (void)webStateWasShown:(WebState*)webState {
DCHECK_EQ(_webState, webState);
if (_pendingAutoSigninPasswordForm) {
[self showAutosigninNotification:std::move(_pendingAutoSigninPasswordForm)];
......@@ -295,12 +297,12 @@ NSString* const kSuggestionSuffix = @" ••••••••";
}
// If Tab was hidden, hide auto sign-in notification.
- (void)webStateWasHidden:(web::WebState*)webState {
- (void)webStateWasHidden:(WebState*)webState {
DCHECK_EQ(_webState, webState);
[self hideAutosigninNotification];
}
- (void)webState:(web::WebState*)webState
- (void)webState:(WebState*)webState
didFinishNavigation:(web::NavigationContext*)navigation {
DCHECK_EQ(_webState, webState);
if (!navigation->HasCommitted() || navigation->IsSameDocument())
......@@ -318,7 +320,7 @@ NSString* const kSuggestionSuffix = @" ••••••••";
/*form_may_be_submitted=*/navigation->IsRendererInitiated());
}
- (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
- (void)webState:(WebState*)webState didLoadPageWithSuccess:(BOOL)success {
DCHECK_EQ(_webState, webState);
// Clear per-page state.
[self.suggestionHelper resetForNewPage];
......@@ -344,8 +346,8 @@ NSString* const kSuggestionSuffix = @" ••••••••";
[self findPasswordFormsAndSendThemToPasswordStore];
}
- (void)webState:(web::WebState*)webState
frameDidBecomeAvailable:(web::WebFrame*)web_frame {
- (void)webState:(WebState*)webState
frameDidBecomeAvailable:(WebFrame*)web_frame {
DCHECK_EQ(_webState, webState);
DCHECK(web_frame);
UniqueIDTabHelper* uniqueIDTabHelper =
......@@ -355,7 +357,7 @@ NSString* const kSuggestionSuffix = @" ••••••••";
[self.formHelper setUpForUniqueIDsWithInitialState:nextAvailableRendererID];
}
- (void)webStateDestroyed:(web::WebState*)webState {
- (void)webStateDestroyed:(WebState*)webState {
DCHECK_EQ(_webState, webState);
if (_webState) {
_webState->RemoveObserver(_webStateObserverBridge.get());
......@@ -373,6 +375,14 @@ NSString* const kSuggestionSuffix = @" ••••••••";
_lastTypedValue = nil;
}
// Track detaching iframes.
- (void)webState:(WebState*)webState
frameWillBecomeUnavailable:(WebFrame*)web_frame {
if (web_frame->IsMainFrame() || !web_frame->CanCallJavaScriptFunction())
return;
_passwordManager->OnIframeDetach(web_frame->GetFrameId());
}
#pragma mark - FormSuggestionProvider
- (id<FormSuggestionProvider>)suggestionProvider {
......@@ -383,7 +393,7 @@ NSString* const kSuggestionSuffix = @" ••••••••";
(FormSuggestionProviderQuery*)formQuery
isMainFrame:(BOOL)isMainFrame
hasUserGesture:(BOOL)hasUserGesture
webState:(web::WebState*)webState
webState:(WebState*)webState
completionHandler:
(SuggestionsAvailableCompletion)completion {
if (!GetPageURLAndCheckTrustLevel(webState, nullptr))
......@@ -432,7 +442,7 @@ NSString* const kSuggestionSuffix = @" ••••••••";
}
- (void)retrieveSuggestionsForForm:(FormSuggestionProviderQuery*)formQuery
webState:(web::WebState*)webState
webState:(WebState*)webState
completionHandler:(SuggestionsReadyCompletion)completion {
if (!GetPageURLAndCheckTrustLevel(webState, nullptr))
return;
......@@ -548,7 +558,7 @@ NSString* const kSuggestionSuffix = @" ••••••••";
#pragma mark - PasswordManagerClientDelegate
- (web::WebState*)webState {
- (WebState*)webState {
return _webState;
}
......@@ -1023,9 +1033,9 @@ NSString* const kSuggestionSuffix = @" ••••••••";
#pragma mark - FormActivityObserver
- (void)webState:(web::WebState*)webState
- (void)webState:(WebState*)webState
didRegisterFormActivity:(const autofill::FormActivityParams&)params
inFrame:(web::WebFrame*)frame {
inFrame:(WebFrame*)frame {
DCHECK_EQ(_webState, webState);
if (!GetPageURLAndCheckTrustLevel(webState, nullptr))
......
......@@ -5,8 +5,11 @@
#import <Foundation/Foundation.h>
#import "components/password_manager/ios/js_password_manager.h"
#include "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/test/web_js_test.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#import "ios/web/public/web_state.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
......@@ -14,6 +17,8 @@
#error "This file requires ARC support."
#endif
using web::WebFrame;
// Unit tests for
// components/password_manager/ios/resources/password_controller.js
namespace {
......@@ -158,11 +163,15 @@ TEST_F(PasswordControllerJsTest,
ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);");
const std::string base_url = BaseUrl();
WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame();
std::string mainFrameID = main_frame->GetFrameId();
NSString* result = [NSString
stringWithFormat:
@"[{\"name\":\"login_form\",\"origin\":\"%s\",\"action\":\"https://"
@"chromium.test/generic_submit\",\"name_attribute\":\"login_form\","
@"\"id_attribute\":\"\",\"unique_renderer_id\":\"0\",\"fields\":[{"
@"\"id_attribute\":\"\",\"unique_renderer_id\":\"0\","
@"\"frame_id\":\"%s\","
@"\"fields\":[{"
@"\"identifier\":\"name\","
@"\"name\":\"name\",\"name_attribute\":\"name\",\"id_attribute\":"
@"\"\",\"unique_renderer_id\":\"1\",\"form_control_type\":\"text\","
......@@ -177,7 +186,7 @@ TEST_F(PasswordControllerJsTest,
@"\"should_autocomplete\":true,\"is_focusable\":true,"
@"\"max_length\":524288,\"is_checkable\":false,\"value\":\"\","
@"\"label\":\"Password:\"}]}]",
base_url.c_str()];
base_url.c_str(), mainFrameID.c_str()];
EXPECT_NSEQ(result, ExecuteJavaScriptWithFormat(
@"__gCrWeb.passwords.findPasswordForms()"));
}
......@@ -201,11 +210,15 @@ TEST_F(PasswordControllerJsTest,
ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);");
const std::string base_url = BaseUrl();
WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame();
std::string mainFrameID = main_frame->GetFrameId();
NSString* result = [NSString
stringWithFormat:
@"[{\"name\":\"login_form1\",\"origin\":\"%s\",\"action\":\"%s"
@"generic_submit\",\"name_attribute\":\"\",\"id_attribute\":"
@"\"login_form1\",\"unique_renderer_id\":\"0\",\"fields\":[{"
@"\"login_form1\",\"unique_renderer_id\":\"0\","
@"\"frame_id\":\"%s\","
@"\"fields\":[{"
@"\"identifier\":\"name\","
@"\"name\":\"name\",\"name_attribute\":\"name\",\"id_attribute\":"
@"\"\",\"unique_renderer_id\":\"1\",\"form_control_type\":\"text\","
......@@ -222,7 +235,9 @@ TEST_F(PasswordControllerJsTest,
@"\"label\":\"Password:\"}]},{\"name\":\"login_form2\",\"origin\":"
@"\"https://chromium.test/\",\"action\":\"https://chromium.test/"
@"generic_s2\",\"name_attribute\":\"login_form2\","
@"\"id_attribute\":\"\",\"unique_renderer_id\":\"3\",\"fields\":[{"
@"\"id_attribute\":\"\",\"unique_renderer_id\":\"3\","
@"\"frame_id\":\"%s\","
@"\"fields\":[{"
@"\"identifier\":\"name2\","
@"\"name\":\"name2\",\"name_attribute\":\"name2\",\"id_attribute\":"
@"\"\",\"unique_renderer_id\":\"4\",\"form_control_type\":\"text\","
......@@ -238,7 +253,8 @@ TEST_F(PasswordControllerJsTest,
@"\"max_length\":524288,\"is_checkable\":false,"
@"\"value\":\"\","
@"\"label\":\"Password:\"}]}]",
base_url.c_str(), base_url.c_str()];
base_url.c_str(), base_url.c_str(), mainFrameID.c_str(),
mainFrameID.c_str()];
EXPECT_NSEQ(result, ExecuteJavaScriptWithFormat(
@"__gCrWeb.passwords.findPasswordForms()"));
......@@ -259,11 +275,14 @@ TEST_F(PasswordControllerJsTest, GetPasswordFormData) {
const std::string base_url = BaseUrl();
NSString* parameter = @"window.document.getElementsByTagName('form')[0]";
WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame();
std::string mainFrameID = main_frame->GetFrameId();
NSString* result = [NSString
stringWithFormat:
@"{\"name\":\"np\",\"origin\":\"%s\",\"action\":\"%sgeneric_submit\","
@"\"name_attribute\":\"np\",\"id_attribute\":\"np1\",\"unique_"
@"renderer_id\":\"0\","
@"\"frame_id\":\"%s\","
@"\"fields\":[{\"identifier\":\"name\",\"name\":\"name\","
@"\"name_attribute\":\"name\",\"id_attribute\":\"\",\"unique_"
@"renderer_id\":\"1\",\"form_"
......@@ -278,13 +297,12 @@ TEST_F(PasswordControllerJsTest, GetPasswordFormData) {
@"\"should_autocomplete\":true,\"is_focusable\":true,"
@"\"max_length\":524288,"
@"\"is_checkable\":false,\"value\":\"\",\"label\":\"Password:\"}]}",
base_url.c_str(), base_url.c_str()];
base_url.c_str(), base_url.c_str(), mainFrameID.c_str()];
EXPECT_NSEQ(
result,
ExecuteJavaScriptWithFormat(
@"__gCrWeb.stringify(__gCrWeb.passwords.getPasswordFormData(%@))",
parameter));
EXPECT_NSEQ(result, ExecuteJavaScriptWithFormat(
@"__gCrWeb.stringify(__gCrWeb.passwords."
@"getPasswordFormData(%@, window))",
parameter));
}
// Check that if a form action is not set then the action is parsed to the
......@@ -301,11 +319,14 @@ TEST_F(PasswordControllerJsTest, FormActionIsNotSet) {
ExecuteJavaScript(@"__gCrWeb.fill.setUpForUniqueIDs(0);");
const std::string base_url = BaseUrl();
WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame();
std::string mainFrameID = main_frame->GetFrameId();
NSString* result = [NSString
stringWithFormat:
@"[{\"name\":\"login_form\",\"origin\":\"%s\",\"action\":\"%s\","
@"\"name_attribute\":\"login_form\",\"id_attribute\":\"\",\"unique_"
@"renderer_id\":\"0\","
@"\"frame_id\":\"%s\","
@"\"fields\":[{\"identifier\":\"name\",\"name\":\"name\","
@"\"name_attribute\":\"name\",\"id_attribute\":\"\",\"unique_"
@"renderer_id\":\"1\",\"form_"
......@@ -320,7 +341,7 @@ TEST_F(PasswordControllerJsTest, FormActionIsNotSet) {
@"\"should_autocomplete\":true,\"is_focusable\":true,"
@"\"max_length\":524288,"
@"\"is_checkable\":false,\"value\":\"\",\"label\":\"Password:\"}]}]",
base_url.c_str(), base_url.c_str()];
base_url.c_str(), base_url.c_str(), mainFrameID.c_str()];
EXPECT_NSEQ(result, ExecuteJavaScriptWithFormat(
@"__gCrWeb.passwords.findPasswordForms()"));
}
......@@ -362,11 +383,14 @@ TEST_F(PasswordControllerJsTest, TouchendAsSubmissionIndicator) {
// Check that there was only 1 call for invokeOnHost.
EXPECT_NSEQ(@1, ExecuteJavaScriptWithFormat(@"invokeOnHostCalls"));
WebFrame* main_frame = web_state()->GetWebFramesManager()->GetMainWebFrame();
std::string mainFrameID = main_frame->GetFrameId();
NSString* expected_command = [NSString
stringWithFormat:
@"{\"name\":\"login_form\",\"origin\":\"https://chromium.test/"
@"\",\"action\":\"%s\",\"name_attribute\":\"login_form\","
@"\"id_attribute\":\"login_form\",\"unique_renderer_id\":\"0\","
@"\"frame_id\":\"%s\","
@"\"fields\":"
@"[{\"identifier\":\"username\","
@"\"name\":\"username\",\"name_attribute\":\"username\","
......@@ -385,7 +409,7 @@ TEST_F(PasswordControllerJsTest, TouchendAsSubmissionIndicator) {
@"\"is_focusable\":true,\"max_length\":524288,\"is_checkable\":false,"
@"\"value\":\"password1\",\"label\":\"Password:\"}],"
@"\"command\":\"passwordForm.submitButtonClick\"}",
BaseUrl().c_str()];
BaseUrl().c_str(), mainFrameID.c_str()];
// Check that invokeOnHost was called with the correct argument.
EXPECT_NSEQ(
......
......@@ -84,6 +84,7 @@ using base::test::ios::kWaitForJSCompletionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using testing::WithArg;
using testing::_;
using web::WebFrame;
namespace {
......@@ -1890,7 +1891,7 @@ TEST_F(PasswordControllerTest, FindDynamicallyAddedForm2) {
ExecuteJavaScript(kAddFormDynamicallyScript);
std::string mainFrameID = web::GetMainWebFrameId(web_state());
web::WebFrame* frame = web::GetWebFrameWithId(web_state(), mainFrameID);
WebFrame* frame = web::GetWebFrameWithId(web_state(), mainFrameID);
autofill::FormActivityParams params;
params.type = "form_changed";
params.frame_id = mainFrameID;
......@@ -1926,7 +1927,7 @@ TEST_F(PasswordControllerTest, DetectSubmissionOnRemovedForm) {
EXPECT_CALL(*weak_client_, PromptUserToSaveOrUpdatePasswordPtr)
.WillOnce(WithArg<0>(SaveToScopedPtr(&form_manager_to_save)));
web::WebFrame* frame = web::GetWebFrameWithId(web_state(), mainFrameID);
WebFrame* frame = web::GetWebFrameWithId(web_state(), mainFrameID);
autofill::FormActivityParams params;
params.type = "password_form_removed";
params.unique_form_id = 0;
......@@ -1960,7 +1961,7 @@ TEST_F(PasswordControllerTest,
EXPECT_CALL(*weak_client_, PromptUserToSaveOrUpdatePasswordPtr).Times(0);
std::string mainFrameID = web::GetMainWebFrameId(web_state());
web::WebFrame* frame = web::GetWebFrameWithId(web_state(), mainFrameID);
WebFrame* frame = web::GetWebFrameWithId(web_state(), mainFrameID);
autofill::FormActivityParams params;
params.type = "password_form_removed";
params.unique_form_id = 0;
......@@ -1970,3 +1971,110 @@ TEST_F(PasswordControllerTest,
didRegisterFormActivity:params
inFrame:frame];
}
// Tests that submission is detected on removal of the form that had user input.
TEST_F(PasswordControllerTest, DetectSubmissionOnIFrameDetach) {
ON_CALL(*store_, GetLogins)
.WillByDefault(WithArg<1>(InvokeEmptyConsumerWithForms()));
EXPECT_TRUE(
LoadHtml("<script>"
" function FillFrame() {"
" var doc = frames['frame1'].document.open();"
" doc.write('<form id=\"form1\">');"
" doc.write('<input id=\"un\" type=\"text\">');"
" doc.write('<input id=\"pw\" type=\"password\">');"
" doc.write('</form>');"
" doc.close();"
// This event listerer is set by Chrome, but it gets disabled
// by document.write(). This is quite uncommon way to add
// content to an iframe, but it is the only way for this test.
// Reattaching it manually for test purposes.
" frames[0].addEventListener('unload', function(event) {"
" __gCrWeb.common.sendWebKitMessage('FrameBecameUnavailable',"
" frames[0].__gCrWeb.message.getFrameId());"
"});"
"}"
"</script>"
"<body onload='FillFrame()'>"
"<iframe id='frame1' name='frame1'></iframe>"
"</body>"));
std::string mainFrameID = web::GetMainWebFrameId(web_state());
std::set<WebFrame*> all_frames =
web_state()->GetWebFramesManager()->GetAllWebFrames();
std::string iFrameID;
for (auto* frame : all_frames) {
if (!frame->IsMainFrame()) {
iFrameID = frame->GetFrameId();
break;
}
}
SimulateUserTyping("form1", FormRendererId(0), "un", FieldRendererId(1),
"user1", iFrameID);
SimulateUserTyping("form1", FormRendererId(0), "pw", FieldRendererId(2),
"password1", iFrameID);
std::unique_ptr<PasswordFormManagerForUI> form_manager_to_save;
EXPECT_CALL(*weak_client_, PromptUserToSaveOrUpdatePasswordPtr)
.WillOnce(WithArg<0>(SaveToScopedPtr(&form_manager_to_save)));
ExecuteJavaScript(@"var frame1 = document.getElementById('frame1');"
"frame1.parentNode.removeChild(frame1);");
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool() {
auto frames = web_state()->GetWebFramesManager()->GetAllWebFrames();
return frames.size() == 1;
}));
ASSERT_TRUE(form_manager_to_save);
EXPECT_EQ("https://chromium.test/",
form_manager_to_save->GetPendingCredentials().signon_realm);
EXPECT_EQ(ASCIIToUTF16("user1"),
form_manager_to_save->GetPendingCredentials().username_value);
EXPECT_EQ(ASCIIToUTF16("password1"),
form_manager_to_save->GetPendingCredentials().password_value);
auto* form_manager =
static_cast<PasswordFormManager*>(form_manager_to_save.get());
EXPECT_TRUE(form_manager->is_submitted());
EXPECT_FALSE(form_manager->IsPasswordUpdate());
}
// Tests that no submission is detected on removal of the form that had no user
// input.
TEST_F(PasswordControllerTest,
DetectNoSubmissionOnIFrameDetachWithoutUserInput) {
ON_CALL(*store_, GetLogins)
.WillByDefault(WithArg<1>(InvokeEmptyConsumerWithForms()));
EXPECT_TRUE(
LoadHtml("<script>"
" function FillFrame() {"
" var doc = frames['frame1'].document.open();"
" doc.write('<form id=\"form1\">');"
" doc.write('<input id=\"un\" type=\"text\">');"
" doc.write('<input id=\"pw\" type=\"password\">');"
" doc.write('</form>');"
" doc.close();"
// This event listerer is set by Chrome, but it gets disabled
// by document.write(). This is quite uncommon way to add
// content to an iframe, but it is the only way for this test.
// Reattaching it manually for test purposes.
" frames[0].addEventListener('unload', function(event) {"
" __gCrWeb.common.sendWebKitMessage('FrameBecameUnavailable',"
" frames[0].__gCrWeb.message.getFrameId());"
"});"
"}"
"</script>"
"<body onload='FillFrame()'>"
"<iframe id='frame1' name='frame1'></iframe>"
"</body>"));
EXPECT_CALL(*weak_client_, PromptUserToSaveOrUpdatePasswordPtr).Times(0);
ExecuteJavaScript(@"var frame1 = document.getElementById('frame1');"
"frame1.parentNode.removeChild(frame1);");
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool() {
auto frames = web_state()->GetWebFramesManager()->GetAllWebFrames();
return frames.size() == 1;
}));
}
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