| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/renderer_host/page_impl.h" |
| |
| #include "base/command_line.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "content/browser/renderer_host/frame_tree.h" |
| #include "content/browser/renderer_host/frame_tree_node.h" |
| #include "content/browser/renderer_host/navigation_request.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/content_navigation_policy.h" |
| #include "content/public/browser/page.h" |
| #include "content/public/browser/page_user_data.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/back_forward_cache_util.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/mock_web_contents_observer.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/public/test/render_frame_host_test_support.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/public/test/url_loader_interceptor.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| int next_id = 0; |
| |
| // Example class which inherits the PageUserData, all the data is |
| // associated to the lifetime of the page. |
| class Data : public PageUserData<Data> { |
| public: |
| ~Data() override; |
| |
| base::WeakPtr<Data> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); } |
| |
| int unique_id() { return unique_id_; } |
| |
| private: |
| explicit Data(Page& page) : PageUserData(page) { unique_id_ = ++next_id; } |
| |
| friend class content::PageUserData<Data>; |
| |
| int unique_id_; |
| |
| base::WeakPtrFactory<Data> weak_ptr_factory_{this}; |
| |
| PAGE_USER_DATA_KEY_DECL(); |
| }; |
| |
| PAGE_USER_DATA_KEY_IMPL(Data); |
| |
| Data::~Data() { |
| // Both Page and RenderFrameHost should be non-null and valid before Data |
| // deletion, as they will be destroyed after PageUserData destruction. |
| EXPECT_TRUE(&page()); |
| EXPECT_TRUE(&(page().GetMainDocument())); |
| } |
| |
| } // namespace |
| |
| class PageImplTest : public ContentBrowserTest { |
| public: |
| ~PageImplTest() override = default; |
| |
| protected: |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ContentBrowserTest::SetUpOnMainThread(); |
| } |
| |
| WebContentsImpl* web_contents() const { |
| return static_cast<WebContentsImpl*>(shell()->web_contents()); |
| } |
| |
| RenderFrameHostImpl* primary_main_frame_host() { |
| return web_contents()->GetPrimaryFrameTree().root()->current_frame_host(); |
| } |
| |
| PageImpl& page() { return primary_main_frame_host()->GetPage(); } |
| |
| Data* CreateOrGetDataForPage(Page& page) { |
| Data* data = Data::GetOrCreateForPage(page); |
| EXPECT_TRUE(data); |
| return data; |
| } |
| |
| void EnsureEqualPageUserData(Data* data_a, Data* data_b) { |
| EXPECT_EQ(data_a->unique_id(), data_b->unique_id()); |
| } |
| }; |
| |
| class PageImplPrerenderBrowserTest : public PageImplTest { |
| public: |
| PageImplPrerenderBrowserTest() |
| : prerender_helper_( |
| base::BindRepeating(&PageImplPrerenderBrowserTest::GetWebContents, |
| base::Unretained(this))) {} |
| |
| void SetUp() override { |
| prerender_helper_.SetUp(embedded_test_server()); |
| PageImplTest::SetUp(); |
| } |
| |
| content::test::PrerenderTestHelper& prerender_test_helper() { |
| return prerender_helper_; |
| } |
| |
| content::WebContents* GetWebContents() { return web_contents(); } |
| |
| protected: |
| test::PrerenderTestHelper prerender_helper_; |
| }; |
| |
| // Test that Page and PageUserData objects are same for main RenderFrameHosts |
| // and subframes which belong to the same Page. |
| IN_PROC_BROWSER_TEST_F(PageImplTest, AllFramesBelongToTheSamePage) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| |
| // 1) Navigate to a(b). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = primary_main_frame_host(); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); |
| |
| // 2) Check Page for RenderFrameHosts a and b, they both should point to same |
| // Page object. |
| PageImpl& page_a = rfh_a->GetPage(); |
| PageImpl& page_b = rfh_b->GetPage(); |
| EXPECT_EQ(&page_a, &page_b); |
| EXPECT_TRUE(page_a.IsPrimary()); |
| |
| // 3) Check that PageUserData objects for both pages a and b have same |
| // unique_id's. |
| EnsureEqualPageUserData(CreateOrGetDataForPage(page_a), |
| CreateOrGetDataForPage(page_b)); |
| } |
| |
| // Test that Page and PageUserData objects are accessible inside |
| // RenderFrameDeleted callback. |
| IN_PROC_BROWSER_TEST_F(PageImplTest, RenderFrameHostDeleted) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = primary_main_frame_host(); |
| PageImpl& page_a = rfh_a->GetPage(); |
| base::WeakPtr<Data> data = CreateOrGetDataForPage(page_a)->GetWeakPtr(); |
| RenderFrameDeletedObserver delete_rfh_a(rfh_a); |
| |
| // 2) PageUserData associated with page_a should be valid when |
| // RenderFrameDeleted callback is invoked. |
| testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents()); |
| EXPECT_CALL(observer, RenderFrameDeleted(testing::_)) |
| .WillOnce( |
| testing::Invoke([data, rfh_a](RenderFrameHost* render_frame_host) { |
| // Both PageUserData and Page objects should be accessible before |
| // RenderFrameHost deletion. |
| EXPECT_EQ(rfh_a, render_frame_host); |
| DCHECK(&render_frame_host->GetPage()); |
| EXPECT_TRUE(data); |
| })); |
| |
| // Test needs rfh_a to be deleted after navigating but it doesn't happen with |
| // BackForwardCache as it is stored in cache. |
| DisableBackForwardCacheForTesting(web_contents(), |
| BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| // 3) Navigate to B, deleting rfh_a. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| delete_rfh_a.WaitUntilDeleted(); |
| } |
| |
| // Test basic functionality of PageUserData. |
| IN_PROC_BROWSER_TEST_F(PageImplTest, GetCreateAndDeleteUserDataForPage) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| PageImpl& page_a = page(); |
| |
| // 2) Get the Data associated with this Page. It should be null |
| // before creation. |
| Data* data = Data::GetForPage(page_a); |
| EXPECT_FALSE(data); |
| |
| // 3) Create Data and check that GetForPage shouldn't return null |
| // now. |
| Data::CreateForPage(page_a); |
| base::WeakPtr<Data> created_data = Data::GetForPage(page_a)->GetWeakPtr(); |
| EXPECT_TRUE(created_data); |
| |
| // 4) Delete Data and check that GetForPage should return null. |
| Data::DeleteForPage(page_a); |
| EXPECT_FALSE(created_data); |
| EXPECT_FALSE(Data::GetForPage(page_a)); |
| } |
| |
| // Test GetOrCreateForPage API of PageUserData. |
| IN_PROC_BROWSER_TEST_F(PageImplTest, GetOrCreateForPage) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| PageImpl& page_a = page(); |
| |
| // 2) Get the Data associated with this RenderFrameHost. It should be null |
| // before creation. |
| Data* data = Data::GetForPage(page_a); |
| EXPECT_FALSE(data); |
| |
| // 3) |GetOrCreateForPage| should create Data. |
| base::WeakPtr<Data> created_data = |
| Data::GetOrCreateForPage(page_a)->GetWeakPtr(); |
| EXPECT_TRUE(created_data); |
| |
| // 4) Another call to |GetOrCreateForPage| should not create the |
| // new data and the previous data created in 3) should be preserved. |
| Data* new_created_data = Data::GetOrCreateForPage(page_a); |
| EXPECT_TRUE(created_data); |
| EnsureEqualPageUserData(created_data.get(), new_created_data); |
| } |
| |
| // Test that the Page object doesn't change for new subframe RFHs after |
| // subframes does a cross-site or same-site navigation. |
| // |
| // 1) Navigate to A(A1, B). |
| // 2) Navigate cross site A1 to C. |
| // 3) Navigate same site B to B2. |
| IN_PROC_BROWSER_TEST_F(PageImplTest, PageObjectAfterSubframeNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 1) Navigate to A1(A2, B). |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(a,b)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| RenderFrameHostImpl* rfh_a = primary_main_frame_host(); |
| RenderFrameHostImpl* rfh_a2 = rfh_a->child_at(0)->current_frame_host(); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(1)->current_frame_host(); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| |
| // 2) Check that Page for A1, A2, B point to same object. |
| PageImpl& page_a = rfh_a->GetPage(); |
| PageImpl& page_a2 = rfh_a2->GetPage(); |
| PageImpl& page_b = rfh_b->GetPage(); |
| EXPECT_EQ(&page_a, &page_a2); |
| EXPECT_EQ(&page_a2, &page_b); |
| |
| // 3) Navigate subframe cross-site from A2 -> C. |
| GURL url_c = embedded_test_server()->GetURL("c.com", "/title1.html"); |
| EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), url_c)); |
| |
| // 4) Page object of new subframe C should be same as page_a. |
| RenderFrameHostImpl* rfh_c = rfh_a->child_at(0)->current_frame_host(); |
| PageImpl& page_c = rfh_c->GetPage(); |
| EXPECT_EQ(&page_c, &page_a); |
| |
| // 5) Navigate subframe same-site from B -> B2. |
| GURL url_b = embedded_test_server()->GetURL("b.com", "/title2.html"); |
| EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(1), url_b)); |
| |
| // 6) Page object of new subframe B2 should be same as page_a. |
| RenderFrameHostImpl* rfh_b2 = rfh_a->child_at(1)->current_frame_host(); |
| PageImpl& page_b2 = rfh_b2->GetPage(); |
| EXPECT_EQ(&page_b2, &page_a); |
| } |
| |
| // Test that Page and PageUserData object remains the same for pending page |
| // before and after commit. |
| IN_PROC_BROWSER_TEST_F(PageImplTest, PageObjectBeforeAndAfterCommit) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html")); |
| |
| // Isolate "b.com" so we are guaranteed to get a different process |
| // for navigations to this origin on Android. Doing this ensures that a |
| // speculative RenderFrameHost is used. |
| IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(), |
| {"b.com"}); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = primary_main_frame_host(); |
| PageImpl& page_a = rfh_a->GetPage(); |
| base::WeakPtr<Data> data_a = CreateOrGetDataForPage(page_a)->GetWeakPtr(); |
| |
| // 2) Start navigation to B, but don't commit yet. |
| TestNavigationManager manager(shell()->web_contents(), url_b); |
| shell()->LoadURL(url_b); |
| EXPECT_TRUE(manager.WaitForRequestStart()); |
| |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| RenderFrameHostImpl* pending_rfh = |
| root->render_manager()->speculative_frame_host(); |
| NavigationRequest* navigation_request = root->navigation_request(); |
| EXPECT_EQ(navigation_request->associated_rfh_type(), |
| NavigationRequest::AssociatedRenderFrameHostType::SPECULATIVE); |
| EXPECT_TRUE(pending_rfh); |
| |
| // 3) While there is a speculative RenderFrameHost in the root FrameTreeNode, |
| // get the Page associated with this RenderFrameHost and PageUserData |
| // associated with this Page. |
| PageImpl& pending_rfh_page = pending_rfh->GetPage(); |
| EXPECT_NE(&pending_rfh_page, &page_a); |
| EXPECT_TRUE(page_a.IsPrimary()); |
| EXPECT_FALSE(pending_rfh_page.IsPrimary()); |
| base::WeakPtr<Data> data_before_commit = |
| CreateOrGetDataForPage(pending_rfh_page)->GetWeakPtr(); |
| EXPECT_NE(data_before_commit.get()->unique_id(), data_a.get()->unique_id()); |
| |
| // 4) Let the navigation finish and make sure it has succeeded. |
| manager.WaitForNavigationFinished(); |
| EXPECT_EQ(url_b, |
| web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL()); |
| |
| RenderFrameHostImpl* rfh_b = primary_main_frame_host(); |
| EXPECT_EQ(pending_rfh, rfh_b); |
| PageImpl& rfh_b_page = rfh_b->GetPage(); |
| |
| // 5) Check |pending_rfh_page| and |rfh_b_page| point to the same object. |
| EXPECT_EQ(&pending_rfh_page, &rfh_b_page); |
| EXPECT_TRUE(rfh_b_page.IsPrimary()); |
| } |
| |
| // Test that WebContentsObserver::PrimaryPageChanged is invoked on primary page |
| // changes after page related data is updated e.g. LastCommittedURL. |
| IN_PROC_BROWSER_TEST_F(PageImplTest, PrimaryPageChangedOnCrossSiteNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b, c)")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| Page* invoked_page; |
| GURL last_committed_url; |
| int http_status_code; |
| |
| // 2) Invoke MockWebContentsObserver to check the values inside |
| // PrimaryPageChanged(Page&) match the ones inside |
| // DidFinishNavigation(NavigationHandle*). |
| WebContents* contents = web_contents(); |
| testing::NiceMock<MockWebContentsObserver> web_contents_observer(contents); |
| testing::InSequence s; |
| |
| { |
| // 3) Stores the values of page invoked on PrimaryPageChanged and |
| // LastCommittedUrl, HttpStatusCode to match with ones inside |
| // DidFinishNavigation and page after navigation. |
| EXPECT_CALL(web_contents_observer, PrimaryPageChanged(testing::_)) |
| .WillOnce(testing::Invoke([&invoked_page, &last_committed_url, |
| &http_status_code, url_b, this](Page& page) { |
| invoked_page = &page; |
| last_committed_url = page.GetMainDocument().GetLastCommittedURL(); |
| http_status_code = web_contents() |
| ->GetController() |
| .GetVisibleEntry() |
| ->GetHttpStatusCode(); |
| EXPECT_EQ(last_committed_url, url_b); |
| EXPECT_TRUE(page.IsPrimary()); |
| EXPECT_EQ(&web_contents()->GetPrimaryPage(), &page); |
| })); |
| |
| EXPECT_CALL(web_contents_observer, DidFinishNavigation(testing::_)) |
| .WillOnce(testing::Invoke([&last_committed_url, &http_status_code]( |
| NavigationHandle* navigation_handle) { |
| EXPECT_EQ(navigation_handle->GetURL(), last_committed_url); |
| EXPECT_EQ(http_status_code, navigation_handle->GetWebContents() |
| ->GetController() |
| .GetVisibleEntry() |
| ->GetHttpStatusCode()); |
| })); |
| } |
| |
| // 4) Navigate to B. PrimaryPageChanged and DidFinishNavigation should be |
| // triggered for new `page_b` and should match `invoked_page`. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = primary_main_frame_host(); |
| PageImpl& page_b = rfh_b->GetPage(); |
| EXPECT_EQ(&page_b, invoked_page); |
| } |
| |
| // Test that a new Page object is created for a same-site same-RFH navigation. |
| IN_PROC_BROWSER_TEST_F(PageImplTest, SameSiteSameRenderFrameHostNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html")); |
| |
| // 1) Navigate to A1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a1)); |
| RenderFrameHostImplWrapper main_rfh_a1(primary_main_frame_host()); |
| base::WeakPtr<Page> page_a1 = main_rfh_a1->GetPage().GetWeakPtr(); |
| testing::NiceMock<MockWebContentsObserver> page_changed_observer( |
| web_contents()); |
| base::WeakPtr<Data> data = CreateOrGetDataForPage(*page_a1)->GetWeakPtr(); |
| |
| // 2) Navigate to A2. This will result in invoking PrimaryPageChanged |
| // callback. |
| EXPECT_CALL(page_changed_observer, PrimaryPageChanged(testing::_)).Times(1); |
| EXPECT_TRUE(NavigateToURL(shell(), url_a2)); |
| RenderFrameHostImplWrapper main_rfh_a2(primary_main_frame_host()); |
| EXPECT_EQ(CanSameSiteMainFrameNavigationsChangeRenderFrameHosts(), |
| main_rfh_a1.get() != main_rfh_a2.get()); |
| PageImpl& page_a2 = main_rfh_a2.get()->GetPage(); |
| |
| if (IsBackForwardCacheEnabled()) { |
| // 3a) With back/forward cache enabled, both Page objects should be in |
| // existence at the same time. |
| EXPECT_TRUE(page_a1); |
| EXPECT_NE(page_a1.get(), &page_a2); |
| // And the user data associated with the page (now in bfcache) should also |
| // still be alive. |
| EXPECT_TRUE(data); |
| } else { |
| // 3b) Otherwise, check that the old Page object was destroyed. There is no |
| // other way to validate that the old Page and new Page are different: |
| // comparing pointer values is not a stable test, since the new Page could |
| // be reallocated at the same address. |
| EXPECT_FALSE(page_a1); |
| // Similarly, expect any PageUserData from the old Page to be gone. |
| EXPECT_FALSE(data); |
| } |
| } |
| |
| // Test that a new Page object is created when RenderFrame is recreated after |
| // crash. |
| IN_PROC_BROWSER_TEST_F(PageImplTest, NewPageObjectCreatedOnFrameCrash) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = primary_main_frame_host(); |
| base::WeakPtr<Page> page_a = rfh_a->GetPage().GetWeakPtr(); |
| testing::NiceMock<MockWebContentsObserver> page_changed_observer( |
| web_contents()); |
| base::WeakPtr<Data> data = CreateOrGetDataForPage(*page_a)->GetWeakPtr(); |
| |
| // 2) Make the renderer crash this should not reset the Page or delete the |
| // PageUserData. |
| RenderProcessHost* renderer_process = rfh_a->GetProcess(); |
| RenderProcessHostWatcher crash_observer( |
| renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| renderer_process->Shutdown(0); |
| crash_observer.Wait(); |
| EXPECT_TRUE(&(rfh_a->GetPage())); |
| EXPECT_TRUE(data); |
| |
| // 3) Re-initialize RenderFrame, this should result in invoking |
| // PrimaryPageChanged callback. |
| EXPECT_CALL(page_changed_observer, PrimaryPageChanged(testing::_)).Times(1); |
| FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); |
| root->render_manager()->InitializeMainRenderFrameForImmediateUse(); |
| |
| // 4) Check that the old Page object was destroyed. There is no other way to |
| // validate that the old Page and new Page are different: comparing pointer |
| // values is not a stable test, since the new Page could be reallocated at the |
| // same address. |
| EXPECT_FALSE(page_a); |
| // Similarly, expect any PageUserData from the old Page to be gone. |
| EXPECT_FALSE(data); |
| } |
| |
| // Test that a new Page object is created when we do a same-site navigation |
| // after renderer crashes. |
| IN_PROC_BROWSER_TEST_F(PageImplTest, SameSiteNavigationAfterFrameCrash) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html")); |
| |
| // 1) Navigate to A1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a1)); |
| RenderFrameHostImpl* rfh_a1 = primary_main_frame_host(); |
| base::WeakPtr<Page> page_a1 = rfh_a1->GetPage().GetWeakPtr(); |
| testing::NiceMock<MockWebContentsObserver> page_changed_observer( |
| web_contents()); |
| base::WeakPtr<Data> data = CreateOrGetDataForPage(*page_a1)->GetWeakPtr(); |
| |
| // 2) Crash the renderer hosting current RFH. This should not reset the Page |
| // or delete the PageUserData. |
| RenderProcessHost* renderer_process = rfh_a1->GetProcess(); |
| RenderProcessHostWatcher crash_observer( |
| renderer_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| renderer_process->Shutdown(0); |
| crash_observer.Wait(); |
| EXPECT_TRUE(&(web_contents()->GetPrimaryMainFrame()->GetPage())); |
| EXPECT_TRUE(data); |
| |
| // 3) Navigate same-site to A2. This will result in invoking |
| // PrimaryPageChanged callback after new Page creation. |
| EXPECT_CALL(page_changed_observer, PrimaryPageChanged(testing::_)).Times(1); |
| EXPECT_TRUE(NavigateToURL(shell(), url_a2)); |
| |
| // 4) Check that the old Page object was destroyed. There is no other way to |
| // validate that the old Page and new Page are different: comparing pointer |
| // values is not a stable test, since the new Page could be reallocated at the |
| // same address. |
| EXPECT_FALSE(page_a1); |
| // Similarly, expect any PageUserData from the old Page to be gone. |
| EXPECT_FALSE(data); |
| } |
| |
| // Test PageImpl with BackForwardCache feature enabled. |
| class PageImplWithBackForwardCacheTest : public PageImplTest { |
| public: |
| PageImplWithBackForwardCacheTest() { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{features::kBackForwardCache, |
| // Set a very long TTL before expiration (longer than the test |
| // timeout) so tests that are expecting deletion don't pass when |
| // they shouldn't. |
| {{"TimeToLiveInBackForwardCacheInSeconds", "3600"}}}}, |
| // Allow BackForwardCache for all devices regardless of their memory. |
| {features::kBackForwardCacheMemoryControls}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Tests that PageImpl object is not cleared on storing and restoring a Page |
| // from back-forward cache. |
| IN_PROC_BROWSER_TEST_F(PageImplWithBackForwardCacheTest, |
| BackForwardCacheNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); |
| |
| // 1) Navigate to A(B). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = primary_main_frame_host(); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); |
| testing::NiceMock<MockWebContentsObserver> page_changed_observer( |
| web_contents()); |
| |
| // 2) Get the PageImpl object associated with A and B RenderFrameHost. |
| PageImpl& page_a = rfh_a->GetPage(); |
| PageImpl& page_b = rfh_b->GetPage(); |
| Data* data = CreateOrGetDataForPage(page_a); |
| |
| // 3) Navigate to C. PrimaryPageChanged should be triggered as A(B) is stored |
| // in BackForwardCache. |
| EXPECT_CALL(page_changed_observer, PrimaryPageChanged(testing::_)).Times(1); |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| EXPECT_FALSE(page_a.IsPrimary()); |
| EXPECT_FALSE(page_b.IsPrimary()); |
| |
| // 4) PageImpl associated with document should point to the same object. |
| // PageUserData should not be deleted on navigating away with |
| // BackForwardCache. |
| EXPECT_EQ(&page_a, &(rfh_a->GetPage())); |
| EXPECT_EQ(&page_b, &(rfh_b->GetPage())); |
| EXPECT_TRUE(data); |
| |
| // 5) Go back to A(B) and the Page object before and after restore should |
| // point to the same object. PrimaryPageChanged should still be triggered when |
| // primary page changes to the existing page restored from the |
| // BackForwardCache point to the same object and PageUserData should not be |
| // deleted. |
| EXPECT_CALL(page_changed_observer, PrimaryPageChanged(testing::_)).Times(1); |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(&page_a, &(rfh_a->GetPage())); |
| EXPECT_EQ(&page_b, &(rfh_b->GetPage())); |
| EXPECT_TRUE(page_a.IsPrimary()); |
| EXPECT_TRUE(page_b.IsPrimary()); |
| EXPECT_TRUE(data); |
| } |
| |
| // Tests that PageImpl object is correct for IsPrimary. |
| IN_PROC_BROWSER_TEST_F(PageImplPrerenderBrowserTest, IsPrimary) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Navigate to a site. |
| GURL url_a = embedded_test_server()->GetURL("/empty.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = primary_main_frame_host(); |
| EXPECT_TRUE(rfh_a->GetPage().IsPrimary()); |
| testing::NiceMock<MockWebContentsObserver> page_changed_observer( |
| web_contents()); |
| |
| // Prerender to another site. |
| GURL prerender_url = embedded_test_server()->GetURL("/title2.html"); |
| prerender_helper_.AddPrerender(prerender_url); |
| int host_id = prerender_test_helper().GetHostForUrl(prerender_url); |
| content::test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| content::RenderFrameHost* prerender_frame = |
| prerender_test_helper().GetPrerenderedMainFrameHost(host_id); |
| Page& prerender_page = prerender_frame->GetPage(); |
| EXPECT_FALSE(prerender_page.IsPrimary()); |
| |
| // Navigate to the prerendered site. PrimaryPageChanged should only be |
| // triggered on activation. |
| EXPECT_CALL(page_changed_observer, PrimaryPageChanged(testing::_)).Times(1); |
| prerender_helper_.NavigatePrimaryPage(prerender_url); |
| EXPECT_TRUE(host_observer.was_activated()); |
| EXPECT_EQ(&prerender_page, &(primary_main_frame_host()->GetPage())); |
| EXPECT_TRUE(prerender_page.IsPrimary()); |
| } |
| |
| // Tests that IsPrimary returns false when pending deletion. |
| IN_PROC_BROWSER_TEST_F(PageImplPrerenderBrowserTest, IsPrimaryPendingDeletion) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| GURL url_d(embedded_test_server()->GetURL("d.com", "/title1.html")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = primary_main_frame_host(); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); |
| EXPECT_TRUE(rfh_a->GetPage().IsPrimary()); |
| EXPECT_TRUE(rfh_b->GetPage().IsPrimary()); |
| LeaveInPendingDeletionState(rfh_a); |
| LeaveInPendingDeletionState(rfh_b); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_d)); |
| |
| EXPECT_FALSE(rfh_a->GetPage().IsPrimary()); |
| EXPECT_FALSE(rfh_b->GetPage().IsPrimary()); |
| } |
| |
| } // namespace content |