blob: f21f28a32f90f464064af0bf25e8c52ffa04dd57 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <WebKit/WebKit.h>
#import "base/functional/bind.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "ios/web/js_messaging/java_script_content_world.h"
#import "ios/web/js_messaging/page_script_util.h"
#import "ios/web/js_messaging/web_frame_impl.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/test/web_state_test_util.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#import "ios/web/public/web_state.h"
#import "ios/web/test/js_test_util_internal.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
using base::test::ios::kWaitForJSCompletionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
namespace {
// Returns the first WebFrame found which is not the main frame in the given
// `web_state`. Does not wait and returns null if such a frame is not found.
web::WebFrame* GetChildWebFrameForWebState(web::WebState* web_state) {
__block web::WebFramesManager* manager =
web_state->GetPageWorldWebFramesManager();
web::WebFrame* iframe = nullptr;
for (web::WebFrame* frame : manager->GetAllWebFrames()) {
if (!frame->IsMainFrame()) {
iframe = frame;
break;
}
}
return iframe;
}
} // namespace
namespace web {
// Test fixture to test WebFrameImpl with a real JavaScript context.
typedef WebTestWithWebState WebFrameImplIntTest;
// Tests that the expected result is received from executing a JavaScript
// function via `CallJavaScriptFunction` on the main frame.
TEST_F(WebFrameImplIntTest, CallJavaScriptFunctionOnMainFrame) {
ASSERT_TRUE(LoadHtml("<p>"));
WebFrame* main_frame =
web_state()->GetPageWorldWebFramesManager()->GetMainWebFrame();
ASSERT_TRUE(main_frame);
__block bool called = false;
main_frame->CallJavaScriptFunction(
"crweb.getFrameId", base::Value::List(),
base::BindOnce(^(const base::Value* value) {
ASSERT_TRUE(value->is_string());
EXPECT_EQ(value->GetString(), main_frame->GetFrameId());
called = true;
}),
// Increase feature timeout in order to fail on test specific timeout.
2 * kWaitForJSCompletionTimeout);
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
return called;
}));
}
TEST_F(WebFrameImplIntTest, CallJavaScriptFunctionOnIframe) {
ASSERT_TRUE(LoadHtml("<p><iframe srcdoc='<p>'/>"));
__block WebFramesManager* manager =
web_state()->GetPageWorldWebFramesManager();
ASSERT_TRUE(WaitUntilConditionOrTimeout(
base::test::ios::kWaitForJSCompletionTimeout, ^bool {
return manager->GetAllWebFrames().size() == 2;
}));
WebFrame* iframe = GetChildWebFrameForWebState(web_state());
ASSERT_TRUE(iframe);
__block bool called = false;
iframe->CallJavaScriptFunction(
"crweb.getFrameId", base::Value::List(),
base::BindOnce(^(const base::Value* value) {
ASSERT_TRUE(value->is_string());
EXPECT_EQ(value->GetString(), iframe->GetFrameId());
called = true;
}),
// Increase feature timeout in order to fail on test specific timeout.
2 * kWaitForJSCompletionTimeout);
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
return called;
}));
}
TEST_F(WebFrameImplIntTest, CallJavaScriptFunctionTimeout) {
ASSERT_TRUE(LoadHtml("<p>"));
// Inject a function which will never return in order to test feature
// timeout.
ExecuteJavaScript(@"function testFunctionNeverReturns(){"
" while(true) {}"
"};"
@"crWebApi = __gCrWeb.getRegisteredApi('crweb');"
@"crWebApi.addFunction('testFunctionNeverReturns', "
@"testFunctionNeverReturns);");
WebFrame* main_frame =
web_state()->GetPageWorldWebFramesManager()->GetMainWebFrame();
ASSERT_TRUE(main_frame);
__block bool called = false;
main_frame->CallJavaScriptFunction(
"crweb.testFunctionNeverReturns", base::Value::List(),
base::BindOnce(^(const base::Value* value) {
EXPECT_FALSE(value);
called = true;
}),
// A small timeout less than kWaitForJSCompletionTimeout. Since this test
// case tests the timeout, it will take at least this long to execute.
// This value should be very small to avoid increasing test suite
// execution time, but long enough to avoid flake.
base::Milliseconds(5));
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
base::RunLoop().RunUntilIdle();
return called;
}));
}
// Tests that the expected result is received from executing a JavaScript
// function via `CallJavaScriptFunction` on the main frame in the page content
// world.
TEST_F(WebFrameImplIntTest, CallJavaScriptFunctionMainFramePageContentWorld) {
ASSERT_TRUE(LoadHtml("<p>"));
ExecuteJavaScript(@"function fakeFunction() {"
@" return '10';"
@"};"
@"crWebApi = __gCrWeb.getRegisteredApi('crweb');"
@"crWebApi.addFunction('fakeFunction', fakeFunction);");
web::WebFrameImpl* main_frame_impl = static_cast<web::WebFrameImpl*>(
web_state()->GetPageWorldWebFramesManager()->GetMainWebFrame());
ASSERT_TRUE(main_frame_impl);
JavaScriptContentWorld world(GetBrowserState(), WKContentWorld.pageWorld);
__block bool called = false;
auto block = ^(const base::Value* value) {
ASSERT_TRUE(value->is_string());
EXPECT_EQ(value->GetString(), "10");
called = true;
};
EXPECT_TRUE(main_frame_impl->CallJavaScriptFunctionInContentWorld(
"crweb.fakeFunction", base::Value::List(), &world, base::BindOnce(block),
// Increase feature timeout in order to fail on test specific timeout.
2 * kWaitForJSCompletionTimeout));
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
return called;
}));
}
// Test fixture for testing WebFrameImpl in different content worlds.
class WebFrameImplContentWorldIntTest
: public WebFrameImplIntTest,
public testing::WithParamInterface<ContentWorld> {
protected:
// Returns the main frame of the test's content world.
WebFrameImpl* main_frame() {
return static_cast<web::WebFrameImpl*>(
web_state()->GetWebFramesManager(GetParam())->GetMainWebFrame());
}
// Returns the `WKContentWorld` in which `WebFrameImpl` should execute
// scripts.
WKContentWorld* GetWKContentWorld() {
switch (GetParam()) {
case ContentWorld::kIsolatedWorld:
return WKContentWorld.defaultClientWorld;
case ContentWorld::kPageContentWorld:
return WKContentWorld.pageWorld;
case ContentWorld::kAllContentWorlds:
NOTREACHED();
}
}
// Executes `script` in the WKWebView associated to the current WebState in
// the test's content world.
void ExecuteJavaScriptInTestContentWorld(NSString* script) {
WKWebView* web_view =
[web::test::GetWebController(web_state()) ensureWebViewCreated];
test::ExecuteJavaScriptInWebViewAndWorld(web_view, GetWKContentWorld(),
script);
}
};
// Tests that the expected result is received from executing a script via
// `ExecuteJavaScript` on the main frame in each content world.
TEST_P(WebFrameImplContentWorldIntTest, ExecuteJavaScriptMainFrame) {
ASSERT_TRUE(LoadHtml("<p>"));
ExecuteJavaScriptInTestContentWorld(
@"function fakeFunction() {"
@" return '10';"
@"};"
@"crWebApi = __gCrWeb.getRegisteredApi('crweb');"
@"crWebApi.addFunction('fakeFunction', fakeFunction);");
web::WebFrameImpl* main_frame_impl = main_frame();
ASSERT_TRUE(main_frame_impl);
__block bool called = false;
auto block = ^(const base::Value* value, NSError* error) {
ASSERT_FALSE(error);
ASSERT_TRUE(value->is_string());
EXPECT_EQ(value->GetString(), "10");
called = true;
};
EXPECT_TRUE(main_frame_impl->ExecuteJavaScript(
u"__gCrWeb.callFunctionInGcrWeb('crweb', 'fakeFunction', [])",
base::BindOnce(block)));
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
return called;
}));
}
// Tests that the expected result is received from executing a JavaScript
// function via `CallJavaScriptFunction` on the main frame in each content
// world.
TEST_P(WebFrameImplContentWorldIntTest,
CallJavaScriptFunctionMainFrameIsolatedWorld) {
ASSERT_TRUE(LoadHtml("<p>"));
ExecuteJavaScriptInTestContentWorld(
@"function fakeFunction() {"
@" return '10';"
@"};"
@"crWebApi = __gCrWeb.getRegisteredApi('crweb');"
@"crWebApi.addFunction('fakeFunction', fakeFunction);");
web::WebFrameImpl* main_frame_impl = main_frame();
ASSERT_TRUE(main_frame_impl);
__block bool called = false;
auto block = ^(const base::Value* value) {
ASSERT_TRUE(value->is_string());
EXPECT_EQ(value->GetString(), "10");
called = true;
};
EXPECT_TRUE(main_frame_impl->CallJavaScriptFunction(
"crweb.fakeFunction", base::Value::List(), base::BindOnce(block),
// Increase feature timeout in order to fail on test specific timeout.
2 * kWaitForJSCompletionTimeout));
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
return called;
}));
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/,
WebFrameImplContentWorldIntTest,
::testing::Values(ContentWorld::kIsolatedWorld,
ContentWorld::kPageContentWorld));
} // namespace web