| // Copyright 2015 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 <stdint.h> |
| |
| #include "base/command_line.h" |
| #include "base/debug/leak_annotations.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "content/child/web_url_loader_impl.h" |
| #include "content/common/frame_messages.h" |
| #include "content/common/frame_owner_properties.h" |
| #include "content/common/renderer.mojom.h" |
| #include "content/common/view_messages.h" |
| #include "content/public/common/previews_state.h" |
| #include "content/public/renderer/document_state.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/navigation_state_impl.h" |
| #include "content/renderer/render_frame_impl.h" |
| #include "content/renderer/render_view_impl.h" |
| #include "content/test/fake_compositor_dependencies.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/WebKit/public/platform/WebEffectiveConnectionType.h" |
| #include "third_party/WebKit/public/platform/WebString.h" |
| #include "third_party/WebKit/public/platform/WebURLRequest.h" |
| #include "third_party/WebKit/public/web/WebHistoryItem.h" |
| #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| #include "third_party/WebKit/public/web/WebView.h" |
| |
| using blink::WebString; |
| |
| namespace { |
| const int32_t kSubframeRouteId = 20; |
| const int32_t kSubframeWidgetRouteId = 21; |
| const int32_t kFrameProxyRouteId = 22; |
| } // namespace |
| |
| namespace content { |
| |
| // RenderFrameImplTest creates a RenderFrameImpl that is a child of the |
| // main frame, and has its own RenderWidget. This behaves like an out |
| // of process frame even though it is in the same process as its parent. |
| class RenderFrameImplTest : public RenderViewTest { |
| public: |
| ~RenderFrameImplTest() override {} |
| |
| void SetUp() override { |
| RenderViewTest::SetUp(); |
| EXPECT_TRUE(GetMainRenderFrame()->is_main_frame_); |
| |
| mojom::CreateFrameWidgetParams widget_params; |
| widget_params.routing_id = kSubframeWidgetRouteId; |
| widget_params.hidden = false; |
| |
| IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); |
| |
| LoadHTML("Parent frame <iframe name='frame'></iframe>"); |
| |
| FrameReplicationState frame_replication_state; |
| frame_replication_state.name = "frame"; |
| frame_replication_state.unique_name = "frame-uniqueName"; |
| |
| RenderFrameImpl::FromWebFrame( |
| view_->GetMainRenderFrame()->GetWebFrame()->FirstChild()) |
| ->OnSwapOut(kFrameProxyRouteId, false, frame_replication_state); |
| |
| RenderFrameImpl::CreateFrame( |
| kSubframeRouteId, MSG_ROUTING_NONE, MSG_ROUTING_NONE, |
| kFrameProxyRouteId, MSG_ROUTING_NONE, frame_replication_state, |
| &compositor_deps_, widget_params, FrameOwnerProperties()); |
| |
| frame_ = RenderFrameImpl::FromRoutingID(kSubframeRouteId); |
| EXPECT_FALSE(frame_->is_main_frame_); |
| } |
| |
| void TearDown() override { |
| #if defined(LEAK_SANITIZER) |
| // Do this before shutting down V8 in RenderViewTest::TearDown(). |
| // http://crbug.com/328552 |
| __lsan_do_leak_check(); |
| #endif |
| RenderViewTest::TearDown(); |
| } |
| |
| void SetPreviewsState(RenderFrameImpl* frame, PreviewsState previews_state) { |
| frame->previews_state_ = previews_state; |
| } |
| |
| void SetEffectionConnectionType(RenderFrameImpl* frame, |
| blink::WebEffectiveConnectionType type) { |
| frame->effective_connection_type_ = type; |
| } |
| |
| RenderFrameImpl* GetMainRenderFrame() { |
| return static_cast<RenderFrameImpl*>(view_->GetMainRenderFrame()); |
| } |
| |
| RenderFrameImpl* frame() { return frame_; } |
| |
| content::RenderWidget* frame_widget() const { |
| return frame_->render_widget_.get(); |
| } |
| |
| private: |
| RenderFrameImpl* frame_; |
| FakeCompositorDependencies compositor_deps_; |
| }; |
| |
| class RenderFrameTestObserver : public RenderFrameObserver { |
| public: |
| explicit RenderFrameTestObserver(RenderFrame* render_frame) |
| : RenderFrameObserver(render_frame), visible_(false) {} |
| |
| ~RenderFrameTestObserver() override {} |
| |
| // RenderFrameObserver implementation. |
| void WasShown() override { visible_ = true; } |
| void WasHidden() override { visible_ = false; } |
| void OnDestruct() override { delete this; } |
| |
| bool visible() { return visible_; } |
| |
| private: |
| bool visible_; |
| }; |
| |
| // Verify that a frame with a RenderFrameProxy as a parent has its own |
| // RenderWidget. |
| TEST_F(RenderFrameImplTest, SubframeWidget) { |
| EXPECT_TRUE(frame_widget()); |
| EXPECT_NE(frame_widget(), static_cast<RenderViewImpl*>(view_)->GetWidget()); |
| } |
| |
| // Verify a subframe RenderWidget properly processes its viewport being |
| // resized. |
| TEST_F(RenderFrameImplTest, FrameResize) { |
| ResizeParams resize_params; |
| gfx::Size size(200, 200); |
| resize_params.screen_info = ScreenInfo(); |
| resize_params.new_size = size; |
| resize_params.physical_backing_size = size; |
| resize_params.top_controls_height = 0.f; |
| resize_params.browser_controls_shrink_blink_size = false; |
| resize_params.is_fullscreen_granted = false; |
| |
| ViewMsg_Resize resize_message(0, resize_params); |
| frame_widget()->OnMessageReceived(resize_message); |
| |
| EXPECT_EQ(frame_widget()->GetWebWidget()->Size(), blink::WebSize(size)); |
| } |
| |
| // Verify a subframe RenderWidget properly processes a WasShown message. |
| TEST_F(RenderFrameImplTest, FrameWasShown) { |
| RenderFrameTestObserver observer(frame()); |
| |
| ViewMsg_WasShown was_shown_message(0, true, ui::LatencyInfo()); |
| frame_widget()->OnMessageReceived(was_shown_message); |
| |
| EXPECT_FALSE(frame_widget()->is_hidden()); |
| EXPECT_TRUE(observer.visible()); |
| } |
| |
| // Ensure that a RenderFrameImpl does not crash if the RenderView receives |
| // a WasShown message after the frame's widget has been closed. |
| TEST_F(RenderFrameImplTest, FrameWasShownAfterWidgetClose) { |
| RenderFrameTestObserver observer(frame()); |
| |
| ViewMsg_Close close_message(0); |
| frame_widget()->OnMessageReceived(close_message); |
| |
| ViewMsg_WasShown was_shown_message(0, true, ui::LatencyInfo()); |
| static_cast<RenderViewImpl*>(view_)->OnMessageReceived(was_shown_message); |
| |
| // This test is primarily checking that this case does not crash, but |
| // observers should still be notified. |
| EXPECT_TRUE(observer.visible()); |
| } |
| |
| // Test that LoFi state only updates for new main frame documents. Subframes |
| // inherit from the main frame and should not change at commit time. |
| TEST_F(RenderFrameImplTest, LoFiNotUpdatedOnSubframeCommits) { |
| SetPreviewsState(GetMainRenderFrame(), SERVER_LOFI_ON); |
| SetPreviewsState(frame(), SERVER_LOFI_ON); |
| EXPECT_EQ(SERVER_LOFI_ON, GetMainRenderFrame()->GetPreviewsState()); |
| EXPECT_EQ(SERVER_LOFI_ON, frame()->GetPreviewsState()); |
| |
| blink::WebHistoryItem item; |
| item.Initialize(); |
| |
| // The main frame's and subframe's LoFi states should stay the same on |
| // navigations within the page. |
| frame()->DidNavigateWithinPage(item, blink::kWebStandardCommit, true); |
| EXPECT_EQ(SERVER_LOFI_ON, frame()->GetPreviewsState()); |
| GetMainRenderFrame()->DidNavigateWithinPage(item, blink::kWebStandardCommit, |
| true); |
| EXPECT_EQ(SERVER_LOFI_ON, GetMainRenderFrame()->GetPreviewsState()); |
| |
| // The subframe's LoFi state should not be reset on commit. |
| DocumentState* document_state = |
| DocumentState::FromDataSource(frame()->GetWebFrame()->DataSource()); |
| static_cast<NavigationStateImpl*>(document_state->navigation_state()) |
| ->set_was_within_same_document(false); |
| |
| frame()->DidCommitProvisionalLoad(item, blink::kWebStandardCommit); |
| EXPECT_EQ(SERVER_LOFI_ON, frame()->GetPreviewsState()); |
| |
| // The main frame's LoFi state should be reset to off on commit. |
| document_state = DocumentState::FromDataSource( |
| GetMainRenderFrame()->GetWebFrame()->DataSource()); |
| static_cast<NavigationStateImpl*>(document_state->navigation_state()) |
| ->set_was_within_same_document(false); |
| |
| // Calling didCommitProvisionalLoad is not representative of a full navigation |
| // but serves the purpose of testing the LoFi state logic. |
| GetMainRenderFrame()->DidCommitProvisionalLoad(item, |
| blink::kWebStandardCommit); |
| EXPECT_EQ(PREVIEWS_OFF, GetMainRenderFrame()->GetPreviewsState()); |
| // The subframe would be deleted here after a cross-document navigation. It |
| // happens to be left around in this test because this does not simulate the |
| // frame detach. |
| } |
| |
| // Test that effective connection type only updates for new main frame |
| // documents. |
| TEST_F(RenderFrameImplTest, EffectiveConnectionType) { |
| EXPECT_EQ(blink::WebEffectiveConnectionType::kTypeUnknown, |
| frame()->GetEffectiveConnectionType()); |
| EXPECT_EQ(blink::WebEffectiveConnectionType::kTypeUnknown, |
| GetMainRenderFrame()->GetEffectiveConnectionType()); |
| |
| const struct { |
| blink::WebEffectiveConnectionType type; |
| } tests[] = {{blink::WebEffectiveConnectionType::kTypeUnknown}, |
| {blink::WebEffectiveConnectionType::kType2G}, |
| {blink::WebEffectiveConnectionType::kType4G}}; |
| |
| for (size_t i = 0; i < arraysize(tests); ++i) { |
| SetEffectionConnectionType(GetMainRenderFrame(), tests[i].type); |
| SetEffectionConnectionType(frame(), tests[i].type); |
| |
| EXPECT_EQ(tests[i].type, frame()->GetEffectiveConnectionType()); |
| EXPECT_EQ(tests[i].type, |
| GetMainRenderFrame()->GetEffectiveConnectionType()); |
| |
| blink::WebHistoryItem item; |
| item.Initialize(); |
| |
| // The main frame's and subframe's effective connection type should stay the |
| // same on navigations within the page. |
| frame()->DidNavigateWithinPage(item, blink::kWebStandardCommit, true); |
| EXPECT_EQ(tests[i].type, frame()->GetEffectiveConnectionType()); |
| GetMainRenderFrame()->DidNavigateWithinPage(item, blink::kWebStandardCommit, |
| true); |
| EXPECT_EQ(tests[i].type, frame()->GetEffectiveConnectionType()); |
| |
| // The subframe's effective connection type should not be reset on commit. |
| DocumentState* document_state = |
| DocumentState::FromDataSource(frame()->GetWebFrame()->DataSource()); |
| static_cast<NavigationStateImpl*>(document_state->navigation_state()) |
| ->set_was_within_same_document(false); |
| |
| frame()->DidCommitProvisionalLoad(item, blink::kWebStandardCommit); |
| EXPECT_EQ(tests[i].type, frame()->GetEffectiveConnectionType()); |
| |
| // The main frame's effective connection type should be reset on commit. |
| document_state = DocumentState::FromDataSource( |
| GetMainRenderFrame()->GetWebFrame()->DataSource()); |
| static_cast<NavigationStateImpl*>(document_state->navigation_state()) |
| ->set_was_within_same_document(false); |
| |
| GetMainRenderFrame()->DidCommitProvisionalLoad(item, |
| blink::kWebStandardCommit); |
| EXPECT_EQ(blink::WebEffectiveConnectionType::kTypeUnknown, |
| GetMainRenderFrame()->GetEffectiveConnectionType()); |
| |
| // The subframe would be deleted here after a cross-document navigation. |
| // It happens to be left around in this test because this does not simulate |
| // the frame detach. |
| } |
| } |
| |
| TEST_F(RenderFrameImplTest, SaveImageFromDataURL) { |
| const IPC::Message* msg1 = render_thread_->sink().GetFirstMessageMatching( |
| FrameHostMsg_SaveImageFromDataURL::ID); |
| EXPECT_FALSE(msg1); |
| render_thread_->sink().ClearMessages(); |
| |
| const std::string image_data_url = |
| "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="; |
| |
| frame()->SaveImageFromDataURL(WebString::FromUTF8(image_data_url)); |
| ProcessPendingMessages(); |
| const IPC::Message* msg2 = render_thread_->sink().GetFirstMessageMatching( |
| FrameHostMsg_SaveImageFromDataURL::ID); |
| EXPECT_TRUE(msg2); |
| |
| FrameHostMsg_SaveImageFromDataURL::Param param1; |
| FrameHostMsg_SaveImageFromDataURL::Read(msg2, ¶m1); |
| EXPECT_EQ(std::get<2>(param1), image_data_url); |
| |
| ProcessPendingMessages(); |
| render_thread_->sink().ClearMessages(); |
| |
| const std::string large_data_url(1024 * 1024 * 20 - 1, 'd'); |
| |
| frame()->SaveImageFromDataURL(WebString::FromUTF8(large_data_url)); |
| ProcessPendingMessages(); |
| const IPC::Message* msg3 = render_thread_->sink().GetFirstMessageMatching( |
| FrameHostMsg_SaveImageFromDataURL::ID); |
| EXPECT_TRUE(msg3); |
| |
| FrameHostMsg_SaveImageFromDataURL::Param param2; |
| FrameHostMsg_SaveImageFromDataURL::Read(msg3, ¶m2); |
| EXPECT_EQ(std::get<2>(param2), large_data_url); |
| |
| ProcessPendingMessages(); |
| render_thread_->sink().ClearMessages(); |
| |
| const std::string exceeded_data_url(1024 * 1024 * 20 + 1, 'd'); |
| |
| frame()->SaveImageFromDataURL(WebString::FromUTF8(exceeded_data_url)); |
| ProcessPendingMessages(); |
| const IPC::Message* msg4 = render_thread_->sink().GetFirstMessageMatching( |
| FrameHostMsg_SaveImageFromDataURL::ID); |
| EXPECT_FALSE(msg4); |
| } |
| |
| TEST_F(RenderFrameImplTest, ZoomLimit) { |
| const double kMinZoomLevel = ZoomFactorToZoomLevel(kMinimumZoomFactor); |
| const double kMaxZoomLevel = ZoomFactorToZoomLevel(kMaximumZoomFactor); |
| |
| // 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. |
| CommonNavigationParams common_params; |
| common_params.url = GURL("data:text/html,min_zoomlimit_test"); |
| common_params.navigation_type = FrameMsg_Navigate_Type::DIFFERENT_DOCUMENT; |
| GetMainRenderFrame()->SetHostZoomLevel(common_params.url, kMinZoomLevel); |
| GetMainRenderFrame()->NavigateInternal( |
| common_params, StartNavigationParams(), RequestNavigationParams(), |
| std::unique_ptr<StreamOverrideParameters>()); |
| 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)); |
| common_params.url = GURL("data:text/html,max_zoomlimit_test"); |
| GetMainRenderFrame()->SetHostZoomLevel(common_params.url, kMaxZoomLevel); |
| GetMainRenderFrame()->NavigateInternal( |
| common_params, StartNavigationParams(), RequestNavigationParams(), |
| std::unique_ptr<StreamOverrideParameters>()); |
| ProcessPendingMessages(); |
| EXPECT_DOUBLE_EQ(kMaxZoomLevel, view_->GetWebView()->ZoomLevel()); |
| } |
| |
| // Regression test for crbug.com/692557. It shouldn't crash if we inititate a |
| // text finding, and then delete the frame immediately before the text finding |
| // returns any text match. |
| TEST_F(RenderFrameImplTest, NoCrashWhenDeletingFrameDuringFind) { |
| blink::WebFindOptions options; |
| options.force = true; |
| FrameMsg_Find find_message(0, 1, base::ASCIIToUTF16("foo"), options); |
| frame()->OnMessageReceived(find_message); |
| |
| FrameMsg_Delete delete_message(0); |
| frame()->OnMessageReceived(delete_message); |
| } |
| |
| } // namespace |