| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/web/js_messaging/web_view_js_utils.h" |
| |
| #import <WebKit/WebKit.h> |
| |
| #import "base/functional/bind.h" |
| #import "base/functional/callback_helpers.h" |
| #import "base/mac/foundation_util.h" |
| #import "base/test/ios/wait_util.h" |
| #import "base/values.h" |
| #import "ios/web/test/fakes/crw_fake_script_message_handler.h" |
| #import "testing/gtest/include/gtest/gtest.h" |
| #import "testing/gtest_mac.h" |
| #import "testing/platform_test.h" |
| #import "third_party/abseil-cpp/absl/types/optional.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using base::test::ios::WaitUntilConditionOrTimeout; |
| using base::test::ios::kWaitForJSCompletionTimeout; |
| |
| namespace web { |
| |
| namespace { |
| // Mock implementation of `__gCrWeb.message.getExistingFrames` which counts the |
| // number of times the function is called. |
| NSString* const kMockGetExistingFramesScript = |
| @"var getExistingFramesCallCount = 0;" |
| @"__gCrWeb = {};" |
| @"__gCrWeb['message'] = {};" |
| @"__gCrWeb.message['getExistingFrames'] = function() {" |
| @" getExistingFramesCallCount++;" |
| @"};" |
| @"true;"; |
| |
| // Returns the WKFrameInfo instance for the main frame of `web_view`. |
| WKFrameInfo* GetMainFrameWKFrameInfo(WKWebView* web_view) { |
| // Setup a message handler and recieve a message to obtain a WKFrameInfo |
| // instance. |
| CRWFakeScriptMessageHandler* script_message_handler = |
| [[CRWFakeScriptMessageHandler alloc] init]; |
| [web_view.configuration.userContentController |
| addScriptMessageHandler:script_message_handler |
| name:@"TestHandler"]; |
| web::ExecuteJavaScript( |
| web_view, |
| @"window.webkit.messageHandlers['TestHandler'].postMessage({});", |
| /*completion_handler=*/nil); |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| return !!script_message_handler.lastReceivedScriptMessage.frameInfo; |
| })); |
| |
| return script_message_handler.lastReceivedScriptMessage.frameInfo; |
| } |
| |
| // Sets up the mock script `kMockGetExistingFramesScript` in the given web view, |
| // frame, and content world. |
| void SetupMockGetExistingFramesScript(WKWebView* web_view, |
| WKFrameInfo* frame_info, |
| WKContentWorld* content_world) { |
| __block bool js_execution_complete = false; |
| web::ExecuteJavaScript(web_view, content_world, frame_info, |
| kMockGetExistingFramesScript, |
| ^(id block_result, NSError* block_error) { |
| ASSERT_FALSE(block_error); |
| js_execution_complete = true; |
| }); |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| return js_execution_complete; |
| })); |
| } |
| |
| // Returns the number of times that the mock function setup by |
| // `SetupMockGetExistingFramesScript` has been called. |
| int GetExistingFramesScriptCallCount(WKWebView* web_view, |
| WKFrameInfo* frame_info, |
| WKContentWorld* content_world) { |
| __block int function_call_count = -1; |
| __block bool js_execution_complete = false; |
| web::ExecuteJavaScript(web_view, content_world, frame_info, |
| @"getExistingFramesCallCount", |
| ^(id block_result, NSError* block_error) { |
| ASSERT_FALSE(block_error); |
| function_call_count = [block_result intValue]; |
| js_execution_complete = true; |
| }); |
| EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| return js_execution_complete; |
| })); |
| return function_call_count; |
| } |
| |
| } // namespace |
| |
| using WebViewJsUtilsTest = PlatformTest; |
| |
| // Tests that ValueResultFromWKResult converts nil value to nullptr. |
| TEST_F(WebViewJsUtilsTest, ValueResultFromUndefinedWKResult) { |
| EXPECT_FALSE(ValueResultFromWKResult(nil)); |
| } |
| |
| // Tests that ValueResultFromWKResult converts string to Value::Type::STRING. |
| TEST_F(WebViewJsUtilsTest, ValueResultFromStringWKResult) { |
| std::unique_ptr<base::Value> value(web::ValueResultFromWKResult(@"test")); |
| EXPECT_TRUE(value); |
| EXPECT_EQ(base::Value::Type::STRING, value->type()); |
| ASSERT_TRUE(value->is_string()); |
| EXPECT_EQ("test", value->GetString()); |
| } |
| |
| // Tests that ValueResultFromWKResult converts inetger to Value::Type::DOUBLE. |
| // NOTE: WKWebView API returns all numbers as kCFNumberFloat64Type, so there is |
| // no way to tell if the result is integer or double. |
| TEST_F(WebViewJsUtilsTest, ValueResultFromIntegerWKResult) { |
| std::unique_ptr<base::Value> value(web::ValueResultFromWKResult(@1)); |
| EXPECT_TRUE(value); |
| ASSERT_EQ(base::Value::Type::DOUBLE, value->type()); |
| EXPECT_EQ(1, value->GetDouble()); |
| } |
| |
| // Tests that ValueResultFromWKResult converts double to Value::Type::DOUBLE. |
| TEST_F(WebViewJsUtilsTest, ValueResultFromDoubleWKResult) { |
| std::unique_ptr<base::Value> value(web::ValueResultFromWKResult(@3.14)); |
| EXPECT_TRUE(value); |
| ASSERT_EQ(base::Value::Type::DOUBLE, value->type()); |
| EXPECT_EQ(3.14, value->GetDouble()); |
| } |
| |
| // Tests that ValueResultFromWKResult converts bool to Value::Type::BOOLEAN. |
| TEST_F(WebViewJsUtilsTest, ValueResultFromBoolWKResult) { |
| std::unique_ptr<base::Value> value(web::ValueResultFromWKResult(@YES)); |
| ASSERT_TRUE(value); |
| ASSERT_TRUE(value->is_bool()); |
| EXPECT_TRUE(value->GetBool()); |
| } |
| |
| // Tests that ValueResultFromWKResult converts null to Value::Type::NONE. |
| TEST_F(WebViewJsUtilsTest, ValueResultFromNullWKResult) { |
| std::unique_ptr<base::Value> value( |
| web::ValueResultFromWKResult([NSNull null])); |
| EXPECT_TRUE(value); |
| EXPECT_EQ(base::Value::Type::NONE, value->type()); |
| } |
| |
| // Tests that ValueResultFromWKResult converts NSDictionaries to properly |
| // initialized base::DictionaryValue. |
| TEST_F(WebViewJsUtilsTest, ValueResultFromDictionaryWKResult) { |
| NSDictionary* test_dictionary = |
| @{@"Key1" : @"Value1", |
| @"Key2" : @{@"Key3" : @42}}; |
| |
| std::unique_ptr<base::Value> value( |
| web::ValueResultFromWKResult(test_dictionary)); |
| base::Value::Dict* dictionary = value->GetIfDict(); |
| EXPECT_NE(nullptr, dictionary); |
| |
| std::string* value1 = dictionary->FindString("Key1"); |
| EXPECT_EQ("Value1", *value1); |
| |
| base::Value::Dict const* inner_dictionary = dictionary->FindDict("Key2"); |
| EXPECT_NE(nullptr, inner_dictionary); |
| |
| EXPECT_EQ(42, *inner_dictionary->FindDouble("Key3")); |
| } |
| |
| // Tests that ValueResultFromWKResult converts NSArray to properly |
| // initialized base::ListValue. |
| TEST_F(WebViewJsUtilsTest, ValueResultFromArrayWKResult) { |
| NSArray* test_array = @[ @"Value1", @[ @YES ], @42 ]; |
| |
| std::unique_ptr<base::Value> value(web::ValueResultFromWKResult(test_array)); |
| ASSERT_TRUE(value->is_list()); |
| const base::Value::List& list = value->GetList(); |
| |
| size_t list_size = 3; |
| ASSERT_EQ(list_size, list.size()); |
| |
| ASSERT_TRUE(list[0].is_string()); |
| std::string value1 = list[0].GetString(); |
| EXPECT_EQ("Value1", value1); |
| |
| EXPECT_TRUE(list[1].is_list()); |
| |
| ASSERT_TRUE(list[2].is_double()); |
| double value3 = list[2].GetDouble(); |
| EXPECT_EQ(42, value3); |
| } |
| |
| // Tests that an NSDictionary with a cycle does not cause infinite recursion. |
| TEST_F(WebViewJsUtilsTest, ValueResultFromDictionaryWithDepthCheckWKResult) { |
| // Create a dictionary with a cycle. |
| NSMutableDictionary* test_dictionary = |
| [NSMutableDictionary dictionaryWithCapacity:1]; |
| NSMutableDictionary* test_dictionary_2 = |
| [NSMutableDictionary dictionaryWithCapacity:1]; |
| const char* key = "key"; |
| NSString* obj_c_key = [NSString stringWithCString:key |
| encoding:NSASCIIStringEncoding]; |
| test_dictionary[obj_c_key] = test_dictionary_2; |
| test_dictionary_2[obj_c_key] = test_dictionary; |
| |
| // Break the retain cycle so that the dictionaries are freed. |
| base::ScopedClosureRunner runner(base::BindOnce(^{ |
| [test_dictionary_2 removeAllObjects]; |
| })); |
| |
| // Check that parsing the dictionary stopped at a depth of |
| // `kMaximumParsingRecursionDepth`. |
| std::unique_ptr<base::Value> value = |
| web::ValueResultFromWKResult(test_dictionary); |
| base::Value::Dict* current_dictionary = value->GetIfDict(); |
| base::Value::Dict* inner_dictionary = nullptr; |
| |
| EXPECT_NE(nullptr, current_dictionary); |
| |
| for (int current_depth = 0; current_depth <= kMaximumParsingRecursionDepth; |
| current_depth++) { |
| EXPECT_NE(nullptr, current_dictionary); |
| inner_dictionary = current_dictionary->FindDict(key); |
| current_dictionary = inner_dictionary; |
| } |
| EXPECT_EQ(nullptr, current_dictionary); |
| } |
| |
| // Tests that an NSArray with a cycle does not cause infinite recursion. |
| TEST_F(WebViewJsUtilsTest, ValueResultFromArrayWithDepthCheckWKResult) { |
| // Create an array with a cycle. |
| NSMutableArray* test_array = [NSMutableArray arrayWithCapacity:1]; |
| NSMutableArray* test_array_2 = [NSMutableArray arrayWithCapacity:1]; |
| test_array[0] = test_array_2; |
| test_array_2[0] = test_array; |
| |
| // Break the retain cycle so that the arrays are freed. |
| base::ScopedClosureRunner runner(base::BindOnce(^{ |
| [test_array removeAllObjects]; |
| })); |
| |
| // Check that parsing the array stopped at a depth of |
| // `kMaximumParsingRecursionDepth`. |
| std::unique_ptr<base::Value> value = web::ValueResultFromWKResult(test_array); |
| base::Value::List* current_list = nullptr; |
| base::Value::List* inner_list = nullptr; |
| |
| ASSERT_TRUE(value->is_list()); |
| current_list = &value->GetList(); |
| |
| for (int current_depth = 0; current_depth <= kMaximumParsingRecursionDepth; |
| current_depth++) { |
| ASSERT_TRUE(current_list); |
| |
| inner_list = nullptr; |
| if (!current_list->empty()) |
| inner_list = (*current_list)[0].GetIfList(); |
| current_list = inner_list; |
| } |
| EXPECT_FALSE(current_list); |
| } |
| |
| TEST_F(WebViewJsUtilsTest, NSObjectFromNullptr) { |
| id wk_result = web::NSObjectFromValueResult(nullptr); |
| // `wk_result` should be nil. |
| EXPECT_FALSE(wk_result); |
| } |
| |
| // Tests that NSObjectFromValueResult converts Value::Type::STRING to NSString. |
| TEST_F(WebViewJsUtilsTest, NSObjectFromStringValueResult) { |
| auto value = std::make_unique<base::Value>("test"); |
| id wk_result = web::NSObjectFromValueResult(value.get()); |
| EXPECT_TRUE(wk_result); |
| EXPECT_TRUE([wk_result isKindOfClass:[NSString class]]); |
| EXPECT_NSEQ(@"test", wk_result); |
| } |
| |
| // Tests that NSObjectFromValueResult converts Value::Type::INT to NSNumber. |
| TEST_F(WebViewJsUtilsTest, NSObjectFromIntValueResult) { |
| auto value = std::make_unique<base::Value>(1); |
| id wk_result = web::NSObjectFromValueResult(value.get()); |
| EXPECT_TRUE(wk_result); |
| EXPECT_TRUE([wk_result isKindOfClass:[NSNumber class]]); |
| EXPECT_EQ(1, [wk_result intValue]); |
| } |
| |
| // Tests that NSObjectFromValueResult converts Value::Type::DOUBLE to NSNumber. |
| TEST_F(WebViewJsUtilsTest, NSObjectFromDoubleValueResult) { |
| auto value = std::make_unique<base::Value>(3.14); |
| id wk_result = web::NSObjectFromValueResult(value.get()); |
| EXPECT_TRUE(wk_result); |
| EXPECT_TRUE([wk_result isKindOfClass:[NSNumber class]]); |
| EXPECT_EQ(3.14, [wk_result doubleValue]); |
| } |
| |
| // Tests that NSObjectFromValueResult converts Value::Type::BOOLEAN to NSNumber. |
| TEST_F(WebViewJsUtilsTest, NSObjectFromBoolValueResult) { |
| auto value = std::make_unique<base::Value>(true); |
| id wk_result = web::NSObjectFromValueResult(value.get()); |
| EXPECT_TRUE(wk_result); |
| EXPECT_TRUE([wk_result isKindOfClass:[NSNumber class]]); |
| EXPECT_EQ(YES, [wk_result boolValue]); |
| |
| value.reset(new base::Value(false)); |
| wk_result = web::NSObjectFromValueResult(value.get()); |
| EXPECT_TRUE(wk_result); |
| EXPECT_TRUE([wk_result isKindOfClass:[NSNumber class]]); |
| EXPECT_EQ(NO, [wk_result boolValue]); |
| } |
| |
| // Tests that NSObjectFromValueResult converts Value::Type::NONE to NSNull. |
| TEST_F(WebViewJsUtilsTest, NSObjectFromNoneValueResult) { |
| auto value = std::make_unique<base::Value>(); |
| id wk_result = web::NSObjectFromValueResult(value.get()); |
| EXPECT_TRUE(wk_result); |
| EXPECT_TRUE([wk_result isKindOfClass:[NSNull class]]); |
| } |
| |
| // Tests that NSObjectFromValueResult converts Value::Type::DICT to |
| // NSDictionary. |
| TEST_F(WebViewJsUtilsTest, NSObjectFromDictValueResult) { |
| base::Value::Dict test_dict; |
| test_dict.Set("Key1", "Value1"); |
| |
| base::Value::Dict inner_test_dict; |
| inner_test_dict.Set("Key3", 42); |
| test_dict.Set("Key2", std::move(inner_test_dict)); |
| |
| auto value = std::make_unique<base::Value>(std::move(test_dict)); |
| id wk_result = web::NSObjectFromValueResult(value.get()); |
| EXPECT_TRUE(wk_result); |
| EXPECT_TRUE([wk_result isKindOfClass:[NSDictionary class]]); |
| |
| NSDictionary* wk_result_dictionary = |
| base::mac::ObjCCastStrict<NSDictionary>(wk_result); |
| EXPECT_NSEQ(@"Value1", wk_result_dictionary[@"Key1"]); |
| |
| NSDictionary* inner_dictionary = wk_result_dictionary[@"Key2"]; |
| EXPECT_TRUE(inner_dictionary); |
| EXPECT_NSEQ(@(42), inner_dictionary[@"Key3"]); |
| } |
| |
| // Tests that NSObjectFromValueResult converts Value::Type::LIST to NSArray. |
| TEST_F(WebViewJsUtilsTest, NSObjectFromListValueResult) { |
| base::Value::List test_list; |
| test_list.Append("Value1"); |
| |
| base::Value::List inner_test_list; |
| inner_test_list.Append(true); |
| test_list.Append(std::move(inner_test_list)); |
| |
| test_list.Append(42); |
| |
| auto value = std::make_unique<base::Value>(std::move(test_list)); |
| id wk_result = web::NSObjectFromValueResult(value.get()); |
| EXPECT_TRUE(wk_result); |
| EXPECT_TRUE([wk_result isKindOfClass:[NSArray class]]); |
| |
| NSArray* wk_result_array = base::mac::ObjCCastStrict<NSArray>(wk_result); |
| |
| EXPECT_EQ(3UL, wk_result_array.count); |
| EXPECT_NSEQ(@"Value1", wk_result_array[0]); |
| |
| NSArray* inner_array = wk_result_array[1]; |
| EXPECT_TRUE(inner_array); |
| EXPECT_TRUE([inner_array isKindOfClass:[NSArray class]]); |
| |
| EXPECT_NSEQ(@(42), wk_result_array[2]); |
| } |
| |
| // Tests that ExecuteJavaScript returns an error if there is no web view. |
| TEST_F(WebViewJsUtilsTest, ExecuteJavaScriptNoWebView) { |
| __block bool complete = false; |
| __block id block_result = nil; |
| __block NSError* block_error = nil; |
| web::ExecuteJavaScript(nil, @"return true;", ^(id result, NSError* error) { |
| block_result = [result copy]; |
| block_error = [error copy]; |
| complete = true; |
| }); |
| |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| return complete; |
| })); |
| |
| EXPECT_TRUE(block_error); |
| EXPECT_FALSE(block_result); |
| } |
| |
| // Tests that javascript can be executed. |
| TEST_F(WebViewJsUtilsTest, ExecuteJavaScript) { |
| WKWebView* web_view = [[WKWebView alloc] init]; |
| |
| __block bool complete = false; |
| __block id block_result = nil; |
| __block NSError* block_error = nil; |
| web::ExecuteJavaScript(web_view, @"true", ^(id result, NSError* error) { |
| block_result = [result copy]; |
| block_error = [error copy]; |
| complete = true; |
| }); |
| |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| return complete; |
| })); |
| |
| EXPECT_FALSE(block_error); |
| EXPECT_TRUE(block_result); |
| } |
| |
| // Tests that javascript can be executed in the page content world when the page |
| // content world and web frame are both specified. |
| TEST_F(WebViewJsUtilsTest, ExecuteJavaScriptPageContentWorld) { |
| WKWebView* web_view = [[WKWebView alloc] init]; |
| WKFrameInfo* frame_info = GetMainFrameWKFrameInfo(web_view); |
| ASSERT_TRUE(frame_info); |
| |
| __block bool complete = false; |
| __block id result = nil; |
| __block NSError* error = nil; |
| |
| __block bool set_value_complete = false; |
| __block NSError* set_value_error = nil; |
| |
| // Set `value` in the page content world. |
| web::ExecuteJavaScript(web_view, WKContentWorld.pageWorld, frame_info, |
| @"var value = 3;", |
| ^(id innerResult, NSError* innerError) { |
| set_value_error = [innerError copy]; |
| set_value_complete = true; |
| }); |
| |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| return set_value_complete; |
| })); |
| ASSERT_FALSE(set_value_error); |
| |
| // Ensure the value can be accessed when specifying `frame_info`. |
| web::ExecuteJavaScript(web_view, WKContentWorld.pageWorld, frame_info, |
| @"value", ^(id block_result, NSError* block_error) { |
| result = [block_result copy]; |
| error = [block_error copy]; |
| complete = true; |
| }); |
| |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| return complete; |
| })); |
| |
| EXPECT_FALSE(error); |
| EXPECT_TRUE(result); |
| EXPECT_NSEQ(@(3), result); |
| } |
| |
| // Tests that javascript can be executed in an isolated content world and that |
| // it can not be accessed from the page content world. |
| TEST_F(WebViewJsUtilsTest, ExecuteJavaScriptIsolatedWorld) { |
| WKWebView* web_view = [[WKWebView alloc] init]; |
| WKFrameInfo* frame_info = GetMainFrameWKFrameInfo(web_view); |
| ASSERT_TRUE(frame_info); |
| |
| __block bool set_value_complete = false; |
| __block NSError* set_value_error = nil; |
| // Set `value` in the page content world. |
| web::ExecuteJavaScript(web_view, WKContentWorld.defaultClientWorld, |
| frame_info, @"var value = 3;", |
| ^(id result, NSError* error) { |
| set_value_error = [error copy]; |
| set_value_complete = true; |
| }); |
| |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| return set_value_complete; |
| })); |
| ASSERT_FALSE(set_value_error); |
| |
| __block bool isolated_world_complete = false; |
| __block id isolated_world_result = nil; |
| __block NSError* isolated_world_error = nil; |
| // Ensure the value can be accessed when specifying an isolated world and |
| // `frame_info`. |
| web::ExecuteJavaScript(web_view, WKContentWorld.defaultClientWorld, |
| frame_info, @"value", |
| ^(id block_result, NSError* block_error) { |
| isolated_world_result = [block_result copy]; |
| isolated_world_error = [block_error copy]; |
| isolated_world_complete = true; |
| }); |
| |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| return isolated_world_complete; |
| })); |
| |
| EXPECT_FALSE(isolated_world_error); |
| EXPECT_TRUE(isolated_world_result); |
| EXPECT_NSEQ(@(3), isolated_world_result); |
| |
| __block bool page_world_complete = false; |
| __block id page_world_result = nil; |
| __block NSError* page_world_error = nil; |
| // The value should not be accessible from the page content world. |
| web::ExecuteJavaScript(web_view, WKContentWorld.pageWorld, frame_info, |
| @"try { value } catch (error) { false }", |
| ^(id block_result, NSError* block_error) { |
| page_world_result = [block_result copy]; |
| page_world_error = [block_error copy]; |
| page_world_complete = true; |
| }); |
| |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{ |
| return page_world_complete; |
| })); |
| |
| EXPECT_FALSE(page_world_error); |
| EXPECT_TRUE(page_world_result); |
| EXPECT_FALSE([page_world_result boolValue]); |
| } |
| |
| // Tests that __gCrWeb.message.getExistingFrames() is called in the specified |
| // world. |
| TEST_F(WebViewJsUtilsTest, RegisterExistingFrames) { |
| WKWebView* web_view = [[WKWebView alloc] init]; |
| WKFrameInfo* frame_info = GetMainFrameWKFrameInfo(web_view); |
| ASSERT_TRUE(frame_info); |
| |
| // Create mock __gCrWeb.message.getExistingFrames() in both content worlds. |
| SetupMockGetExistingFramesScript(web_view, frame_info, |
| WKContentWorld.pageWorld); |
| SetupMockGetExistingFramesScript(web_view, frame_info, |
| WKContentWorld.defaultClientWorld); |
| |
| // Verify that getExistingFrames is correctly called in the page world. Only |
| // WKContentWorld.pageWorld should receive the call. |
| web::RegisterExistingFrames(web_view, WKContentWorld.pageWorld); |
| EXPECT_EQ(1, GetExistingFramesScriptCallCount(web_view, frame_info, |
| WKContentWorld.pageWorld)); |
| EXPECT_EQ(0, GetExistingFramesScriptCallCount( |
| web_view, frame_info, WKContentWorld.defaultClientWorld)); |
| |
| // Verify that getExistingFrames is correctly called in an isolated world. |
| // WKContentWorld.pageWorld should not receive another call, but |
| // WKContentWorld.defaultClientWorld should now receive it. |
| web::RegisterExistingFrames(web_view, WKContentWorld.defaultClientWorld); |
| EXPECT_EQ(1, GetExistingFramesScriptCallCount(web_view, frame_info, |
| WKContentWorld.pageWorld)); |
| EXPECT_EQ(1, GetExistingFramesScriptCallCount( |
| web_view, frame_info, WKContentWorld.defaultClientWorld)); |
| } |
| |
| } // namespace web |