| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdint.h> |
| |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/debug/leak_annotations.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/gtest_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/unguessable_token.h" |
| #include "build/build_config.h" |
| #include "content/common/renderer.mojom.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/renderer/content_renderer_client.h" |
| #include "content/public/test/frame_load_waiter.h" |
| #include "content/public/test/local_frame_host_interceptor.h" |
| #include "content/public/test/policy_container_utils.h" |
| #include "content/public/test/render_view_test.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/renderer/agent_scheduling_group.h" |
| #include "content/renderer/document_state.h" |
| #include "content/renderer/mojo/blink_interface_registry_impl.h" |
| #include "content/renderer/navigation_state.h" |
| #include "content/renderer/render_frame_impl.h" |
| #include "content/test/frame_host_test_interface.mojom.h" |
| #include "content/test/test_render_frame.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/navigation/navigation_params.h" |
| #include "third_party/blink/public/common/navigation/navigation_params_mojom_traits.h" |
| #include "third_party/blink/public/common/tokens/tokens.h" |
| #include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom.h" |
| #include "third_party/blink/public/mojom/frame/frame_replication_state.mojom.h" |
| #include "third_party/blink/public/mojom/frame/tree_scope_type.mojom.h" |
| #include "third_party/blink/public/mojom/frame/viewport_intersection_state.mojom.h" |
| #include "third_party/blink/public/mojom/widget/platform_widget.mojom.h" |
| #include "third_party/blink/public/mojom/widget/record_content_to_visible_time_request.mojom.h" |
| #include "third_party/blink/public/platform/web_runtime_features.h" |
| #include "third_party/blink/public/platform/web_string.h" |
| #include "third_party/blink/public/platform/web_url.h" |
| #include "third_party/blink/public/platform/web_url_request.h" |
| #include "third_party/blink/public/web/web_frame_widget.h" |
| #include "third_party/blink/public/web/web_history_item.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/public/web/web_view.h" |
| #include "ui/display/screen_info.h" |
| #include "ui/display/screen_infos.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/native_theme/native_theme_features.h" |
| |
| using blink::WebURLRequest; |
| |
| namespace content { |
| |
| namespace { |
| |
| constexpr int32_t kSubframeRouteId = 20; |
| constexpr int32_t kSubframeWidgetRouteId = 21; |
| |
| const char kParentFrameHTML[] = "Parent frame <iframe name='frame'></iframe>"; |
| |
| const char kAutoplayTestOrigin[] = "https://www.google.com"; |
| } // namespace |
| |
| // 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: |
| explicit RenderFrameImplTest( |
| RenderFrameImpl::CreateRenderFrameImplFunction hook_function = nullptr) |
| : RenderViewTest(/*hook_render_frame_creation=*/!hook_function) { |
| if (hook_function) |
| RenderFrameImpl::InstallCreateHook(hook_function); |
| } |
| ~RenderFrameImplTest() override = default; |
| |
| void SetUp() override { |
| blink::WebRuntimeFeatures::EnableOverlayScrollbars( |
| ui::IsOverlayScrollbarEnabled()); |
| RenderViewTest::SetUp(); |
| EXPECT_TRUE(GetMainRenderFrame()->is_main_frame_); |
| |
| IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); |
| |
| LoadHTML(kParentFrameHTML); |
| LoadChildFrame(); |
| } |
| |
| void LoadChildFrame() { |
| mojom::CreateFrameWidgetParamsPtr widget_params = |
| mojom::CreateFrameWidgetParams::New(); |
| widget_params->routing_id = kSubframeWidgetRouteId; |
| widget_params->visual_properties.new_size = gfx::Size(100, 100); |
| widget_params->visual_properties.screen_infos = |
| display::ScreenInfos(display::ScreenInfo()); |
| |
| widget_remote_.reset(); |
| mojo::PendingAssociatedReceiver<blink::mojom::Widget> |
| blink_widget_receiver = |
| widget_remote_.BindNewEndpointAndPassDedicatedReceiver(); |
| |
| mojo::AssociatedRemote<blink::mojom::WidgetHost> blink_widget_host; |
| mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> |
| blink_widget_host_receiver = |
| blink_widget_host.BindNewEndpointAndPassDedicatedReceiver(); |
| |
| widget_params->widget = std::move(blink_widget_receiver); |
| widget_params->widget_host = blink_widget_host.Unbind(); |
| |
| auto frame_replication_state = blink::mojom::FrameReplicationState::New(); |
| frame_replication_state->name = "frame"; |
| frame_replication_state->unique_name = "frame-uniqueName"; |
| |
| auto remote_frame_interfaces = |
| blink::mojom::RemoteFrameInterfacesFromBrowser::New(); |
| mojo::AssociatedRemote<blink::mojom::RemoteFrame> frame; |
| remote_frame_interfaces->frame_receiver = |
| frame.BindNewEndpointAndPassDedicatedReceiver(); |
| |
| mojo::AssociatedRemote<blink::mojom::RemoteFrameHost> frame_host; |
| std::ignore = frame_host.BindNewEndpointAndPassDedicatedReceiver(); |
| remote_frame_interfaces->frame_host = frame_host.Unbind(); |
| |
| auto remote_main_frame_interfaces = |
| blink::mojom::RemoteMainFrameInterfaces::New(); |
| mojo::AssociatedRemote<blink::mojom::RemoteMainFrame> main_frame; |
| remote_main_frame_interfaces->main_frame = |
| main_frame.BindNewEndpointAndPassDedicatedReceiver(); |
| |
| mojo::AssociatedRemote<blink::mojom::RemoteMainFrameHost> main_frame_host; |
| std::ignore = main_frame_host.BindNewEndpointAndPassDedicatedReceiver(); |
| remote_main_frame_interfaces->main_frame_host = main_frame_host.Unbind(); |
| |
| blink::RemoteFrameToken remote_child_token = blink::RemoteFrameToken(); |
| RenderFrameImpl::FromWebFrame( |
| GetMainRenderFrame()->GetWebFrame()->FirstChild()) |
| ->Unload(false, frame_replication_state->Clone(), remote_child_token, |
| std::move(remote_frame_interfaces), |
| std::move(remote_main_frame_interfaces)); |
| MockPolicyContainerHost mock_policy_container_host; |
| RenderFrameImpl::CreateFrame( |
| *agent_scheduling_group_, blink::LocalFrameToken(), kSubframeRouteId, |
| TestRenderFrame::CreateStubFrameReceiver(), |
| TestRenderFrame::CreateStubBrowserInterfaceBrokerRemote(), |
| TestRenderFrame::CreateStubAssociatedInterfaceProviderRemote(), |
| /*previous_frame_token=*/absl::nullopt, |
| /*opener_frame_token=*/absl::nullopt, |
| /*parent_frame_token=*/remote_child_token, |
| /*previous_sibling_frame_token=*/absl::nullopt, |
| base::UnguessableToken::Create(), |
| blink::mojom::TreeScopeType::kDocument, |
| std::move(frame_replication_state), std::move(widget_params), |
| blink::mojom::FrameOwnerProperties::New(), |
| /*has_committed_real_load=*/true, blink::DocumentToken(), |
| blink::mojom::PolicyContainer::New( |
| blink::mojom::PolicyContainerPolicies::New(), |
| mock_policy_container_host |
| .BindNewEndpointAndPassDedicatedRemote())); |
| |
| frame_ = static_cast<TestRenderFrame*>( |
| 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(); |
| } |
| |
| TestRenderFrame* GetMainRenderFrame() { |
| return static_cast<TestRenderFrame*>(RenderViewTest::GetMainRenderFrame()); |
| } |
| |
| TestRenderFrame* frame() { return frame_; } |
| |
| blink::WebFrameWidget* frame_widget() const { |
| return frame_->GetLocalRootWebFrameWidget(); |
| } |
| |
| mojo::AssociatedRemote<blink::mojom::Widget>& widget_remote() { |
| return widget_remote_; |
| } |
| |
| static url::Origin GetOriginForFrame(TestRenderFrame* frame) { |
| return url::Origin(frame->GetWebFrame()->GetSecurityOrigin()); |
| } |
| |
| static int32_t AutoplayFlagsForFrame(TestRenderFrame* frame) { |
| return frame->GetWebView()->AutoplayFlagsForTest(); |
| } |
| |
| private: |
| TestRenderFrame* frame_; |
| mojo::AssociatedRemote<blink::mojom::Widget> widget_remote_; |
| }; |
| |
| class RenderFrameTestObserver : public RenderFrameObserver { |
| public: |
| explicit RenderFrameTestObserver(RenderFrame* render_frame) |
| : RenderFrameObserver(render_frame), |
| visible_(false), |
| last_intersection_rect_(-1, -1, -1, -1) {} |
| |
| ~RenderFrameTestObserver() override {} |
| |
| // RenderFrameObserver implementation. |
| void WasShown() override { visible_ = true; } |
| void WasHidden() override { visible_ = false; } |
| void OnDestruct() override { delete this; } |
| void OnMainFrameIntersectionChanged( |
| const gfx::Rect& intersection_rect) override { |
| last_intersection_rect_ = intersection_rect; |
| } |
| void OnMainFrameViewportRectangleChanged( |
| const gfx::Rect& viewport_rect) override { |
| last_viewport_rect_ = viewport_rect; |
| } |
| |
| bool visible() const { return visible_; } |
| gfx::Rect last_intersection_rect() const { return last_intersection_rect_; } |
| gfx::Rect last_viewport_rect() const { return last_viewport_rect_; } |
| |
| private: |
| bool visible_; |
| gfx::Rect last_intersection_rect_; |
| gfx::Rect last_viewport_rect_; |
| }; |
| |
| // Verify that a frame with a WebRemoteFrame as a parent has its own |
| // RenderWidget. |
| TEST_F(RenderFrameImplTest, SubframeWidget) { |
| EXPECT_TRUE(frame_widget()); |
| |
| RenderFrameImpl* main_frame = GetMainRenderFrame(); |
| blink::WebFrameWidget* main_frame_widget = |
| main_frame->GetLocalRootWebFrameWidget(); |
| EXPECT_NE(frame_widget(), main_frame_widget); |
| } |
| |
| // Verify a subframe RenderWidget properly processes its viewport being |
| // resized. |
| TEST_F(RenderFrameImplTest, FrameResize) { |
| // Make an update where the widget's size and the visible_viewport_size |
| // are not the same. |
| blink::VisualProperties visual_properties; |
| visual_properties.screen_infos = display::ScreenInfos(display::ScreenInfo()); |
| gfx::Size widget_size(400, 200); |
| gfx::Size visible_size(350, 170); |
| visual_properties.new_size = widget_size; |
| visual_properties.compositor_viewport_pixel_rect = gfx::Rect(widget_size); |
| visual_properties.visible_viewport_size = visible_size; |
| |
| blink::WebFrameWidget* main_frame_widget = |
| GetMainRenderFrame()->GetLocalRootWebFrameWidget(); |
| |
| // The main frame's widget will receive the resize message before the |
| // subframe's widget, and it will set the size for the WebView. |
| main_frame_widget->ApplyVisualProperties(visual_properties); |
| // The main frame widget's size is the "widget size", not the visible viewport |
| // size, which is given to blink separately. |
| EXPECT_EQ(gfx::Size(web_view_->MainFrameWidget()->Size()), widget_size); |
| EXPECT_EQ(gfx::SizeF(web_view_->VisualViewportSize()), |
| gfx::SizeF(visible_size)); |
| // The main frame doesn't change other local roots directly. |
| EXPECT_NE(gfx::Size(frame_widget()->Size()), visible_size); |
| |
| // A subframe in the same process does not modify the WebView. |
| frame_widget()->ApplyVisualProperties(visual_properties); |
| EXPECT_EQ(gfx::Size(frame_widget()->Size()), widget_size); |
| |
| // A subframe in another process would use the |visible_viewport_size| as its |
| // size. |
| } |
| |
| // Verify a subframe RenderWidget properly processes a WasShown message. |
| TEST_F(RenderFrameImplTest, FrameWasShown) { |
| RenderFrameTestObserver observer(frame()); |
| |
| widget_remote()->WasShown( |
| /* was_evicted=*/false, |
| blink::mojom::RecordContentToVisibleTimeRequestPtr()); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(frame_widget()->IsHidden()); |
| EXPECT_TRUE(observer.visible()); |
| } |
| |
| namespace { |
| class DownloadURLMockLocalFrameHost : public LocalFrameHostInterceptor { |
| public: |
| explicit DownloadURLMockLocalFrameHost( |
| blink::AssociatedInterfaceProvider* provider) |
| : LocalFrameHostInterceptor(provider) {} |
| |
| MOCK_METHOD3(RunModalAlertDialog, |
| void(const std::u16string& alert_message, |
| bool disable_third_party_subframe_suppresion, |
| RunModalAlertDialogCallback callback)); |
| MOCK_METHOD1(DownloadURL, void(blink::mojom::DownloadURLParamsPtr params)); |
| }; |
| |
| class DownloadURLTestRenderFrame : public TestRenderFrame { |
| public: |
| static RenderFrameImpl* CreateTestRenderFrame( |
| RenderFrameImpl::CreateParams params) { |
| return new DownloadURLTestRenderFrame(std::move(params)); |
| } |
| |
| ~DownloadURLTestRenderFrame() override = default; |
| |
| blink::AssociatedInterfaceProvider* GetRemoteAssociatedInterfaces() override { |
| blink::AssociatedInterfaceProvider* associated_interface_provider = |
| RenderFrameImpl::GetRemoteAssociatedInterfaces(); |
| |
| // Attach our fake local frame host at the very first call to |
| // GetRemoteAssociatedInterfaces. |
| if (!local_frame_host_) { |
| local_frame_host_ = std::make_unique<DownloadURLMockLocalFrameHost>( |
| associated_interface_provider); |
| } |
| return associated_interface_provider; |
| } |
| |
| DownloadURLMockLocalFrameHost* download_url_mock_local_frame_host() { |
| return local_frame_host_.get(); |
| } |
| |
| private: |
| explicit DownloadURLTestRenderFrame(RenderFrameImpl::CreateParams params) |
| : TestRenderFrame(std::move(params)) {} |
| |
| std::unique_ptr<DownloadURLMockLocalFrameHost> local_frame_host_; |
| }; |
| } // namespace |
| |
| class RenderViewImplDownloadURLTest : public RenderFrameImplTest { |
| public: |
| RenderViewImplDownloadURLTest() |
| : RenderFrameImplTest( |
| &DownloadURLTestRenderFrame::CreateTestRenderFrame) {} |
| |
| DownloadURLMockLocalFrameHost* download_url_mock_local_frame_host() { |
| return static_cast<DownloadURLTestRenderFrame*>(frame()) |
| ->download_url_mock_local_frame_host(); |
| } |
| }; |
| |
| // Tests that url download are throttled when reaching the limit. |
| TEST_F(RenderViewImplDownloadURLTest, DownloadUrlLimit) { |
| WebURLRequest request; |
| request.SetUrl(GURL("http://test/test.pdf")); |
| request.SetRequestorOrigin( |
| blink::WebSecurityOrigin::Create(GURL("http://test"))); |
| |
| EXPECT_CALL(*download_url_mock_local_frame_host(), DownloadURL(testing::_)) |
| .Times(10); |
| for (int i = 0; i < 10; ++i) { |
| frame()->GetWebFrame()->DownloadURL( |
| request, network::mojom::RedirectMode::kManual, mojo::NullRemote()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| EXPECT_CALL(*download_url_mock_local_frame_host(), DownloadURL(testing::_)) |
| .Times(0); |
| frame()->GetWebFrame()->DownloadURL( |
| request, network::mojom::RedirectMode::kManual, mojo::NullRemote()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // 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) { |
| frame()->GetWebFrame()->FindForTesting( |
| 1, "foo", true /* match_case */, true /* forward */, |
| true /* new_session */, true /* force */, false /* wrap_within_frame */, |
| false /* async */); |
| |
| static_cast<mojom::Frame*>(frame())->Delete( |
| mojom::FrameDeleteIntention::kNotMainFrame); |
| } |
| |
| TEST_F(RenderFrameImplTest, AutoplayFlags) { |
| // Add autoplay flags to the page. |
| GetMainRenderFrame()->AddAutoplayFlags( |
| url::Origin::Create(GURL(kAutoplayTestOrigin)), |
| blink::mojom::kAutoplayFlagHighMediaEngagement); |
| |
| // Navigate the top frame. |
| LoadHTMLWithUrlOverride(kParentFrameHTML, kAutoplayTestOrigin); |
| |
| // Check the flags have been set correctly. |
| EXPECT_EQ(blink::mojom::kAutoplayFlagHighMediaEngagement, |
| AutoplayFlagsForFrame(GetMainRenderFrame())); |
| |
| // Navigate the child frame. |
| LoadChildFrame(); |
| |
| // Check the flags are set on both frames. |
| EXPECT_EQ(blink::mojom::kAutoplayFlagHighMediaEngagement, |
| AutoplayFlagsForFrame(GetMainRenderFrame())); |
| EXPECT_EQ(blink::mojom::kAutoplayFlagHighMediaEngagement, |
| AutoplayFlagsForFrame(frame())); |
| |
| // Navigate the top frame. |
| LoadHTMLWithUrlOverride(kParentFrameHTML, "https://www.example.com"); |
| LoadChildFrame(); |
| |
| // Check the flags have been cleared. |
| EXPECT_EQ(blink::mojom::kAutoplayFlagNone, |
| AutoplayFlagsForFrame(GetMainRenderFrame())); |
| EXPECT_EQ(blink::mojom::kAutoplayFlagNone, AutoplayFlagsForFrame(frame())); |
| } |
| |
| TEST_F(RenderFrameImplTest, AutoplayFlags_WrongOrigin) { |
| // Add autoplay flags to the page. |
| GetMainRenderFrame()->AddAutoplayFlags( |
| url::Origin(), blink::mojom::kAutoplayFlagHighMediaEngagement); |
| |
| // Navigate the top frame. |
| LoadHTMLWithUrlOverride(kParentFrameHTML, kAutoplayTestOrigin); |
| |
| // Check the flags have been not been set. |
| EXPECT_EQ(blink::mojom::kAutoplayFlagNone, |
| AutoplayFlagsForFrame(GetMainRenderFrame())); |
| } |
| |
| TEST_F(RenderFrameImplTest, FileUrlPathAlias) { |
| const struct { |
| const char* original; |
| const char* transformed; |
| } kTestCases[] = { |
| {"file:///alias", "file:///replacement"}, |
| {"file:///alias/path/to/file", "file:///replacement/path/to/file"}, |
| {"file://alias/path/to/file", "file://alias/path/to/file"}, |
| {"file:///notalias/path/to/file", "file:///notalias/path/to/file"}, |
| {"file:///root/alias/path/to/file", "file:///root/alias/path/to/file"}, |
| {"file:///", "file:///"}, |
| }; |
| base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( |
| switches::kFileUrlPathAlias, "/alias=/replacement"); |
| |
| for (const auto& test_case : kTestCases) { |
| WebURLRequest request; |
| request.SetUrl(GURL(test_case.original)); |
| GetMainRenderFrame()->WillSendRequest( |
| request, blink::WebLocalFrameClient::ForRedirect(false)); |
| EXPECT_EQ(test_case.transformed, request.Url().GetString().Utf8()); |
| } |
| } |
| |
| TEST_F(RenderFrameImplTest, MainFrameIntersectionRecorded) { |
| RenderFrameTestObserver observer(frame()); |
| gfx::Rect mainframe_intersection(0, 0, 200, 140); |
| frame()->OnMainFrameIntersectionChanged(mainframe_intersection); |
| // Setting a new frame intersection in a local frame triggers the render frame |
| // observer call. |
| EXPECT_EQ(observer.last_intersection_rect(), mainframe_intersection); |
| } |
| |
| TEST_F(RenderFrameImplTest, MainFrameViewportRectRecorded) { |
| RenderFrameTestObserver observer(GetMainRenderFrame()); |
| gfx::Rect mainframe_viewport(0, 0, 200, 140); |
| GetMainRenderFrame()->OnMainFrameViewportRectangleChanged(mainframe_viewport); |
| EXPECT_EQ(observer.last_viewport_rect(), mainframe_viewport); |
| |
| // After a navigation, the notification of `mainframe_viewport` should be |
| // propagated to `RenderFrameTestObserver` again for the new document. |
| LoadHTML(kParentFrameHTML); |
| RenderFrameTestObserver observer2(GetMainRenderFrame()); |
| GetMainRenderFrame()->OnMainFrameViewportRectangleChanged(mainframe_viewport); |
| EXPECT_EQ(observer2.last_viewport_rect(), mainframe_viewport); |
| } |
| |
| // Used to annotate the source of an interface request. |
| struct SourceAnnotation { |
| // The URL of the active document in the frame, at the time the interface was |
| // requested by the RenderFrame. |
| GURL document_url; |
| |
| // The RenderFrameObserver event in response to which the interface is |
| // requested by the RenderFrame. |
| std::string render_frame_event; |
| |
| bool operator==(const SourceAnnotation& rhs) const { |
| return document_url == rhs.document_url && |
| render_frame_event == rhs.render_frame_event; |
| } |
| bool operator!=(const SourceAnnotation& rhs) const { return !(*this == rhs); } |
| }; |
| |
| std::ostream& operator<<(std::ostream& out, const SourceAnnotation& s) { |
| out << s.document_url.possibly_invalid_spec() << " : " |
| << s.render_frame_event; |
| return out; |
| } |
| |
| // RenderFrameRemoteInterfacesTest ------------------------------------ |
| |
| namespace { |
| |
| constexpr char kTestFirstURL[] = "http://foo.com/1"; |
| constexpr char kTestSecondURL[] = "http://foo.com/2"; |
| // constexpr char kTestCrossOriginURL[] = "http://bar.com/"; |
| constexpr char kAboutBlankURL[] = "about:blank"; |
| |
| constexpr char kFrameEventDidCreateNewFrame[] = "did-create-new-frame"; |
| constexpr char kFrameEventDidCreateNewDocument[] = "did-create-new-document"; |
| constexpr char kFrameEventDidCreateDocumentElement[] = |
| "did-create-document-element"; |
| constexpr char kFrameEventReadyToCommitNavigation[] = |
| "ready-to-commit-navigation"; |
| constexpr char kFrameEventDidCommitProvisionalLoad[] = |
| "did-commit-provisional-load"; |
| constexpr char kFrameEventDidCommitSameDocumentLoad[] = |
| "did-commit-same-document-load"; |
| constexpr char kFrameEventAfterCommit[] = "after-commit"; |
| |
| constexpr char kNoDocumentMarkerURL[] = "data:,No document."; |
| |
| class TestSimpleBrowserInterfaceBrokerImpl |
| : public blink::mojom::BrowserInterfaceBroker { |
| public: |
| using BinderCallback = |
| base::RepeatingCallback<void(mojo::ScopedMessagePipeHandle)>; |
| |
| // Incoming interface requests for |interface_name| will invoke |binder|. |
| // Everything else is ignored. |
| TestSimpleBrowserInterfaceBrokerImpl(const std::string& interface_name, |
| BinderCallback binder_callback) |
| : receiver_(this), |
| interface_name_(interface_name), |
| binder_callback_(binder_callback) {} |
| |
| TestSimpleBrowserInterfaceBrokerImpl( |
| const TestSimpleBrowserInterfaceBrokerImpl&) = delete; |
| TestSimpleBrowserInterfaceBrokerImpl& operator=( |
| const TestSimpleBrowserInterfaceBrokerImpl&) = delete; |
| |
| void BindAndFlush( |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> receiver) { |
| ASSERT_FALSE(receiver_.is_bound()); |
| receiver_.Bind(std::move(receiver)); |
| receiver_.FlushForTesting(); |
| } |
| |
| private: |
| // blink::mojom::BrowserInterfaceBroker: |
| void GetInterface(mojo::GenericPendingReceiver receiver) override { |
| if (receiver.interface_name().value() == interface_name_) |
| binder_callback_.Run(receiver.PassPipe()); |
| } |
| |
| mojo::Receiver<blink::mojom::BrowserInterfaceBroker> receiver_; |
| |
| std::string interface_name_; |
| BinderCallback binder_callback_; |
| }; |
| |
| class FrameHostTestInterfaceImpl : public mojom::FrameHostTestInterface { |
| public: |
| FrameHostTestInterfaceImpl() = default; |
| |
| FrameHostTestInterfaceImpl(const FrameHostTestInterfaceImpl&) = delete; |
| FrameHostTestInterfaceImpl& operator=(const FrameHostTestInterfaceImpl&) = |
| delete; |
| |
| ~FrameHostTestInterfaceImpl() override {} |
| |
| void BindAndFlush( |
| mojo::PendingReceiver<mojom::FrameHostTestInterface> receiver) { |
| receiver_.Bind(std::move(receiver)); |
| receiver_.WaitForIncomingCall(); |
| } |
| |
| const absl::optional<SourceAnnotation>& ping_source() const { |
| return ping_source_; |
| } |
| |
| protected: |
| void Ping(const GURL& url, const std::string& event) override { |
| ping_source_ = SourceAnnotation{url, event}; |
| } |
| |
| private: |
| mojo::Receiver<mojom::FrameHostTestInterface> receiver_{this}; |
| absl::optional<SourceAnnotation> ping_source_; |
| }; |
| |
| // RenderFrameObserver that issues FrameHostTestInterface interface requests |
| // through the RenderFrame's |remote_interfaces_| in response to observing |
| // important milestones in a frame's lifecycle. |
| class FrameHostTestInterfaceRequestIssuer : public RenderFrameObserver { |
| public: |
| explicit FrameHostTestInterfaceRequestIssuer(RenderFrame* render_frame) |
| : RenderFrameObserver(render_frame) {} |
| |
| FrameHostTestInterfaceRequestIssuer( |
| const FrameHostTestInterfaceRequestIssuer&) = delete; |
| FrameHostTestInterfaceRequestIssuer& operator=( |
| const FrameHostTestInterfaceRequestIssuer&) = delete; |
| |
| void RequestTestInterfaceOnFrameEvent(const std::string& event) { |
| mojo::Remote<mojom::FrameHostTestInterface> remote; |
| blink::WebDocument document = render_frame()->GetWebFrame()->GetDocument(); |
| render_frame()->GetBrowserInterfaceBroker()->GetInterface( |
| remote.BindNewPipeAndPassReceiver()); |
| remote->Ping( |
| !document.IsNull() ? GURL(document.Url()) : GURL(kNoDocumentMarkerURL), |
| event); |
| } |
| |
| private: |
| // RenderFrameObserver: |
| void OnDestruct() override {} |
| |
| void DidCreateDocumentElement() override { |
| RequestTestInterfaceOnFrameEvent(kFrameEventDidCreateDocumentElement); |
| } |
| |
| void DidCreateNewDocument() override { |
| RequestTestInterfaceOnFrameEvent(kFrameEventDidCreateNewDocument); |
| } |
| |
| void ReadyToCommitNavigation(blink::WebDocumentLoader* loader) override { |
| RequestTestInterfaceOnFrameEvent(kFrameEventReadyToCommitNavigation); |
| } |
| |
| void DidCommitProvisionalLoad(ui::PageTransition transition) override { |
| RequestTestInterfaceOnFrameEvent(kFrameEventDidCommitProvisionalLoad); |
| } |
| |
| void DidFinishSameDocumentNavigation() override { |
| RequestTestInterfaceOnFrameEvent(kFrameEventDidCommitSameDocumentLoad); |
| } |
| }; |
| |
| // RenderFrameObserver that can be used to wait for the next commit in a frame. |
| class FrameCommitWaiter : public RenderFrameObserver { |
| public: |
| explicit FrameCommitWaiter(RenderFrame* render_frame) |
| : RenderFrameObserver(render_frame) {} |
| |
| FrameCommitWaiter(const FrameCommitWaiter&) = delete; |
| FrameCommitWaiter& operator=(const FrameCommitWaiter&) = delete; |
| |
| void Wait() { |
| if (did_commit_) |
| return; |
| run_loop_.Run(); |
| } |
| |
| private: |
| // RenderFrameObserver: |
| void OnDestruct() override {} |
| |
| void DidCommitProvisionalLoad(ui::PageTransition transition) override { |
| did_commit_ = true; |
| run_loop_.Quit(); |
| } |
| |
| base::RunLoop run_loop_; |
| bool did_commit_ = false; |
| }; |
| |
| // Testing ContentRendererClient implementation that fires the |callback| |
| // whenever a new frame is created. |
| class FrameCreationObservingRendererClient : public ContentRendererClient { |
| public: |
| using FrameCreatedCallback = base::RepeatingCallback<void(TestRenderFrame*)>; |
| |
| FrameCreationObservingRendererClient() {} |
| |
| FrameCreationObservingRendererClient( |
| const FrameCreationObservingRendererClient&) = delete; |
| FrameCreationObservingRendererClient& operator=( |
| const FrameCreationObservingRendererClient&) = delete; |
| |
| ~FrameCreationObservingRendererClient() override {} |
| |
| void set_callback(FrameCreatedCallback callback) { |
| callback_ = std::move(callback); |
| } |
| |
| void reset_callback() { callback_.Reset(); } |
| |
| protected: |
| void RenderFrameCreated(RenderFrame* render_frame) override { |
| ContentRendererClient::RenderFrameCreated(render_frame); |
| if (callback_) |
| callback_.Run(static_cast<TestRenderFrame*>(render_frame)); |
| } |
| |
| private: |
| FrameCreatedCallback callback_; |
| }; |
| |
| // Expects observing the creation of a new frame, and creates an instance of |
| // FrameHostTestInterfaceRequestIssuerRenderFrame for that new frame to exercise |
| // its RemoteInterfaceProvider interface. |
| class ScopedNewFrameInterfaceProviderExerciser { |
| public: |
| explicit ScopedNewFrameInterfaceProviderExerciser( |
| FrameCreationObservingRendererClient* frame_creation_observer, |
| const absl::optional<std::string>& html_override_for_first_load = |
| absl::nullopt) |
| : frame_creation_observer_(frame_creation_observer), |
| html_override_for_first_load_(html_override_for_first_load) { |
| frame_creation_observer_->set_callback(base::BindRepeating( |
| &ScopedNewFrameInterfaceProviderExerciser::OnFrameCreated, |
| base::Unretained(this))); |
| } |
| |
| ScopedNewFrameInterfaceProviderExerciser( |
| const ScopedNewFrameInterfaceProviderExerciser&) = delete; |
| ScopedNewFrameInterfaceProviderExerciser& operator=( |
| const ScopedNewFrameInterfaceProviderExerciser&) = delete; |
| |
| ~ScopedNewFrameInterfaceProviderExerciser() { |
| frame_creation_observer_->reset_callback(); |
| } |
| |
| void ExpectNewFrameAndWaitForLoad(const GURL& expected_loaded_url) { |
| ASSERT_NE(nullptr, frame_); |
| frame_commit_waiter_->Wait(); |
| |
| ASSERT_FALSE(frame_->GetWebFrame()->GetCurrentHistoryItem().IsNull()); |
| ASSERT_FALSE(frame_->GetWebFrame()->GetDocument().IsNull()); |
| EXPECT_EQ(expected_loaded_url, |
| GURL(frame_->GetWebFrame()->GetDocument().Url())); |
| |
| browser_interface_broker_receiver_for_first_document_ = |
| frame_->TakeLastBrowserInterfaceBrokerReceiver(); |
| } |
| |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> |
| browser_interface_broker_receiver_for_initial_empty_document() { |
| return std::move( |
| browser_interface_broker_receiver_for_initial_empty_document_); |
| } |
| |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> |
| browser_interface_broker_receiver_for_first_document() { |
| return std::move(browser_interface_broker_receiver_for_first_document_); |
| } |
| |
| private: |
| void OnFrameCreated(TestRenderFrame* frame) { |
| ASSERT_EQ(nullptr, frame_); |
| frame_ = frame; |
| frame_commit_waiter_.emplace(frame); |
| |
| if (html_override_for_first_load_.has_value()) { |
| frame_->SetHTMLOverrideForNextNavigation( |
| std::move(html_override_for_first_load_).value()); |
| } |
| |
| // The FrameHostTestInterfaceRequestIssuer needs to stay alive even after |
| // this method returns, so that it continues to observe RenderFrame |
| // lifecycle events and request test interfaces in response. |
| test_request_issuer_.emplace(frame); |
| test_request_issuer_->RequestTestInterfaceOnFrameEvent( |
| kFrameEventDidCreateNewFrame); |
| |
| browser_interface_broker_receiver_for_initial_empty_document_ = |
| frame_->TakeLastBrowserInterfaceBrokerReceiver(); |
| EXPECT_TRUE(frame->GetWebFrame()->GetCurrentHistoryItem().IsNull()); |
| } |
| |
| FrameCreationObservingRendererClient* frame_creation_observer_; |
| TestRenderFrame* frame_ = nullptr; |
| absl::optional<std::string> html_override_for_first_load_; |
| GURL first_committed_url_; |
| |
| absl::optional<FrameCommitWaiter> frame_commit_waiter_; |
| absl::optional<FrameHostTestInterfaceRequestIssuer> test_request_issuer_; |
| |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> |
| browser_interface_broker_receiver_for_initial_empty_document_; |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> |
| browser_interface_broker_receiver_for_first_document_; |
| }; |
| |
| // Extracts all interface receivers for FrameHostTestInterface pending on the |
| // specified |browser_interface_broker_receiver|, and returns a list of the |
| // source annotations that are provided in the pending Ping() call for each of |
| // these FrameHostTestInterface receivers. |
| void ExpectPendingInterfaceReceiversFromSources( |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> |
| browser_interface_broker_receiver, |
| std::vector<SourceAnnotation> expected_sources) { |
| std::vector<SourceAnnotation> sources; |
| ASSERT_TRUE(browser_interface_broker_receiver.is_valid()); |
| |
| std::vector<SourceAnnotation> browser_interface_broker_sources; |
| ASSERT_TRUE(browser_interface_broker_receiver.is_valid()); |
| TestSimpleBrowserInterfaceBrokerImpl browser_broker( |
| mojom::FrameHostTestInterface::Name_, |
| base::BindLambdaForTesting([&browser_interface_broker_sources]( |
| mojo::ScopedMessagePipeHandle handle) { |
| FrameHostTestInterfaceImpl impl; |
| impl.BindAndFlush(mojo::PendingReceiver<mojom::FrameHostTestInterface>( |
| std::move(handle))); |
| ASSERT_TRUE(impl.ping_source().has_value()); |
| browser_interface_broker_sources.push_back(impl.ping_source().value()); |
| })); |
| browser_broker.BindAndFlush(std::move(browser_interface_broker_receiver)); |
| EXPECT_THAT(browser_interface_broker_sources, |
| ::testing::ElementsAreArray(expected_sources)); |
| } |
| |
| } // namespace |
| |
| class RenderFrameRemoteInterfacesTest : public RenderViewTest { |
| public: |
| RenderFrameRemoteInterfacesTest() { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kAllowContentInitiatedDataUrlNavigations); |
| blink::WebRuntimeFeatures::EnableFeatureFromString( |
| "AllowContentInitiatedDataUrlNavigations", true); |
| } |
| |
| RenderFrameRemoteInterfacesTest(const RenderFrameRemoteInterfacesTest&) = |
| delete; |
| RenderFrameRemoteInterfacesTest& operator=( |
| const RenderFrameRemoteInterfacesTest&) = delete; |
| |
| ~RenderFrameRemoteInterfacesTest() override { |
| blink::WebRuntimeFeatures::EnableFeatureFromString( |
| "AllowContentInitiatedDataUrlNavigations", false); |
| } |
| |
| protected: |
| void SetUp() override { |
| RenderViewTest::SetUp(); |
| LoadHTML("Nothing to see here."); |
| } |
| |
| 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(); |
| } |
| |
| FrameCreationObservingRendererClient* frame_creation_observer() { |
| DCHECK(frame_creation_observer_); |
| return frame_creation_observer_; |
| } |
| |
| TestRenderFrame* GetMainRenderFrame() { |
| return static_cast<TestRenderFrame*>(RenderViewTest::GetMainRenderFrame()); |
| } |
| |
| ContentRendererClient* CreateContentRendererClient() override { |
| frame_creation_observer_ = new FrameCreationObservingRendererClient(); |
| return frame_creation_observer_; |
| } |
| |
| private: |
| // Owned by RenderViewTest. |
| FrameCreationObservingRendererClient* frame_creation_observer_ = nullptr; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Expect that |remote_interfaces_| is bound before the first committed load in |
| // a child frame, and then re-bound on the first commit. |
| TEST_F(RenderFrameRemoteInterfacesTest, ChildFrameAtFirstCommittedLoad) { |
| ScopedNewFrameInterfaceProviderExerciser child_frame_exerciser( |
| frame_creation_observer()); |
| const std::string html = base::StringPrintf("<iframe src=\"%s\"></iframe>", |
| "data:text/html,Child"); |
| LoadHTMLWithUrlOverride(html.c_str(), kTestSecondURL); |
| |
| const GURL child_frame_url("data:text/html,Child"); |
| ASSERT_NO_FATAL_FAILURE( |
| child_frame_exerciser.ExpectNewFrameAndWaitForLoad(child_frame_url)); |
| |
| // TODO(https://crbug.com/792410): It is unfortunate how many internal |
| // details of frame/document creation this encodes. Need to decouple. |
| const GURL initial_empty_url(kAboutBlankURL); |
| ExpectPendingInterfaceReceiversFromSources( |
| child_frame_exerciser |
| .browser_interface_broker_receiver_for_initial_empty_document(), |
| {{initial_empty_url, kFrameEventDidCreateNewFrame}, |
| {child_frame_url, kFrameEventReadyToCommitNavigation}, |
| // TODO(https://crbug.com/555773): It seems strange that the new |
| // document is created and DidCreateNewDocument is invoked *before* the |
| // provisional load would have even committed. |
| {child_frame_url, kFrameEventDidCreateNewDocument}}); |
| ExpectPendingInterfaceReceiversFromSources( |
| child_frame_exerciser |
| .browser_interface_broker_receiver_for_first_document(), |
| {{child_frame_url, kFrameEventDidCommitProvisionalLoad}, |
| {child_frame_url, kFrameEventDidCreateDocumentElement}}); |
| } |
| |
| // Expect that |remote_interfaces_| is bound before the first committed load in |
| // the main frame of an opened window, and then re-bound on the first commit. |
| TEST_F(RenderFrameRemoteInterfacesTest, |
| MainFrameOfOpenedWindowAtFirstCommittedLoad) { |
| const GURL new_window_url("data:text/html,NewWindow"); |
| ScopedNewFrameInterfaceProviderExerciser main_frame_exerciser( |
| frame_creation_observer(), std::string("foo")); |
| const std::string html = |
| base::StringPrintf("<script>window.open(\"%s\", \"_blank\")</script>", |
| "data:text/html,NewWindow"); |
| LoadHTMLWithUrlOverride(html.c_str(), kTestSecondURL); |
| ASSERT_NO_FATAL_FAILURE( |
| main_frame_exerciser.ExpectNewFrameAndWaitForLoad(new_window_url)); |
| |
| // The URL of the initial empty document is "" for opened windows, in |
| // contrast to child frames, where it is "about:blank". See |
| // Document::Document and Document::SetURL for more details. |
| // |
| // Furthermore, for main frames, InitializeCoreFrame is invoked first, and |
| // RenderFrameImpl::Initialize is invoked second, in contrast to child |
| // frames where it is vice versa. ContentRendererClient::RenderFrameCreated |
| // is invoked from RenderFrameImpl::Initialize, so we miss the events |
| // related to initial empty document that is created from |
| // InitializeCoreFrame, and there is already a document when |
| // RenderFrameCreated is invoked. |
| // |
| // TODO(https://crbug.com/792410): It is unfortunate how many internal |
| // details of frame/document creation this encodes. Need to decouple. |
| const GURL initial_empty_url; |
| ExpectPendingInterfaceReceiversFromSources( |
| main_frame_exerciser |
| .browser_interface_broker_receiver_for_initial_empty_document(), |
| {{initial_empty_url, kFrameEventDidCreateNewFrame}, |
| {new_window_url, kFrameEventReadyToCommitNavigation}, |
| {new_window_url, kFrameEventDidCreateNewDocument}}); |
| ExpectPendingInterfaceReceiversFromSources( |
| main_frame_exerciser |
| .browser_interface_broker_receiver_for_first_document(), |
| {{new_window_url, kFrameEventDidCommitProvisionalLoad}, |
| {new_window_url, kFrameEventDidCreateDocumentElement}}); |
| } |
| |
| // Expect that |remote_interfaces_| is not bound to a new pipe if the first |
| // committed load in the child frame has the same security origin as that of the |
| // initial empty document. |
| // |
| // In this case, the LocalDOMWindow object associated with the initial empty |
| // document will be re-used for the newly committed document. Here, we must |
| // continue using the InterfaceProvider connection created for the initial empty |
| // document to support the following use-case: |
| // 1) Parent frame dynamically injects an <iframe>. |
| // 2) The parent frame calls `child.contentDocument.write(...)` to inject |
| // Javascript that may stash objects on the child frame's global object |
| // (LocalDOMWindow). Internally, these objects may be using Mojo services |
| // exposed by the RenderFrameHost. The InterfaceRequests for these services |
| // might still be en-route to the RemnderFrameHost's InterfaceProvider. |
| // 3) The `child` frame commits the first real load, and it is same-origin. |
| // 4) The global object in the child frame's browsing context is re-used. |
| // 5) Javascript objects stashed on the global object should continue to work. |
| // |
| // TODO(japhet?): javascript: urls are untestable here, because they don't |
| // go through the normal commit pipeline. If we were to give javascript: urls |
| // their own DocumentLoader in blink and model them as a real navigation, we |
| // should add a test case here. |
| // TODO(crbug.com/718652): when all clients are converted to use |
| // BrowserInterfaceBroker, PendingReceiver<InterfaceProvider>-related code will |
| // be removed. |
| TEST_F(RenderFrameRemoteInterfacesTest, |
| ChildFrameReusingWindowOfInitialDocument) { |
| const GURL main_frame_url(kTestFirstURL); |
| const GURL initial_empty_url(kAboutBlankURL); |
| |
| constexpr const char* kTestCases[] = {kTestSecondURL, kAboutBlankURL}; |
| |
| for (const char* test_case : kTestCases) { |
| SCOPED_TRACE(::testing::Message() << "child_frame_url = " << test_case); |
| |
| // Override the URL for the first navigation in the newly created frame to |
| // |child_frame_url|. |
| const GURL child_frame_url(test_case); |
| ScopedNewFrameInterfaceProviderExerciser child_frame_exerciser( |
| frame_creation_observer(), std::string("foo")); |
| |
| std::string html = "<iframe src='" + child_frame_url.spec() + "'></iframe>"; |
| LoadHTMLWithUrlOverride(html.c_str(), main_frame_url.spec().c_str()); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| child_frame_exerciser.ExpectNewFrameAndWaitForLoad(child_frame_url)); |
| |
| ExpectPendingInterfaceReceiversFromSources( |
| child_frame_exerciser |
| .browser_interface_broker_receiver_for_initial_empty_document(), |
| {{initial_empty_url, kFrameEventDidCreateNewFrame}, |
| {child_frame_url, kFrameEventReadyToCommitNavigation}, |
| {child_frame_url, kFrameEventDidCreateNewDocument}, |
| {child_frame_url, kFrameEventDidCommitProvisionalLoad}, |
| {child_frame_url, kFrameEventDidCreateDocumentElement}}); |
| |
| auto browser_interface_broker_receiver = |
| child_frame_exerciser |
| .browser_interface_broker_receiver_for_first_document(); |
| ASSERT_FALSE(browser_interface_broker_receiver.is_valid()); |
| } |
| } |
| |
| // Expect that |remote_interfaces_| is bound to a new pipe on cross-document |
| // navigations. |
| TEST_F(RenderFrameRemoteInterfacesTest, ReplacedOnNonSameDocumentNavigation) { |
| LoadHTMLWithUrlOverride("", kTestFirstURL); |
| |
| auto browser_interface_broker_receiver_for_first_document = |
| GetMainRenderFrame()->TakeLastBrowserInterfaceBrokerReceiver(); |
| |
| FrameHostTestInterfaceRequestIssuer requester(GetMainRenderFrame()); |
| requester.RequestTestInterfaceOnFrameEvent(kFrameEventAfterCommit); |
| |
| LoadHTMLWithUrlOverride("", kTestSecondURL); |
| |
| auto browser_interface_broker_receiver_for_second_document = |
| GetMainRenderFrame()->TakeLastBrowserInterfaceBrokerReceiver(); |
| |
| ASSERT_TRUE(browser_interface_broker_receiver_for_first_document.is_valid()); |
| |
| ExpectPendingInterfaceReceiversFromSources( |
| std::move(browser_interface_broker_receiver_for_first_document), |
| {{GURL(kTestFirstURL), kFrameEventAfterCommit}, |
| {GURL(kTestSecondURL), kFrameEventReadyToCommitNavigation}, |
| {GURL(kTestSecondURL), kFrameEventDidCreateNewDocument}}); |
| |
| ASSERT_TRUE(browser_interface_broker_receiver_for_second_document.is_valid()); |
| |
| ExpectPendingInterfaceReceiversFromSources( |
| std::move(browser_interface_broker_receiver_for_second_document), |
| {{GURL(kTestSecondURL), kFrameEventDidCommitProvisionalLoad}, |
| {GURL(kTestSecondURL), kFrameEventDidCreateDocumentElement}}); |
| } |
| |
| // Expect that |remote_interfaces_| is not bound to a new pipe on same-document |
| // navigations, i.e. the existing InterfaceProvider connection is continued to |
| // be used. |
| TEST_F(RenderFrameRemoteInterfacesTest, ReusedOnSameDocumentNavigation) { |
| LoadHTMLWithUrlOverride("", kTestFirstURL); |
| |
| auto browser_interface_broker_receiver = |
| GetMainRenderFrame()->TakeLastBrowserInterfaceBrokerReceiver(); |
| |
| FrameHostTestInterfaceRequestIssuer requester(GetMainRenderFrame()); |
| OnSameDocumentNavigation(GetMainFrame(), true /* is_new_navigation */); |
| |
| ASSERT_TRUE(browser_interface_broker_receiver.is_valid()); |
| |
| ExpectPendingInterfaceReceiversFromSources( |
| std::move(browser_interface_broker_receiver), |
| {{GURL(kTestFirstURL), kFrameEventDidCommitSameDocumentLoad}}); |
| } |
| |
| TEST_F(RenderFrameImplTest, LastCommittedUrlForUKM) { |
| // Test the case where we have a data url with a base_url. |
| GURL data_url = GURL("data:text/html,"); |
| auto common_params = blink::CreateCommonNavigationParams(); |
| common_params->url = data_url; |
| common_params->navigation_type = |
| blink::mojom::NavigationType::DIFFERENT_DOCUMENT; |
| common_params->transition = ui::PAGE_TRANSITION_TYPED; |
| common_params->base_url_for_data_url = GURL("about:blank"); |
| auto commit_params = blink::CreateCommitNavigationParams(); |
| auto waiter = std::make_unique<FrameLoadWaiter>(GetMainRenderFrame()); |
| GetMainRenderFrame()->Navigate(std::move(common_params), |
| std::move(commit_params)); |
| waiter->Wait(); |
| EXPECT_EQ(GURL(GetMainRenderFrame()->LastCommittedUrlForUKM()), data_url); |
| |
| // Test the case where we have an unreachable URL. |
| GURL unreachable_url = GURL("http://www.example.com"); |
| waiter = std::make_unique<FrameLoadWaiter>(GetMainRenderFrame()); |
| GetMainRenderFrame()->LoadHTMLStringForTesting( |
| "test", data_url, "UTF-8", unreachable_url, |
| false /* replace_current_item */); |
| waiter->Wait(); |
| EXPECT_EQ(GURL(GetMainRenderFrame()->LastCommittedUrlForUKM()), |
| unreachable_url); |
| |
| // Test the base case, normal load. |
| GURL override_url = GURL("http://example.com"); |
| waiter = std::make_unique<FrameLoadWaiter>(GetMainRenderFrame()); |
| LoadHTMLWithUrlOverride("Test", "http://example.com"); |
| waiter->Wait(); |
| EXPECT_EQ(GURL(GetMainRenderFrame()->LastCommittedUrlForUKM()), override_url); |
| } |
| |
| // Verify that a frame with a pending update is cancelled when a forced update |
| // is sent. |
| TEST_F(RenderFrameImplTest, SendUpdateCancelsPending) { |
| RenderFrameImpl* main_frame = GetMainRenderFrame(); |
| main_frame->StartDelayedSyncTimer(); |
| EXPECT_TRUE(main_frame->delayed_state_sync_timer_.IsRunning()); |
| main_frame->SendUpdateState(); |
| EXPECT_FALSE(main_frame->delayed_state_sync_timer_.IsRunning()); |
| } |
| |
| } // namespace content |