// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/basictypes.h" #include "base/bind.h" #include "base/callback.h" #include "base/memory/shared_memory.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "base/win/windows_version.h" #include "content/child/request_extra_data.h" #include "content/child/service_worker/service_worker_network_provider.h" #include "content/common/frame_messages.h" #include "content/common/ssl_status_serialization.h" #include "content/common/view_messages.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/web_ui_controller_factory.h" #include "content/public/common/bindings_policy.h" #include "content/public/common/content_switches.h" #include "content/public/common/page_zoom.h" #include "content/public/common/url_constants.h" #include "content/public/common/url_utils.h" #include "content/public/renderer/content_renderer_client.h" #include "content/public/renderer/document_state.h" #include "content/public/renderer/navigation_state.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/frame_load_waiter.h" #include "content/public/test/render_view_test.h" #include "content/public/test/test_utils.h" #include "content/renderer/accessibility/renderer_accessibility.h" #include "content/renderer/history_controller.h" #include "content/renderer/history_serialization.h" #include "content/renderer/render_process.h" #include "content/renderer/render_view_impl.h" #include "content/shell/browser/shell.h" #include "content/shell/browser/shell_browser_context.h" #include "content/test/mock_keyboard.h" #include "net/base/net_errors.h" #include "net/cert/cert_status_flags.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/public/platform/WebData.h" #include "third_party/WebKit/public/platform/WebHTTPBody.h" #include "third_party/WebKit/public/platform/WebString.h" #include "third_party/WebKit/public/platform/WebURLResponse.h" #include "third_party/WebKit/public/web/WebDataSource.h" #include "third_party/WebKit/public/web/WebDeviceEmulationParams.h" #include "third_party/WebKit/public/web/WebHistoryItem.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebPerformance.h" #include "third_party/WebKit/public/web/WebRuntimeFeatures.h" #include "third_party/WebKit/public/web/WebView.h" #include "third_party/WebKit/public/web/WebWindowFeatures.h" #include "ui/events/event.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/gfx/codec/jpeg_codec.h" #include "ui/gfx/range/range.h" #if defined(USE_AURA) && defined(USE_X11) #include #include "ui/events/event_constants.h" #include "ui/events/keycodes/keyboard_code_conversion.h" #include "ui/events/test/events_test_utils.h" #include "ui/events/test/events_test_utils_x11.h" #endif #if defined(USE_OZONE) #include "ui/events/keycodes/keyboard_code_conversion.h" #endif using blink::WebFrame; using blink::WebInputEvent; using blink::WebLocalFrame; using blink::WebMouseEvent; using blink::WebRuntimeFeatures; using blink::WebString; using blink::WebTextDirection; using blink::WebURLError; namespace content { namespace { static const int kProxyRoutingId = 13; #if (defined(USE_AURA) && defined(USE_X11)) || defined(USE_OZONE) // Converts MockKeyboard::Modifiers to ui::EventFlags. int ConvertMockKeyboardModifier(MockKeyboard::Modifiers modifiers) { static struct ModifierMap { MockKeyboard::Modifiers src; int dst; } kModifierMap[] = { { MockKeyboard::LEFT_SHIFT, ui::EF_SHIFT_DOWN }, { MockKeyboard::RIGHT_SHIFT, ui::EF_SHIFT_DOWN }, { MockKeyboard::LEFT_CONTROL, ui::EF_CONTROL_DOWN }, { MockKeyboard::RIGHT_CONTROL, ui::EF_CONTROL_DOWN }, { MockKeyboard::LEFT_ALT, ui::EF_ALT_DOWN }, { MockKeyboard::RIGHT_ALT, ui::EF_ALT_DOWN }, }; int flags = 0; for (size_t i = 0; i < arraysize(kModifierMap); ++i) { if (kModifierMap[i].src & modifiers) { flags |= kModifierMap[i].dst; } } return flags; } #endif class WebUITestWebUIControllerFactory : public WebUIControllerFactory { public: WebUIController* CreateWebUIControllerForURL(WebUI* web_ui, const GURL& url) const override { return NULL; } WebUI::TypeID GetWebUIType(BrowserContext* browser_context, const GURL& url) const override { return WebUI::kNoWebUI; } bool UseWebUIForURL(BrowserContext* browser_context, const GURL& url) const override { return HasWebUIScheme(url); } bool UseWebUIBindingsForURL(BrowserContext* browser_context, const GURL& url) const override { return HasWebUIScheme(url); } }; } // namespace class RenderViewImplTest : public RenderViewTest { public: RenderViewImplTest() { // Attach a pseudo keyboard device to this object. mock_keyboard_.reset(new MockKeyboard()); } ~RenderViewImplTest() override {} void SetUp() override { RenderViewTest::SetUp(); // Enable Blink's experimental and test only features so that test code // does not have to bother enabling each feature. WebRuntimeFeatures::enableExperimentalFeatures(true); WebRuntimeFeatures::enableTestOnlyFeatures(true); } RenderViewImpl* view() { return static_cast(view_); } int view_page_id() { return view()->page_id_; } RenderFrameImpl* frame() { return static_cast(view()->GetMainRenderFrame()); } // Sends IPC messages that emulates a key-press event. int SendKeyEvent(MockKeyboard::Layout layout, int key_code, MockKeyboard::Modifiers modifiers, base::string16* output) { #if defined(OS_WIN) // Retrieve the Unicode character for the given tuple (keyboard-layout, // key-code, and modifiers). // Exit when a keyboard-layout driver cannot assign a Unicode character to // the tuple to prevent sending an invalid key code to the RenderView // object. CHECK(mock_keyboard_.get()); CHECK(output); int length = mock_keyboard_->GetCharacters(layout, key_code, modifiers, output); if (length != 1) return -1; // Create IPC messages from Windows messages and send them to our // back-end. // A keyboard event of Windows consists of three Windows messages: // WM_KEYDOWN, WM_CHAR, and WM_KEYUP. // WM_KEYDOWN and WM_KEYUP sends virtual-key codes. On the other hand, // WM_CHAR sends a composed Unicode character. MSG msg1 = { NULL, WM_KEYDOWN, key_code, 0 }; ui::KeyEvent evt1(msg1); NativeWebKeyboardEvent keydown_event(evt1); SendNativeKeyEvent(keydown_event); MSG msg2 = { NULL, WM_CHAR, (*output)[0], 0 }; ui::KeyEvent evt2(msg2); NativeWebKeyboardEvent char_event(evt2); SendNativeKeyEvent(char_event); MSG msg3 = { NULL, WM_KEYUP, key_code, 0 }; ui::KeyEvent evt3(msg3); NativeWebKeyboardEvent keyup_event(evt3); SendNativeKeyEvent(keyup_event); return length; #elif defined(USE_AURA) && defined(USE_X11) // We ignore |layout|, which means we are only testing the layout of the // current locale. TODO(mazda): fix this to respect |layout|. CHECK(output); const int flags = ConvertMockKeyboardModifier(modifiers); ui::ScopedXI2Event xevent; xevent.InitKeyEvent(ui::ET_KEY_PRESSED, static_cast(key_code), flags); ui::KeyEvent event1(xevent); NativeWebKeyboardEvent keydown_event(event1); SendNativeKeyEvent(keydown_event); // X11 doesn't actually have native character events, but give the test // what it wants. xevent.InitKeyEvent(ui::ET_KEY_PRESSED, static_cast(key_code), flags); ui::KeyEvent event2(xevent); event2.set_character(GetCharacterFromKeyCode(event2.key_code(), event2.flags())); ui::KeyEventTestApi test_event2(&event2); test_event2.set_is_char(true); NativeWebKeyboardEvent char_event(event2); SendNativeKeyEvent(char_event); xevent.InitKeyEvent(ui::ET_KEY_RELEASED, static_cast(key_code), flags); ui::KeyEvent event3(xevent); NativeWebKeyboardEvent keyup_event(event3); SendNativeKeyEvent(keyup_event); long c = GetCharacterFromKeyCode(static_cast(key_code), flags); output->assign(1, static_cast(c)); return 1; #elif defined(USE_OZONE) const int flags = ConvertMockKeyboardModifier(modifiers); ui::KeyEvent keydown_event(ui::ET_KEY_PRESSED, static_cast(key_code), flags); NativeWebKeyboardEvent keydown_web_event(keydown_event); SendNativeKeyEvent(keydown_web_event); ui::KeyEvent char_event(keydown_event.GetCharacter(), static_cast(key_code), flags); NativeWebKeyboardEvent char_web_event(char_event); SendNativeKeyEvent(char_web_event); ui::KeyEvent keyup_event(ui::ET_KEY_RELEASED, static_cast(key_code), flags); NativeWebKeyboardEvent keyup_web_event(keyup_event); SendNativeKeyEvent(keyup_web_event); long c = GetCharacterFromKeyCode(static_cast(key_code), flags); output->assign(1, static_cast(c)); return 1; #else NOTIMPLEMENTED(); return L'\0'; #endif } void EnablePreferredSizeMode() { view()->OnEnablePreferredSizeChangedMode(); } const gfx::Size& GetPreferredSize() { view()->CheckPreferredSize(); return view()->preferred_size_; } void SetZoomLevel(double level) { view()->OnSetZoomLevelForView(false, level); } private: scoped_ptr mock_keyboard_; }; TEST_F(RenderViewImplTest, SaveImageFromDataURL) { const IPC::Message* msg1 = render_thread_->sink().GetFirstMessageMatching( ViewHostMsg_SaveImageFromDataURL::ID); EXPECT_FALSE(msg1); render_thread_->sink().ClearMessages(); const std::string image_data_url = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="; view()->saveImageFromDataURL(WebString::fromUTF8(image_data_url)); ProcessPendingMessages(); const IPC::Message* msg2 = render_thread_->sink().GetFirstMessageMatching( ViewHostMsg_SaveImageFromDataURL::ID); EXPECT_TRUE(msg2); ViewHostMsg_SaveImageFromDataURL::Param param1; ViewHostMsg_SaveImageFromDataURL::Read(msg2, ¶m1); EXPECT_EQ(get<1>(param1).length(), image_data_url.length()); EXPECT_EQ(get<1>(param1), image_data_url); ProcessPendingMessages(); render_thread_->sink().ClearMessages(); const std::string large_data_url(1024 * 1024 * 10 - 1, 'd'); view()->saveImageFromDataURL(WebString::fromUTF8(large_data_url)); ProcessPendingMessages(); const IPC::Message* msg3 = render_thread_->sink().GetFirstMessageMatching( ViewHostMsg_SaveImageFromDataURL::ID); EXPECT_TRUE(msg3); ViewHostMsg_SaveImageFromDataURL::Param param2; ViewHostMsg_SaveImageFromDataURL::Read(msg3, ¶m2); EXPECT_EQ(get<1>(param2).length(), large_data_url.length()); EXPECT_EQ(get<1>(param2), large_data_url); ProcessPendingMessages(); render_thread_->sink().ClearMessages(); const std::string exceeded_data_url(1024 * 1024 * 10 + 1, 'd'); view()->saveImageFromDataURL(WebString::fromUTF8(exceeded_data_url)); ProcessPendingMessages(); const IPC::Message* msg4 = render_thread_->sink().GetFirstMessageMatching( ViewHostMsg_SaveImageFromDataURL::ID); EXPECT_FALSE(msg4); } // Test that we get form state change notifications when input fields change. TEST_F(RenderViewImplTest, DISABLED_OnNavStateChanged) { // Don't want any delay for form state sync changes. This will still post a // message so updates will get coalesced, but as soon as we spin the message // loop, it will generate an update. view()->set_send_content_state_immediately(true); LoadHTML(""); // We should NOT have gotten a form state change notification yet. EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching( ViewHostMsg_UpdateState::ID)); render_thread_->sink().ClearMessages(); // Change the value of the input. We should have gotten an update state // notification. We need to spin the message loop to catch this update. ExecuteJavaScript("document.getElementById('elt_text').value = 'foo';"); ProcessPendingMessages(); EXPECT_TRUE(render_thread_->sink().GetUniqueMessageMatching( ViewHostMsg_UpdateState::ID)); } TEST_F(RenderViewImplTest, OnNavigationHttpPost) { FrameMsg_Navigate_Params nav_params; // An http url will trigger a resource load so cannot be used here. nav_params.common_params.url = GURL("data:text/html,
Page
"); nav_params.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; nav_params.common_params.transition = ui::PAGE_TRANSITION_TYPED; nav_params.page_id = -1; nav_params.request_params.is_post = true; nav_params.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); // Set up post data. const unsigned char* raw_data = reinterpret_cast( "post \0\ndata"); const unsigned int length = 11; const std::vector post_data(raw_data, raw_data + length); nav_params.request_params.browser_initiated_post_data = post_data; frame()->OnNavigate(nav_params); ProcessPendingMessages(); const IPC::Message* frame_navigate_msg = render_thread_->sink().GetUniqueMessageMatching( FrameHostMsg_DidCommitProvisionalLoad::ID); EXPECT_TRUE(frame_navigate_msg); FrameHostMsg_DidCommitProvisionalLoad::Param host_nav_params; FrameHostMsg_DidCommitProvisionalLoad::Read(frame_navigate_msg, &host_nav_params); EXPECT_TRUE(get<0>(host_nav_params).is_post); // Check post data sent to browser matches EXPECT_TRUE(get<0>(host_nav_params).page_state.IsValid()); scoped_ptr entry = PageStateToHistoryEntry(get<0>(host_nav_params).page_state); blink::WebHTTPBody body = entry->root().httpBody(); blink::WebHTTPBody::Element element; bool successful = body.elementAt(0, element); EXPECT_TRUE(successful); EXPECT_EQ(blink::WebHTTPBody::Element::TypeData, element.type); EXPECT_EQ(length, element.data.size()); EXPECT_EQ(0, memcmp(raw_data, element.data.data(), length)); } TEST_F(RenderViewImplTest, DecideNavigationPolicy) { WebUITestWebUIControllerFactory factory; WebUIControllerFactory::RegisterFactory(&factory); DocumentState state; state.set_navigation_state(NavigationState::CreateContentInitiated()); // Navigations to normal HTTP URLs can be handled locally. blink::WebURLRequest request(GURL("http://foo.com")); blink::WebFrameClient::NavigationPolicyInfo policy_info(request); policy_info.frame = GetMainFrame(); policy_info.extraData = &state; policy_info.navigationType = blink::WebNavigationTypeLinkClicked; policy_info.defaultPolicy = blink::WebNavigationPolicyCurrentTab; blink::WebNavigationPolicy policy = frame()->decidePolicyForNavigation( policy_info); EXPECT_EQ(blink::WebNavigationPolicyCurrentTab, policy); // Verify that form posts to WebUI URLs will be sent to the browser process. blink::WebURLRequest form_request(GURL("chrome://foo")); blink::WebFrameClient::NavigationPolicyInfo form_policy_info(form_request); form_policy_info.frame = GetMainFrame(); form_policy_info.extraData = &state; form_policy_info.navigationType = blink::WebNavigationTypeFormSubmitted; form_policy_info.defaultPolicy = blink::WebNavigationPolicyCurrentTab; form_request.setHTTPMethod("POST"); policy = frame()->decidePolicyForNavigation(form_policy_info); EXPECT_EQ(blink::WebNavigationPolicyIgnore, policy); // Verify that popup links to WebUI URLs also are sent to browser. blink::WebURLRequest popup_request(GURL("chrome://foo")); blink::WebFrameClient::NavigationPolicyInfo popup_policy_info(popup_request); popup_policy_info.frame = GetMainFrame(); popup_policy_info.extraData = &state; popup_policy_info.navigationType = blink::WebNavigationTypeLinkClicked; popup_policy_info.defaultPolicy = blink::WebNavigationPolicyNewForegroundTab; policy = frame()->decidePolicyForNavigation(popup_policy_info); EXPECT_EQ(blink::WebNavigationPolicyIgnore, policy); } TEST_F(RenderViewImplTest, DecideNavigationPolicyHandlesAllTopLevel) { DocumentState state; state.set_navigation_state(NavigationState::CreateContentInitiated()); RendererPreferences prefs = view()->renderer_preferences(); prefs.browser_handles_all_top_level_requests = true; view()->OnSetRendererPrefs(prefs); const blink::WebNavigationType kNavTypes[] = { blink::WebNavigationTypeLinkClicked, blink::WebNavigationTypeFormSubmitted, blink::WebNavigationTypeBackForward, blink::WebNavigationTypeReload, blink::WebNavigationTypeFormResubmitted, blink::WebNavigationTypeOther, }; blink::WebURLRequest request(GURL("http://foo.com")); blink::WebFrameClient::NavigationPolicyInfo policy_info(request); policy_info.frame = GetMainFrame(); policy_info.extraData = &state; policy_info.defaultPolicy = blink::WebNavigationPolicyCurrentTab; for (size_t i = 0; i < arraysize(kNavTypes); ++i) { policy_info.navigationType = kNavTypes[i]; blink::WebNavigationPolicy policy = frame()->decidePolicyForNavigation( policy_info); EXPECT_EQ(blink::WebNavigationPolicyIgnore, policy); } } TEST_F(RenderViewImplTest, DecideNavigationPolicyForWebUI) { // Enable bindings to simulate a WebUI view. view()->OnAllowBindings(BINDINGS_POLICY_WEB_UI); DocumentState state; state.set_navigation_state(NavigationState::CreateContentInitiated()); // Navigations to normal HTTP URLs will be sent to browser process. blink::WebURLRequest request(GURL("http://foo.com")); blink::WebFrameClient::NavigationPolicyInfo policy_info(request); policy_info.frame = GetMainFrame(); policy_info.extraData = &state; policy_info.navigationType = blink::WebNavigationTypeLinkClicked; policy_info.defaultPolicy = blink::WebNavigationPolicyCurrentTab; blink::WebNavigationPolicy policy = frame()->decidePolicyForNavigation( policy_info); EXPECT_EQ(blink::WebNavigationPolicyIgnore, policy); // Navigations to WebUI URLs will also be sent to browser process. blink::WebURLRequest webui_request(GURL("chrome://foo")); blink::WebFrameClient::NavigationPolicyInfo webui_policy_info(webui_request); webui_policy_info.frame = GetMainFrame(); webui_policy_info.extraData = &state; webui_policy_info.navigationType = blink::WebNavigationTypeLinkClicked; webui_policy_info.defaultPolicy = blink::WebNavigationPolicyCurrentTab; policy = frame()->decidePolicyForNavigation(webui_policy_info); EXPECT_EQ(blink::WebNavigationPolicyIgnore, policy); // Verify that form posts to data URLs will be sent to the browser process. blink::WebURLRequest data_request(GURL("data:text/html,foo")); blink::WebFrameClient::NavigationPolicyInfo data_policy_info(data_request); data_policy_info.frame = GetMainFrame(); data_policy_info.extraData = &state; data_policy_info.navigationType = blink::WebNavigationTypeFormSubmitted; data_policy_info.defaultPolicy = blink::WebNavigationPolicyCurrentTab; data_request.setHTTPMethod("POST"); policy = frame()->decidePolicyForNavigation(data_policy_info); EXPECT_EQ(blink::WebNavigationPolicyIgnore, policy); // Verify that a popup that creates a view first and then navigates to a // normal HTTP URL will be sent to the browser process, even though the // new view does not have any enabled_bindings_. blink::WebURLRequest popup_request(GURL("http://foo.com")); blink::WebView* new_web_view = view()->createView( GetMainFrame(), popup_request, blink::WebWindowFeatures(), "foo", blink::WebNavigationPolicyNewForegroundTab, false); RenderViewImpl* new_view = RenderViewImpl::FromWebView(new_web_view); blink::WebFrameClient::NavigationPolicyInfo popup_policy_info(popup_request); popup_policy_info.frame = new_web_view->mainFrame()->toWebLocalFrame(); popup_policy_info.extraData = &state; popup_policy_info.navigationType = blink::WebNavigationTypeLinkClicked; popup_policy_info.defaultPolicy = blink::WebNavigationPolicyNewForegroundTab; policy = static_cast(new_view->GetMainRenderFrame())-> decidePolicyForNavigation(popup_policy_info); EXPECT_EQ(blink::WebNavigationPolicyIgnore, policy); // Clean up after the new view so we don't leak it. new_view->Close(); new_view->Release(); } // Ensure the RenderViewImpl sends an ACK to a SwapOut request, even if it is // already swapped out. http://crbug.com/93427. TEST_F(RenderViewImplTest, SendSwapOutACK) { LoadHTML("
Page A
"); int initial_page_id = view_page_id(); // Increment the ref count so that we don't exit when swapping out. RenderProcess::current()->AddRefProcess(); // Respond to a swap out request. view()->GetMainRenderFrame()->OnSwapOut(kProxyRoutingId, true, content::FrameReplicationState()); // Ensure the swap out commits synchronously. EXPECT_NE(initial_page_id, view_page_id()); // Check for a valid OnSwapOutACK. const IPC::Message* msg = render_thread_->sink().GetUniqueMessageMatching( FrameHostMsg_SwapOut_ACK::ID); ASSERT_TRUE(msg); // It is possible to get another swap out request. Ensure that we send // an ACK, even if we don't have to do anything else. render_thread_->sink().ClearMessages(); view()->GetMainRenderFrame()->OnSwapOut(kProxyRoutingId, false, content::FrameReplicationState()); const IPC::Message* msg2 = render_thread_->sink().GetUniqueMessageMatching( FrameHostMsg_SwapOut_ACK::ID); ASSERT_TRUE(msg2); // If we navigate back to this RenderView, ensure we don't send a state // update for the swapped out URL. (http://crbug.com/72235) FrameMsg_Navigate_Params nav_params; nav_params.common_params.url = GURL("data:text/html,
Page B
"); nav_params.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; nav_params.common_params.transition = ui::PAGE_TRANSITION_TYPED; nav_params.current_history_list_length = 1; nav_params.current_history_list_offset = 0; nav_params.pending_history_list_offset = 1; nav_params.page_id = -1; nav_params.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(nav_params); ProcessPendingMessages(); const IPC::Message* msg3 = render_thread_->sink().GetUniqueMessageMatching( ViewHostMsg_UpdateState::ID); EXPECT_FALSE(msg3); } // Ensure the RenderViewImpl reloads the previous page if a reload request // arrives while it is showing swappedout://. http://crbug.com/143155. TEST_F(RenderViewImplTest, ReloadWhileSwappedOut) { // Load page A. LoadHTML("
Page A
"); // Load page B, which will trigger an UpdateState message for page A. LoadHTML("
Page B
"); // Check for a valid UpdateState message for page A. ProcessPendingMessages(); const IPC::Message* msg_A = render_thread_->sink().GetUniqueMessageMatching( ViewHostMsg_UpdateState::ID); ASSERT_TRUE(msg_A); ViewHostMsg_UpdateState::Param params; ViewHostMsg_UpdateState::Read(msg_A, ¶ms); int page_id_A = get<0>(params); PageState state_A = get<1>(params); EXPECT_EQ(1, page_id_A); render_thread_->sink().ClearMessages(); // Back to page A (page_id 1) and commit. FrameMsg_Navigate_Params params_A; params_A.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; params_A.common_params.transition = ui::PAGE_TRANSITION_FORWARD_BACK; params_A.current_history_list_length = 2; params_A.current_history_list_offset = 1; params_A.pending_history_list_offset = 0; params_A.page_id = 1; params_A.commit_params.page_state = state_A; params_A.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(params_A); ProcessPendingMessages(); // Respond to a swap out request. view()->GetMainRenderFrame()->OnSwapOut(kProxyRoutingId, true, content::FrameReplicationState()); // Check for a OnSwapOutACK. const IPC::Message* msg = render_thread_->sink().GetUniqueMessageMatching( FrameHostMsg_SwapOut_ACK::ID); ASSERT_TRUE(msg); render_thread_->sink().ClearMessages(); // It is possible to get a reload request at this point, containing the // params.page_state of the initial page (e.g., if the new page fails the // provisional load in the renderer process, after we unload the old page). // Ensure the old page gets reloaded, not swappedout://. FrameMsg_Navigate_Params nav_params; nav_params.common_params.url = GURL("data:text/html,
Page A
"); nav_params.common_params.navigation_type = FrameMsg_Navigate_Type::RELOAD; nav_params.common_params.transition = ui::PAGE_TRANSITION_RELOAD; nav_params.current_history_list_length = 2; nav_params.current_history_list_offset = 0; nav_params.pending_history_list_offset = 0; nav_params.page_id = 1; nav_params.commit_params.page_state = state_A; nav_params.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(nav_params); ProcessPendingMessages(); // Verify page A committed, not swappedout://. const IPC::Message* frame_navigate_msg = render_thread_->sink().GetUniqueMessageMatching( FrameHostMsg_DidCommitProvisionalLoad::ID); EXPECT_TRUE(frame_navigate_msg); // Read URL out of the parent trait of the params object. FrameHostMsg_DidCommitProvisionalLoad::Param commit_params; FrameHostMsg_DidCommitProvisionalLoad::Read(frame_navigate_msg, &commit_params); EXPECT_NE(GURL("swappedout://"), get<0>(commit_params).url); } // Verify that security origins are replicated properly to RenderFrameProxies // when swapping out. TEST_F(RenderViewImplTest, OriginReplicationForSwapOut) { // This test should only run with --site-per-process, since origin // replication only happens in that mode. if (!base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kSitePerProcess)) return; LoadHTML( "Hello " ""); WebFrame* web_frame = frame()->GetWebFrame(); RenderFrameImpl* child_frame = static_cast( RenderFrame::FromWebFrame(web_frame->firstChild())); // Swap the child frame out and pass a serialized origin to be set for // WebRemoteFrame. content::FrameReplicationState replication_state; replication_state.origin = url::Origin("http://foo.com"); child_frame->OnSwapOut(kProxyRoutingId, true, replication_state); // The child frame should now be a WebRemoteFrame. EXPECT_TRUE(web_frame->firstChild()->isWebRemoteFrame()); // Expect the origin to be updated properly. blink::WebSecurityOrigin origin = web_frame->firstChild()->securityOrigin(); EXPECT_EQ(origin.toString(), WebString::fromUTF8(replication_state.origin.string())); // Now, swap out the second frame using a unique origin and verify that it is // replicated correctly. replication_state.origin = url::Origin(); RenderFrameImpl* child_frame2 = static_cast( RenderFrame::FromWebFrame(web_frame->lastChild())); child_frame2->OnSwapOut(kProxyRoutingId + 1, true, replication_state); EXPECT_TRUE(web_frame->lastChild()->isWebRemoteFrame()); EXPECT_TRUE(web_frame->lastChild()->securityOrigin().isUnique()); } // Test that we get the correct UpdateState message when we go back twice // quickly without committing. Regression test for http://crbug.com/58082. // Disabled: http://crbug.com/157357 . TEST_F(RenderViewImplTest, DISABLED_LastCommittedUpdateState) { // Load page A. LoadHTML("
Page A
"); // Load page B, which will trigger an UpdateState message for page A. LoadHTML("
Page B
"); // Check for a valid UpdateState message for page A. ProcessPendingMessages(); const IPC::Message* msg_A = render_thread_->sink().GetUniqueMessageMatching( ViewHostMsg_UpdateState::ID); ASSERT_TRUE(msg_A); ViewHostMsg_UpdateState::Param param; ViewHostMsg_UpdateState::Read(msg_A, ¶m); int page_id_A = get<0>(param); PageState state_A = get<1>(param); EXPECT_EQ(1, page_id_A); render_thread_->sink().ClearMessages(); // Load page C, which will trigger an UpdateState message for page B. LoadHTML("
Page C
"); // Check for a valid UpdateState for page B. ProcessPendingMessages(); const IPC::Message* msg_B = render_thread_->sink().GetUniqueMessageMatching( ViewHostMsg_UpdateState::ID); ASSERT_TRUE(msg_B); ViewHostMsg_UpdateState::Read(msg_B, ¶m); int page_id_B = get<0>(param); PageState state_B = get<1>(param); EXPECT_EQ(2, page_id_B); EXPECT_NE(state_A, state_B); render_thread_->sink().ClearMessages(); // Load page D, which will trigger an UpdateState message for page C. LoadHTML("
Page D
"); // Check for a valid UpdateState for page C. ProcessPendingMessages(); const IPC::Message* msg_C = render_thread_->sink().GetUniqueMessageMatching( ViewHostMsg_UpdateState::ID); ASSERT_TRUE(msg_C); ViewHostMsg_UpdateState::Read(msg_C, ¶m); int page_id_C = get<0>(param); PageState state_C = get<1>(param); EXPECT_EQ(3, page_id_C); EXPECT_NE(state_B, state_C); render_thread_->sink().ClearMessages(); // Go back to C and commit, preparing for our real test. FrameMsg_Navigate_Params params_C; params_C.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; params_C.common_params.transition = ui::PAGE_TRANSITION_FORWARD_BACK; params_C.current_history_list_length = 4; params_C.current_history_list_offset = 3; params_C.pending_history_list_offset = 2; params_C.page_id = 3; params_C.commit_params.page_state = state_C; params_C.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(params_C); ProcessPendingMessages(); render_thread_->sink().ClearMessages(); // Go back twice quickly, such that page B does not have a chance to commit. // This leads to two changes to the back/forward list but only one change to // the RenderView's page ID. // Back to page B (page_id 2), without committing. FrameMsg_Navigate_Params params_B; params_B.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; params_B.common_params.transition = ui::PAGE_TRANSITION_FORWARD_BACK; params_B.current_history_list_length = 4; params_B.current_history_list_offset = 2; params_B.pending_history_list_offset = 1; params_B.page_id = 2; params_B.commit_params.page_state = state_B; params_B.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(params_B); // Back to page A (page_id 1) and commit. FrameMsg_Navigate_Params params; params.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; params.common_params.transition = ui::PAGE_TRANSITION_FORWARD_BACK; params_B.current_history_list_length = 4; params_B.current_history_list_offset = 2; params_B.pending_history_list_offset = 0; params.page_id = 1; params.commit_params.page_state = state_A; params.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(params); ProcessPendingMessages(); // Now ensure that the UpdateState message we receive is consistent // and represents page C in both page_id and state. const IPC::Message* msg = render_thread_->sink().GetUniqueMessageMatching( ViewHostMsg_UpdateState::ID); ASSERT_TRUE(msg); ViewHostMsg_UpdateState::Read(msg, ¶m); int page_id = get<0>(param); PageState state = get<1>(param); EXPECT_EQ(page_id_C, page_id); EXPECT_NE(state_A, state); EXPECT_NE(state_B, state); EXPECT_EQ(state_C, state); } // Test that stale back/forward navigations arriving from the browser are // ignored. See http://crbug.com/86758. TEST_F(RenderViewImplTest, StaleNavigationsIgnored) { // Load page A. LoadHTML("
Page A
"); EXPECT_EQ(1, view()->history_list_length_); EXPECT_EQ(0, view()->history_list_offset_); // Load page B, which will trigger an UpdateState message for page A. LoadHTML("
Page B
"); EXPECT_EQ(2, view()->history_list_length_); EXPECT_EQ(1, view()->history_list_offset_); // Check for a valid UpdateState message for page A. ProcessPendingMessages(); const IPC::Message* msg_A = render_thread_->sink().GetUniqueMessageMatching( ViewHostMsg_UpdateState::ID); ASSERT_TRUE(msg_A); ViewHostMsg_UpdateState::Param param; ViewHostMsg_UpdateState::Read(msg_A, ¶m); int page_id_A = get<0>(param); PageState state_A = get<1>(param); EXPECT_EQ(1, page_id_A); render_thread_->sink().ClearMessages(); // Back to page A (page_id 1) and commit. FrameMsg_Navigate_Params params_A; params_A.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; params_A.common_params.transition = ui::PAGE_TRANSITION_FORWARD_BACK; params_A.current_history_list_length = 2; params_A.current_history_list_offset = 1; params_A.pending_history_list_offset = 0; params_A.page_id = 1; params_A.commit_params.page_state = state_A; params_A.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(params_A); ProcessPendingMessages(); // A new navigation commits, clearing the forward history. LoadHTML("
Page C
"); EXPECT_EQ(2, view()->history_list_length_); EXPECT_EQ(1, view()->history_list_offset_); EXPECT_EQ(3, view()->page_id_); // page C is now page id 3 // The browser then sends a stale navigation to B, which should be ignored. FrameMsg_Navigate_Params params_B; params_B.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; params_B.common_params.transition = ui::PAGE_TRANSITION_FORWARD_BACK; params_B.current_history_list_length = 2; params_B.current_history_list_offset = 0; params_B.pending_history_list_offset = 1; params_B.page_id = 2; params_B.commit_params.page_state = state_A; // Doesn't matter, just has to be present. params_B.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(params_B); // State should be unchanged. EXPECT_EQ(2, view()->history_list_length_); EXPECT_EQ(1, view()->history_list_offset_); EXPECT_EQ(3, view()->page_id_); // page C, not page B // Check for a valid DidDropNavigation message. ProcessPendingMessages(); const IPC::Message* msg = render_thread_->sink().GetUniqueMessageMatching( FrameHostMsg_DidDropNavigation::ID); ASSERT_TRUE(msg); render_thread_->sink().ClearMessages(); } // Test that our IME backend sends a notification message when the input focus // changes. TEST_F(RenderViewImplTest, OnImeTypeChanged) { // Enable our IME backend code. view()->OnSetInputMethodActive(true); // Load an HTML page consisting of two input fields. view()->set_send_content_state_immediately(true); LoadHTML("" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""); render_thread_->sink().ClearMessages(); struct InputModeTestCase { const char* input_id; ui::TextInputMode expected_mode; }; static const InputModeTestCase kInputModeTestCases[] = { {"test1", ui::TEXT_INPUT_MODE_DEFAULT}, {"test3", ui::TEXT_INPUT_MODE_VERBATIM}, {"test4", ui::TEXT_INPUT_MODE_LATIN}, {"test5", ui::TEXT_INPUT_MODE_LATIN_NAME}, {"test6", ui::TEXT_INPUT_MODE_LATIN_PROSE}, {"test7", ui::TEXT_INPUT_MODE_FULL_WIDTH_LATIN}, {"test8", ui::TEXT_INPUT_MODE_KANA}, {"test9", ui::TEXT_INPUT_MODE_KATAKANA}, {"test10", ui::TEXT_INPUT_MODE_NUMERIC}, {"test11", ui::TEXT_INPUT_MODE_TEL}, {"test12", ui::TEXT_INPUT_MODE_EMAIL}, {"test13", ui::TEXT_INPUT_MODE_URL}, {"test14", ui::TEXT_INPUT_MODE_DEFAULT}, {"test15", ui::TEXT_INPUT_MODE_VERBATIM}, }; const int kRepeatCount = 10; for (int i = 0; i < kRepeatCount; i++) { // Move the input focus to the first element, where we should // activate IMEs. ExecuteJavaScript("document.getElementById('test1').focus();"); ProcessPendingMessages(); render_thread_->sink().ClearMessages(); // Update the IME status and verify if our IME backend sends an IPC message // to activate IMEs. view()->UpdateTextInputType(); const IPC::Message* msg = render_thread_->sink().GetMessageAt(0); EXPECT_TRUE(msg != NULL); EXPECT_EQ(ViewHostMsg_TextInputTypeChanged::ID, msg->type()); ViewHostMsg_TextInputTypeChanged::Param params; ViewHostMsg_TextInputTypeChanged::Read(msg, ¶ms); ui::TextInputType type = get<0>(params); ui::TextInputMode input_mode = get<1>(params); bool can_compose_inline = get<2>(params); EXPECT_EQ(ui::TEXT_INPUT_TYPE_TEXT, type); EXPECT_EQ(true, can_compose_inline); // Move the input focus to the second element, where we should // de-activate IMEs. ExecuteJavaScript("document.getElementById('test2').focus();"); ProcessPendingMessages(); render_thread_->sink().ClearMessages(); // Update the IME status and verify if our IME backend sends an IPC message // to de-activate IMEs. view()->UpdateTextInputType(); msg = render_thread_->sink().GetMessageAt(0); EXPECT_TRUE(msg != NULL); EXPECT_EQ(ViewHostMsg_TextInputTypeChanged::ID, msg->type()); ViewHostMsg_TextInputTypeChanged::Read(msg, & params); type = get<0>(params); input_mode = get<1>(params); EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, type); for (size_t i = 0; i < arraysize(kInputModeTestCases); i++) { const InputModeTestCase* test_case = &kInputModeTestCases[i]; std::string javascript = base::StringPrintf("document.getElementById('%s').focus();", test_case->input_id); // Move the input focus to the target element, where we should // activate IMEs. ExecuteJavaScriptAndReturnIntValue(base::ASCIIToUTF16(javascript), NULL); ProcessPendingMessages(); render_thread_->sink().ClearMessages(); // Update the IME status and verify if our IME backend sends an IPC // message to activate IMEs. view()->UpdateTextInputType(); const IPC::Message* msg = render_thread_->sink().GetMessageAt(0); EXPECT_TRUE(msg != NULL); EXPECT_EQ(ViewHostMsg_TextInputTypeChanged::ID, msg->type()); ViewHostMsg_TextInputTypeChanged::Read(msg, & params); type = get<0>(params); input_mode = get<1>(params); EXPECT_EQ(test_case->expected_mode, input_mode); } } } // Test that our IME backend can compose CJK words. // Our IME front-end sends many platform-independent messages to the IME backend // while it composes CJK words. This test sends the minimal messages captured // on my local environment directly to the IME backend to verify if the backend // can compose CJK words without any problems. // This test uses an array of command sets because an IME composotion does not // only depends on IME events, but also depends on window events, e.g. moving // the window focus while composing a CJK text. To handle such complicated // cases, this test should not only call IME-related functions in the // RenderWidget class, but also call some RenderWidget members, e.g. // ExecuteJavaScript(), RenderWidget::OnSetFocus(), etc. TEST_F(RenderViewImplTest, ImeComposition) { enum ImeCommand { IME_INITIALIZE, IME_SETINPUTMODE, IME_SETFOCUS, IME_SETCOMPOSITION, IME_CONFIRMCOMPOSITION, IME_CANCELCOMPOSITION }; struct ImeMessage { ImeCommand command; bool enable; int selection_start; int selection_end; const wchar_t* ime_string; const wchar_t* result; }; static const ImeMessage kImeMessages[] = { // Scenario 1: input a Chinese word with Microsoft IME (on Vista). {IME_INITIALIZE, true, 0, 0, NULL, NULL}, {IME_SETINPUTMODE, true, 0, 0, NULL, NULL}, {IME_SETFOCUS, true, 0, 0, NULL, NULL}, {IME_SETCOMPOSITION, false, 1, 1, L"n", L"n"}, {IME_SETCOMPOSITION, false, 2, 2, L"ni", L"ni"}, {IME_SETCOMPOSITION, false, 3, 3, L"nih", L"nih"}, {IME_SETCOMPOSITION, false, 4, 4, L"niha", L"niha"}, {IME_SETCOMPOSITION, false, 5, 5, L"nihao", L"nihao"}, {IME_CONFIRMCOMPOSITION, false, -1, -1, L"\x4F60\x597D", L"\x4F60\x597D"}, // Scenario 2: input a Japanese word with Microsoft IME (on Vista). {IME_INITIALIZE, true, 0, 0, NULL, NULL}, {IME_SETINPUTMODE, true, 0, 0, NULL, NULL}, {IME_SETFOCUS, true, 0, 0, NULL, NULL}, {IME_SETCOMPOSITION, false, 0, 1, L"\xFF4B", L"\xFF4B"}, {IME_SETCOMPOSITION, false, 0, 1, L"\x304B", L"\x304B"}, {IME_SETCOMPOSITION, false, 0, 2, L"\x304B\xFF4E", L"\x304B\xFF4E"}, {IME_SETCOMPOSITION, false, 0, 3, L"\x304B\x3093\xFF4A", L"\x304B\x3093\xFF4A"}, {IME_SETCOMPOSITION, false, 0, 3, L"\x304B\x3093\x3058", L"\x304B\x3093\x3058"}, {IME_SETCOMPOSITION, false, 0, 2, L"\x611F\x3058", L"\x611F\x3058"}, {IME_SETCOMPOSITION, false, 0, 2, L"\x6F22\x5B57", L"\x6F22\x5B57"}, {IME_CONFIRMCOMPOSITION, false, -1, -1, L"", L"\x6F22\x5B57"}, {IME_CANCELCOMPOSITION, false, -1, -1, L"", L"\x6F22\x5B57"}, // Scenario 3: input a Korean word with Microsot IME (on Vista). {IME_INITIALIZE, true, 0, 0, NULL, NULL}, {IME_SETINPUTMODE, true, 0, 0, NULL, NULL}, {IME_SETFOCUS, true, 0, 0, NULL, NULL}, {IME_SETCOMPOSITION, false, 0, 1, L"\x3147", L"\x3147"}, {IME_SETCOMPOSITION, false, 0, 1, L"\xC544", L"\xC544"}, {IME_SETCOMPOSITION, false, 0, 1, L"\xC548", L"\xC548"}, {IME_CONFIRMCOMPOSITION, false, -1, -1, L"", L"\xC548"}, {IME_SETCOMPOSITION, false, 0, 1, L"\x3134", L"\xC548\x3134"}, {IME_SETCOMPOSITION, false, 0, 1, L"\xB140", L"\xC548\xB140"}, {IME_SETCOMPOSITION, false, 0, 1, L"\xB155", L"\xC548\xB155"}, {IME_CANCELCOMPOSITION, false, -1, -1, L"", L"\xC548"}, {IME_SETCOMPOSITION, false, 0, 1, L"\xB155", L"\xC548\xB155"}, {IME_CONFIRMCOMPOSITION, false, -1, -1, L"", L"\xC548\xB155"}, }; for (size_t i = 0; i < arraysize(kImeMessages); i++) { const ImeMessage* ime_message = &kImeMessages[i]; switch (ime_message->command) { case IME_INITIALIZE: // Load an HTML page consisting of a content-editable
element, // and move the input focus to the
element, where we can use // IMEs. view()->OnSetInputMethodActive(ime_message->enable); view()->set_send_content_state_immediately(true); LoadHTML("" "" "" "" "
" "" ""); ExecuteJavaScript("document.getElementById('test1').focus();"); break; case IME_SETINPUTMODE: // Activate (or deactivate) our IME back-end. view()->OnSetInputMethodActive(ime_message->enable); break; case IME_SETFOCUS: // Update the window focus. view()->OnSetFocus(ime_message->enable); break; case IME_SETCOMPOSITION: view()->OnImeSetComposition( base::WideToUTF16(ime_message->ime_string), std::vector(), ime_message->selection_start, ime_message->selection_end); break; case IME_CONFIRMCOMPOSITION: view()->OnImeConfirmComposition( base::WideToUTF16(ime_message->ime_string), gfx::Range::InvalidRange(), false); break; case IME_CANCELCOMPOSITION: view()->OnImeSetComposition( base::string16(), std::vector(), 0, 0); break; } // Update the status of our IME back-end. // TODO(hbono): we should verify messages to be sent from the back-end. view()->UpdateTextInputType(); ProcessPendingMessages(); render_thread_->sink().ClearMessages(); if (ime_message->result) { // Retrieve the content of this page and compare it with the expected // result. const int kMaxOutputCharacters = 128; base::string16 output = GetMainFrame()->contentAsText(kMaxOutputCharacters); EXPECT_EQ(base::WideToUTF16(ime_message->result), output); } } } // Test that the RenderView::OnSetTextDirection() function can change the text // direction of the selected input element. TEST_F(RenderViewImplTest, OnSetTextDirection) { // Load an HTML page consisting of a " "
" "" ""); render_thread_->sink().ClearMessages(); static const struct { WebTextDirection direction; const wchar_t* expected_result; } kTextDirection[] = { { blink::WebTextDirectionRightToLeft, L"\x000A" L"rtl,rtl" }, { blink::WebTextDirectionLeftToRight, L"\x000A" L"ltr,ltr" }, }; for (size_t i = 0; i < arraysize(kTextDirection); ++i) { // Set the text direction of the "); ExecuteJavaScript("document.getElementById('test').focus();"); const base::string16 empty_string; const std::vector empty_underline; std::vector bounds; view()->OnSetFocus(true); view()->OnSetInputMethodActive(true); // ASCII composition const base::string16 ascii_composition = base::UTF8ToUTF16("aiueo"); view()->OnImeSetComposition(ascii_composition, empty_underline, 0, 0); view()->GetCompositionCharacterBounds(&bounds); ASSERT_EQ(ascii_composition.size(), bounds.size()); for (size_t i = 0; i < bounds.size(); ++i) EXPECT_LT(0, bounds[i].width()); view()->OnImeConfirmComposition( empty_string, gfx::Range::InvalidRange(), false); // Non surrogate pair unicode character. const base::string16 unicode_composition = base::UTF8ToUTF16( "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A"); view()->OnImeSetComposition(unicode_composition, empty_underline, 0, 0); view()->GetCompositionCharacterBounds(&bounds); ASSERT_EQ(unicode_composition.size(), bounds.size()); for (size_t i = 0; i < bounds.size(); ++i) EXPECT_LT(0, bounds[i].width()); view()->OnImeConfirmComposition( empty_string, gfx::Range::InvalidRange(), false); // Surrogate pair character. const base::string16 surrogate_pair_char = base::UTF8ToUTF16("\xF0\xA0\xAE\x9F"); view()->OnImeSetComposition(surrogate_pair_char, empty_underline, 0, 0); view()->GetCompositionCharacterBounds(&bounds); ASSERT_EQ(surrogate_pair_char.size(), bounds.size()); EXPECT_LT(0, bounds[0].width()); EXPECT_EQ(0, bounds[1].width()); view()->OnImeConfirmComposition( empty_string, gfx::Range::InvalidRange(), false); // Mixed string. const base::string16 surrogate_pair_mixed_composition = surrogate_pair_char + base::UTF8ToUTF16("\xE3\x81\x82") + surrogate_pair_char + base::UTF8ToUTF16("b") + surrogate_pair_char; const size_t utf16_length = 8UL; const bool is_surrogate_pair_empty_rect[8] = { false, true, false, false, true, false, false, true }; view()->OnImeSetComposition(surrogate_pair_mixed_composition, empty_underline, 0, 0); view()->GetCompositionCharacterBounds(&bounds); ASSERT_EQ(utf16_length, bounds.size()); for (size_t i = 0; i < utf16_length; ++i) { if (is_surrogate_pair_empty_rect[i]) { EXPECT_EQ(0, bounds[i].width()); } else { EXPECT_LT(0, bounds[i].width()); } } view()->OnImeConfirmComposition( empty_string, gfx::Range::InvalidRange(), false); } #endif TEST_F(RenderViewImplTest, ZoomLimit) { const double kMinZoomLevel = ZoomFactorToZoomLevel(kMinimumZoomFactor); const double kMaxZoomLevel = ZoomFactorToZoomLevel(kMaximumZoomFactor); FrameMsg_Navigate_Params params; params.page_id = -1; params.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; params.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); // Verifies navigation to a URL with preset zoom level indeed sets the level. // Regression test for http://crbug.com/139559, where the level was not // properly set when it is out of the default zoom limits of WebView. params.common_params.url = GURL("data:text/html,min_zoomlimit_test"); view()->OnSetZoomLevelForLoadingURL(params.common_params.url, kMinZoomLevel); frame()->OnNavigate(params); ProcessPendingMessages(); EXPECT_DOUBLE_EQ(kMinZoomLevel, view()->GetWebView()->zoomLevel()); // It should work even when the zoom limit is temporarily changed in the page. view()->GetWebView()->zoomLimitsChanged(ZoomFactorToZoomLevel(1.0), ZoomFactorToZoomLevel(1.0)); params.common_params.url = GURL("data:text/html,max_zoomlimit_test"); view()->OnSetZoomLevelForLoadingURL(params.common_params.url, kMaxZoomLevel); frame()->OnNavigate(params); ProcessPendingMessages(); EXPECT_DOUBLE_EQ(kMaxZoomLevel, view()->GetWebView()->zoomLevel()); } TEST_F(RenderViewImplTest, SetEditableSelectionAndComposition) { // Load an HTML page consisting of an input field. LoadHTML("" "" "" "" "" "" ""); ExecuteJavaScript("document.getElementById('test1').focus();"); frame()->OnSetEditableSelectionOffsets(4, 8); const std::vector empty_underline; frame()->OnSetCompositionFromExistingText(7, 10, empty_underline); blink::WebTextInputInfo info = view()->webview()->textInputInfo(); EXPECT_EQ(4, info.selectionStart); EXPECT_EQ(8, info.selectionEnd); EXPECT_EQ(7, info.compositionStart); EXPECT_EQ(10, info.compositionEnd); frame()->OnUnselect(); info = view()->webview()->textInputInfo(); EXPECT_EQ(0, info.selectionStart); EXPECT_EQ(0, info.selectionEnd); } TEST_F(RenderViewImplTest, OnExtendSelectionAndDelete) { // Load an HTML page consisting of an input field. LoadHTML("" "" "" "" "" "" ""); ExecuteJavaScript("document.getElementById('test1').focus();"); frame()->OnSetEditableSelectionOffsets(10, 10); frame()->OnExtendSelectionAndDelete(3, 4); blink::WebTextInputInfo info = view()->webview()->textInputInfo(); EXPECT_EQ("abcdefgopqrstuvwxyz", info.value); EXPECT_EQ(7, info.selectionStart); EXPECT_EQ(7, info.selectionEnd); frame()->OnSetEditableSelectionOffsets(4, 8); frame()->OnExtendSelectionAndDelete(2, 5); info = view()->webview()->textInputInfo(); EXPECT_EQ("abuvwxyz", info.value); EXPECT_EQ(2, info.selectionStart); EXPECT_EQ(2, info.selectionEnd); } // Test that the navigating specific frames works correctly. TEST_F(RenderViewImplTest, NavigateFrame) { // Load page A. LoadHTML("hello "); // Navigate the frame only. FrameMsg_Navigate_Params nav_params; nav_params.common_params.url = GURL("data:text/html,world"); nav_params.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; nav_params.common_params.transition = ui::PAGE_TRANSITION_TYPED; nav_params.current_history_list_length = 1; nav_params.current_history_list_offset = 0; nav_params.pending_history_list_offset = 1; nav_params.page_id = -1; nav_params.frame_to_navigate = "frame"; nav_params.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(nav_params); FrameLoadWaiter( RenderFrame::FromWebFrame(frame()->GetWebFrame()->firstChild())).Wait(); // Copy the document content to std::wstring and compare with the // expected result. const int kMaxOutputCharacters = 256; std::string output = base::UTF16ToUTF8( GetMainFrame()->contentAsText(kMaxOutputCharacters)); EXPECT_EQ(output, "hello \n\nworld"); } // This test ensures that a RenderFrame object is created for the top level // frame in the RenderView. TEST_F(RenderViewImplTest, BasicRenderFrame) { EXPECT_TRUE(view()->main_render_frame_.get()); } TEST_F(RenderViewImplTest, GetSSLStatusOfFrame) { LoadHTML(""); WebLocalFrame* frame = GetMainFrame(); SSLStatus ssl_status = view()->GetSSLStatusOfFrame(frame); EXPECT_FALSE(net::IsCertStatusError(ssl_status.cert_status)); const_cast(frame->dataSource()->response()). setSecurityInfo( SerializeSecurityInfo(0, net::CERT_STATUS_ALL_ERRORS, 0, 0, SignedCertificateTimestampIDStatusList())); ssl_status = view()->GetSSLStatusOfFrame(frame); EXPECT_TRUE(net::IsCertStatusError(ssl_status.cert_status)); } TEST_F(RenderViewImplTest, MessageOrderInDidChangeSelection) { view()->OnSetInputMethodActive(true); view()->set_send_content_state_immediately(true); LoadHTML(""); view()->handling_input_event_ = true; ExecuteJavaScript("document.getElementById('test').focus();"); bool is_input_type_called = false; bool is_selection_called = false; size_t last_input_type = 0; size_t last_selection = 0; for (size_t i = 0; i < render_thread_->sink().message_count(); ++i) { const uint32 type = render_thread_->sink().GetMessageAt(i)->type(); if (type == ViewHostMsg_TextInputTypeChanged::ID) { is_input_type_called = true; last_input_type = i; } else if (type == ViewHostMsg_SelectionChanged::ID) { is_selection_called = true; last_selection = i; } } EXPECT_TRUE(is_input_type_called); EXPECT_TRUE(is_selection_called); // InputTypeChange shold be called earlier than SelectionChanged. EXPECT_LT(last_input_type, last_selection); } class SuppressErrorPageTest : public RenderViewTest { public: ContentRendererClient* CreateContentRendererClient() override { return new TestContentRendererClient; } RenderViewImpl* view() { return static_cast(view_); } RenderFrameImpl* frame() { return static_cast(view()->GetMainRenderFrame()); } private: class TestContentRendererClient : public ContentRendererClient { public: bool ShouldSuppressErrorPage(RenderFrame* render_frame, const GURL& url) override { return url == GURL("http://example.com/suppress"); } void GetNavigationErrorStrings(content::RenderView* render_view, blink::WebFrame* frame, const blink::WebURLRequest& failed_request, const blink::WebURLError& error, std::string* error_html, base::string16* error_description) override { if (error_html) *error_html = "A suffusion of yellow."; } }; }; #if defined(OS_ANDROID) // Crashing on Android: http://crbug.com/311341 #define MAYBE_Suppresses DISABLED_Suppresses #else #define MAYBE_Suppresses Suppresses #endif TEST_F(SuppressErrorPageTest, MAYBE_Suppresses) { WebURLError error; error.domain = WebString::fromUTF8(net::kErrorDomain); error.reason = net::ERR_FILE_NOT_FOUND; error.unreachableURL = GURL("http://example.com/suppress"); WebLocalFrame* web_frame = GetMainFrame(); // Start a load that will reach provisional state synchronously, // but won't complete synchronously. FrameMsg_Navigate_Params params; params.page_id = -1; params.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; params.common_params.url = GURL("data:text/html,test data"); params.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(params); // An error occurred. view()->GetMainRenderFrame()->didFailProvisionalLoad(web_frame, error); const int kMaxOutputCharacters = 22; EXPECT_EQ("", base::UTF16ToASCII(web_frame->contentAsText(kMaxOutputCharacters))); } #if defined(OS_ANDROID) // Crashing on Android: http://crbug.com/311341 #define MAYBE_DoesNotSuppress DISABLED_DoesNotSuppress #else #define MAYBE_DoesNotSuppress DoesNotSuppress #endif TEST_F(SuppressErrorPageTest, MAYBE_DoesNotSuppress) { WebURLError error; error.domain = WebString::fromUTF8(net::kErrorDomain); error.reason = net::ERR_FILE_NOT_FOUND; error.unreachableURL = GURL("http://example.com/dont-suppress"); WebLocalFrame* web_frame = GetMainFrame(); // Start a load that will reach provisional state synchronously, // but won't complete synchronously. FrameMsg_Navigate_Params params; params.page_id = -1; params.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; params.common_params.url = GURL("data:text/html,test data"); params.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(params); // An error occurred. view()->GetMainRenderFrame()->didFailProvisionalLoad(web_frame, error); // The error page itself is loaded asynchronously. FrameLoadWaiter(frame()).Wait(); const int kMaxOutputCharacters = 22; EXPECT_EQ("A suffusion of yellow.", base::UTF16ToASCII(web_frame->contentAsText(kMaxOutputCharacters))); } // Tests if IME API's candidatewindow* events sent from browser are handled // in renderer. TEST_F(RenderViewImplTest, SendCandidateWindowEvents) { // Sends an HTML with an element and scripts to the renderer. // The script handles all 3 of candidatewindow* events for an // InputMethodContext object and once it received 'show', 'update', 'hide' // should appear in the result div. LoadHTML("" "
Result:
" ""); // Fire candidatewindow events. view()->OnCandidateWindowShown(); view()->OnCandidateWindowUpdated(); view()->OnCandidateWindowHidden(); // Retrieve the content and check if it is expected. const int kMaxOutputCharacters = 50; std::string output = base::UTF16ToUTF8( GetMainFrame()->contentAsText(kMaxOutputCharacters)); EXPECT_EQ(output, "\nResult:showupdatehide"); } // Ensure the render view sends favicon url update events correctly. TEST_F(RenderViewImplTest, SendFaviconURLUpdateEvent) { // An event should be sent when a favicon url exists. LoadHTML("" "" "" "" ""); EXPECT_TRUE(render_thread_->sink().GetFirstMessageMatching( ViewHostMsg_UpdateFaviconURL::ID)); render_thread_->sink().ClearMessages(); // An event should not be sent if no favicon url exists. This is an assumption // made by some of Chrome's favicon handling. LoadHTML("" "" "" ""); EXPECT_FALSE(render_thread_->sink().GetFirstMessageMatching( ViewHostMsg_UpdateFaviconURL::ID)); } TEST_F(RenderViewImplTest, FocusElementCallsFocusedNodeChanged) { LoadHTML("" ""); ExecuteJavaScript("document.getElementById('test1').focus();"); const IPC::Message* msg1 = render_thread_->sink().GetFirstMessageMatching( ViewHostMsg_FocusedNodeChanged::ID); EXPECT_TRUE(msg1); ViewHostMsg_FocusedNodeChanged::Param params; ViewHostMsg_FocusedNodeChanged::Read(msg1, ¶ms); EXPECT_TRUE(get<0>(params)); render_thread_->sink().ClearMessages(); ExecuteJavaScript("document.getElementById('test2').focus();"); const IPC::Message* msg2 = render_thread_->sink().GetFirstMessageMatching( ViewHostMsg_FocusedNodeChanged::ID); EXPECT_TRUE(msg2); ViewHostMsg_FocusedNodeChanged::Read(msg2, ¶ms); EXPECT_TRUE(get<0>(params)); render_thread_->sink().ClearMessages(); view()->webview()->clearFocusedElement(); const IPC::Message* msg3 = render_thread_->sink().GetFirstMessageMatching( ViewHostMsg_FocusedNodeChanged::ID); EXPECT_TRUE(msg3); ViewHostMsg_FocusedNodeChanged::Read(msg3, ¶ms); EXPECT_FALSE(get<0>(params)); render_thread_->sink().ClearMessages(); } TEST_F(RenderViewImplTest, ServiceWorkerNetworkProviderSetup) { ServiceWorkerNetworkProvider* provider = NULL; RequestExtraData* extra_data = NULL; // Make sure each new document has a new provider and // that the main request is tagged with the provider's id. LoadHTML("A Document"); ASSERT_TRUE(GetMainFrame()->dataSource()); provider = ServiceWorkerNetworkProvider::FromDocumentState( DocumentState::FromDataSource(GetMainFrame()->dataSource())); ASSERT_TRUE(provider); extra_data = static_cast( GetMainFrame()->dataSource()->request().extraData()); ASSERT_TRUE(extra_data); EXPECT_EQ(extra_data->service_worker_provider_id(), provider->provider_id()); int provider1_id = provider->provider_id(); LoadHTML("New Document B Goes Here"); ASSERT_TRUE(GetMainFrame()->dataSource()); provider = ServiceWorkerNetworkProvider::FromDocumentState( DocumentState::FromDataSource(GetMainFrame()->dataSource())); ASSERT_TRUE(provider); EXPECT_NE(provider1_id, provider->provider_id()); extra_data = static_cast( GetMainFrame()->dataSource()->request().extraData()); ASSERT_TRUE(extra_data); EXPECT_EQ(extra_data->service_worker_provider_id(), provider->provider_id()); // See that subresource requests are also tagged with the provider's id. EXPECT_EQ(frame(), RenderFrameImpl::FromWebFrame(GetMainFrame())); blink::WebURLRequest request(GURL("http://foo.com")); request.setRequestContext(blink::WebURLRequest::RequestContextSubresource); blink::WebURLResponse redirect_response; frame()->willSendRequest(GetMainFrame(), 0, request, redirect_response); extra_data = static_cast(request.extraData()); ASSERT_TRUE(extra_data); EXPECT_EQ(extra_data->service_worker_provider_id(), provider->provider_id()); } TEST_F(RenderViewImplTest, OnSetAccessibilityMode) { ASSERT_EQ(AccessibilityModeOff, frame()->accessibility_mode()); ASSERT_EQ((RendererAccessibility*) NULL, frame()->renderer_accessibility()); frame()->OnSetAccessibilityMode(AccessibilityModeTreeOnly); ASSERT_EQ(AccessibilityModeTreeOnly, frame()->accessibility_mode()); ASSERT_NE((RendererAccessibility*) NULL, frame()->renderer_accessibility()); frame()->OnSetAccessibilityMode(AccessibilityModeOff); ASSERT_EQ(AccessibilityModeOff, frame()->accessibility_mode()); ASSERT_EQ((RendererAccessibility*) NULL, frame()->renderer_accessibility()); frame()->OnSetAccessibilityMode(AccessibilityModeComplete); ASSERT_EQ(AccessibilityModeComplete, frame()->accessibility_mode()); ASSERT_NE((RendererAccessibility*) NULL, frame()->renderer_accessibility()); } TEST_F(RenderViewImplTest, ScreenMetricsEmulation) { LoadHTML(""); blink::WebDeviceEmulationParams params; base::string16 get_width = base::ASCIIToUTF16("Number(window.innerWidth)"); base::string16 get_height = base::ASCIIToUTF16("Number(window.innerHeight)"); int width, height; params.viewSize.width = 327; params.viewSize.height = 415; view()->EnableScreenMetricsEmulation(params); EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(get_width, &width)); EXPECT_EQ(params.viewSize.width, width); EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(get_height, &height)); EXPECT_EQ(params.viewSize.height, height); params.viewSize.width = 1005; params.viewSize.height = 1102; view()->EnableScreenMetricsEmulation(params); EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(get_width, &width)); EXPECT_EQ(params.viewSize.width, width); EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(get_height, &height)); EXPECT_EQ(params.viewSize.height, height); view()->DisableScreenMetricsEmulation(); view()->EnableScreenMetricsEmulation(params); // Don't disable here to test that emulation is being shutdown properly. } // Sanity checks for the Navigation Timing API |navigationStart| override. We // are asserting only most basic constraints, as TimeTicks (passed as the // override) are not comparable with the wall time (returned by the Blink API). TEST_F(RenderViewImplTest, NavigationStartOverride) { // Verify that a navigation that claims to have started at the earliest // possible TimeTicks is indeed reported as one that started before // OnNavigate() is called. base::Time before_navigation = base::Time::Now(); FrameMsg_Navigate_Params early_nav_params; early_nav_params.common_params.url = GURL("data:text/html,
Page
"); early_nav_params.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; early_nav_params.common_params.transition = ui::PAGE_TRANSITION_TYPED; early_nav_params.page_id = -1; early_nav_params.request_params.is_post = true; early_nav_params.commit_params.browser_navigation_start = base::TimeTicks::FromInternalValue(1); frame()->OnNavigate(early_nav_params); ProcessPendingMessages(); base::Time early_nav_reported_start = base::Time::FromDoubleT(GetMainFrame()->performance().navigationStart()); EXPECT_LT(early_nav_reported_start, before_navigation); // Verify that a navigation that claims to have started in the future - 42 // days from now is *not* reported as one that starts in the future; as we // sanitize the override allowing a maximum of ::Now(). FrameMsg_Navigate_Params late_nav_params; late_nav_params.common_params.url = GURL("data:text/html,
Another page
"); late_nav_params.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL; late_nav_params.common_params.transition = ui::PAGE_TRANSITION_TYPED; late_nav_params.page_id = -1; late_nav_params.request_params.is_post = true; late_nav_params.commit_params.browser_navigation_start = base::TimeTicks::Now() + base::TimeDelta::FromDays(42); frame()->OnNavigate(late_nav_params); ProcessPendingMessages(); base::Time after_navigation = base::Time::Now() + base::TimeDelta::FromDays(1); base::Time late_nav_reported_start = base::Time::FromDoubleT(GetMainFrame()->performance().navigationStart()); EXPECT_LE(late_nav_reported_start, after_navigation); } class RenderViewImplInitialSizeTest : public RenderViewImplTest { public: RenderViewImplInitialSizeTest() : RenderViewImplTest(), initial_size_(200, 100) {} protected: scoped_ptr InitialSizeParams() override { scoped_ptr initial_size_params( new ViewMsg_Resize_Params()); initial_size_params->new_size = initial_size_; return initial_size_params.Pass(); } gfx::Size initial_size_; }; TEST_F(RenderViewImplInitialSizeTest, InitialSize) { ASSERT_EQ(initial_size_, view_->GetSize()); ASSERT_EQ(initial_size_, gfx::Size(view_->GetWebView()->size())); } TEST_F(RenderViewImplTest, PreferredSizeZoomed) { LoadHTML("
"); view()->webview()->mainFrame()->setCanHaveScrollbars(false); EnablePreferredSizeMode(); gfx::Size size = GetPreferredSize(); EXPECT_EQ(gfx::Size(400, 400), size); SetZoomLevel(ZoomFactorToZoomLevel(2.0)); size = GetPreferredSize(); EXPECT_EQ(gfx::Size(800, 800), size); } } // namespace content