// 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 #include #include "base/command_line.h" #include "base/logging.h" #include "base/macros.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "content/browser/frame_host/cross_site_transferring_request.h" #include "content/browser/frame_host/interstitial_page_impl.h" #include "content/browser/frame_host/navigation_entry_impl.h" #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/frame_host/render_frame_proxy_host.h" #include "content/browser/media/audio_stream_monitor.h" #include "content/browser/media/media_web_contents_observer.h" #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/site_instance_impl.h" #include "content/browser/webui/content_web_ui_controller_factory.h" #include "content/browser/webui/web_ui_controller_factory_registry.h" #include "content/common/frame_messages.h" #include "content/common/input/synthetic_web_input_event_builders.h" #include "content/common/media/media_player_delegate_messages.h" #include "content/common/site_isolation_policy.h" #include "content/common/view_messages.h" #include "content/public/browser/global_request_id.h" #include "content/public/browser/interstitial_page_delegate.h" #include "content/public/browser/javascript_dialog_manager.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents_delegate.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_ui_controller.h" #include "content/public/common/bindings_policy.h" #include "content/public/common/browser_side_navigation_policy.h" #include "content/public/common/content_constants.h" #include "content/public/common/url_constants.h" #include "content/public/test/mock_render_process_host.h" #include "content/public/test/test_utils.h" #include "content/test/test_content_browser_client.h" #include "content/test/test_content_client.h" #include "content/test/test_render_frame_host.h" #include "content/test/test_render_view_host.h" #include "content/test/test_web_contents.h" #include "net/base/test_data_directory.h" #include "net/test/cert_test_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkColor.h" #include "url/url_constants.h" namespace content { namespace { class TestInterstitialPage; class TestInterstitialPageDelegate : public InterstitialPageDelegate { public: explicit TestInterstitialPageDelegate(TestInterstitialPage* interstitial_page) : interstitial_page_(interstitial_page) {} void CommandReceived(const std::string& command) override; std::string GetHTMLContents() override { return std::string(); } void OnDontProceed() override; void OnProceed() override; private: TestInterstitialPage* interstitial_page_; }; class TestInterstitialPage : public InterstitialPageImpl { public: enum InterstitialState { INVALID = 0, // Hasn't yet been initialized. UNDECIDED, // Initialized, but no decision taken yet. OKED, // Proceed was called. CANCELED // DontProceed was called. }; class Delegate { public: virtual void TestInterstitialPageDeleted( TestInterstitialPage* interstitial) = 0; protected: virtual ~Delegate() {} }; // IMPORTANT NOTE: if you pass stack allocated values for |state| and // |deleted| (like all interstitial related tests do at this point), make sure // to create an instance of the TestInterstitialPageStateGuard class on the // stack in your test. This will ensure that the TestInterstitialPage states // are cleared when the test finishes. // Not doing so will cause stack trashing if your test does not hide the // interstitial, as in such a case it will be destroyed in the test TearDown // method and will dereference the |deleted| local variable which by then is // out of scope. TestInterstitialPage(WebContentsImpl* contents, bool new_navigation, const GURL& url, InterstitialState* state, bool* deleted) : InterstitialPageImpl( contents, static_cast(contents), new_navigation, url, new TestInterstitialPageDelegate(this)), state_(state), deleted_(deleted), command_received_count_(0), delegate_(nullptr) { *state_ = UNDECIDED; *deleted_ = false; } ~TestInterstitialPage() override { if (deleted_) *deleted_ = true; if (delegate_) delegate_->TestInterstitialPageDeleted(this); } void OnDontProceed() { if (state_) *state_ = CANCELED; } void OnProceed() { if (state_) *state_ = OKED; } int command_received_count() const { return command_received_count_; } void TestDomOperationResponse(const std::string& json_string) { if (enabled()) CommandReceived(); } void TestDidNavigate(int page_id, int nav_entry_id, bool did_create_new_entry, const GURL& url) { FrameHostMsg_DidCommitProvisionalLoad_Params params; InitNavigateParams(¶ms, page_id, nav_entry_id, did_create_new_entry, url, ui::PAGE_TRANSITION_TYPED); DidNavigate(GetMainFrame()->GetRenderViewHost(), params); } void TestRenderViewTerminated(base::TerminationStatus status, int error_code) { RenderViewTerminated(GetMainFrame()->GetRenderViewHost(), status, error_code); } bool is_showing() const { return static_cast( GetMainFrame()->GetRenderViewHost()->GetWidget()->GetView()) ->is_showing(); } void ClearStates() { state_ = nullptr; deleted_ = nullptr; delegate_ = nullptr; } void CommandReceived() { command_received_count_++; } void set_delegate(Delegate* delegate) { delegate_ = delegate; } protected: WebContentsView* CreateWebContentsView() override { return nullptr; } private: InterstitialState* state_; bool* deleted_; int command_received_count_; Delegate* delegate_; }; void TestInterstitialPageDelegate::CommandReceived(const std::string& command) { interstitial_page_->CommandReceived(); } void TestInterstitialPageDelegate::OnDontProceed() { interstitial_page_->OnDontProceed(); } void TestInterstitialPageDelegate::OnProceed() { interstitial_page_->OnProceed(); } class TestInterstitialPageStateGuard : public TestInterstitialPage::Delegate { public: explicit TestInterstitialPageStateGuard( TestInterstitialPage* interstitial_page) : interstitial_page_(interstitial_page) { DCHECK(interstitial_page_); interstitial_page_->set_delegate(this); } ~TestInterstitialPageStateGuard() override { if (interstitial_page_) interstitial_page_->ClearStates(); } void TestInterstitialPageDeleted( TestInterstitialPage* interstitial) override { DCHECK(interstitial_page_ == interstitial); interstitial_page_ = nullptr; } private: TestInterstitialPage* interstitial_page_; }; class WebContentsImplTestBrowserClient : public TestContentBrowserClient { public: WebContentsImplTestBrowserClient() : assign_site_for_url_(false) {} ~WebContentsImplTestBrowserClient() override {} bool ShouldAssignSiteForURL(const GURL& url) override { return assign_site_for_url_; } void set_assign_site_for_url(bool assign) { assign_site_for_url_ = assign; } private: bool assign_site_for_url_; }; class WebContentsImplTest : public RenderViewHostImplTestHarness { public: void SetUp() override { RenderViewHostImplTestHarness::SetUp(); WebUIControllerFactory::RegisterFactory( ContentWebUIControllerFactory::GetInstance()); } void TearDown() override { WebUIControllerFactory::UnregisterFactoryForTesting( ContentWebUIControllerFactory::GetInstance()); RenderViewHostImplTestHarness::TearDown(); } bool has_audio_power_save_blocker() { return contents() ->media_web_contents_observer() ->has_audio_power_save_blocker_for_testing(); } bool has_video_power_save_blocker() { return contents() ->media_web_contents_observer() ->has_video_power_save_blocker_for_testing(); } }; class TestWebContentsObserver : public WebContentsObserver { public: explicit TestWebContentsObserver(WebContents* contents) : WebContentsObserver(contents), last_theme_color_(SK_ColorTRANSPARENT) { } ~TestWebContentsObserver() override {} void DidFinishLoad(RenderFrameHost* render_frame_host, const GURL& validated_url) override { last_url_ = validated_url; } void DidFailLoad(RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, const base::string16& error_description, bool was_ignored_by_handler) override { last_url_ = validated_url; } void DidChangeThemeColor(SkColor theme_color) override { last_theme_color_ = theme_color; } const GURL& last_url() const { return last_url_; } SkColor last_theme_color() const { return last_theme_color_; } private: GURL last_url_; SkColor last_theme_color_; DISALLOW_COPY_AND_ASSIGN(TestWebContentsObserver); }; // Pretends to be a normal browser that receives toggles and transitions to/from // a fullscreened state. class FakeFullscreenDelegate : public WebContentsDelegate { public: FakeFullscreenDelegate() : fullscreened_contents_(nullptr) {} ~FakeFullscreenDelegate() override {} void EnterFullscreenModeForTab(WebContents* web_contents, const GURL& origin) override { fullscreened_contents_ = web_contents; } void ExitFullscreenModeForTab(WebContents* web_contents) override { fullscreened_contents_ = nullptr; } bool IsFullscreenForTabOrPending( const WebContents* web_contents) const override { return fullscreened_contents_ && web_contents == fullscreened_contents_; } private: WebContents* fullscreened_contents_; DISALLOW_COPY_AND_ASSIGN(FakeFullscreenDelegate); }; class FakeValidationMessageDelegate : public WebContentsDelegate { public: FakeValidationMessageDelegate() : hide_validation_message_was_called_(false) {} ~FakeValidationMessageDelegate() override {} void HideValidationMessage(WebContents* web_contents) override { hide_validation_message_was_called_ = true; } bool hide_validation_message_was_called() const { return hide_validation_message_was_called_; } private: bool hide_validation_message_was_called_; DISALLOW_COPY_AND_ASSIGN(FakeValidationMessageDelegate); }; } // namespace // Test to make sure that title updates get stripped of whitespace. TEST_F(WebContentsImplTest, UpdateTitle) { NavigationControllerImpl& cont = static_cast(controller()); cont.LoadURL(GURL(url::kAboutBlankURL), Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); FrameHostMsg_DidCommitProvisionalLoad_Params params; InitNavigateParams(¶ms, 0, 0, true, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED); contents()->GetMainFrame()->PrepareForCommit(); contents()->GetMainFrame()->SendNavigateWithParams(¶ms); contents()->UpdateTitle(contents()->GetMainFrame(), 0, base::ASCIIToUTF16(" Lots O' Whitespace\n"), base::i18n::LEFT_TO_RIGHT); EXPECT_EQ(base::ASCIIToUTF16("Lots O' Whitespace"), contents()->GetTitle()); } TEST_F(WebContentsImplTest, UpdateTitleBeforeFirstNavigation) { ASSERT_TRUE(controller().IsInitialNavigation()); const base::string16 title = base::ASCIIToUTF16("Initial Entry Title"); contents()->UpdateTitle(contents()->GetMainFrame(), -1, title, base::i18n::LEFT_TO_RIGHT); EXPECT_EQ(title, contents()->GetTitle()); } TEST_F(WebContentsImplTest, DontUseTitleFromPendingEntry) { const GURL kGURL("chrome://blah"); controller().LoadURL( kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); EXPECT_EQ(base::string16(), contents()->GetTitle()); // Also test setting title while the first navigation is still pending. const base::string16 title = base::ASCIIToUTF16("Initial Entry Title"); contents()->UpdateTitle(contents()->GetMainFrame(), -1, title, base::i18n::LEFT_TO_RIGHT); EXPECT_EQ(title, contents()->GetTitle()); } TEST_F(WebContentsImplTest, UseTitleFromPendingEntryIfSet) { const GURL kGURL("chrome://blah"); const base::string16 title = base::ASCIIToUTF16("My Title"); controller().LoadURL( kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); NavigationEntry* entry = controller().GetVisibleEntry(); ASSERT_EQ(kGURL, entry->GetURL()); entry->SetTitle(title); EXPECT_EQ(title, contents()->GetTitle()); } // Test view source mode for a webui page. TEST_F(WebContentsImplTest, NTPViewSource) { NavigationControllerImpl& cont = static_cast(controller()); const char kUrl[] = "view-source:chrome://blah"; const GURL kGURL(kUrl); process()->sink().ClearMessages(); cont.LoadURL( kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = cont.GetPendingEntry()->GetUniqueID(); rvh()->GetDelegate()->RenderViewCreated(rvh()); // Did we get the expected message? EXPECT_TRUE(process()->sink().GetFirstMessageMatching( ViewMsg_EnableViewSourceMode::ID)); FrameHostMsg_DidCommitProvisionalLoad_Params params; InitNavigateParams(¶ms, 0, entry_id, true, kGURL, ui::PAGE_TRANSITION_TYPED); contents()->GetMainFrame()->PrepareForCommit(); contents()->GetMainFrame()->SendNavigateWithParams(¶ms); // Also check title and url. EXPECT_EQ(base::ASCIIToUTF16(kUrl), contents()->GetTitle()); } // Test to ensure UpdateMaxPageID is working properly. TEST_F(WebContentsImplTest, UpdateMaxPageID) { SiteInstance* instance1 = contents()->GetSiteInstance(); scoped_refptr instance2(SiteInstance::Create(nullptr)); // Starts at -1. EXPECT_EQ(-1, contents()->GetMaxPageID()); EXPECT_EQ(-1, contents()->GetMaxPageIDForSiteInstance(instance1)); EXPECT_EQ(-1, contents()->GetMaxPageIDForSiteInstance(instance2.get())); // Make sure max_page_id_ is monotonically increasing per SiteInstance. contents()->UpdateMaxPageID(3); contents()->UpdateMaxPageID(1); EXPECT_EQ(3, contents()->GetMaxPageID()); EXPECT_EQ(3, contents()->GetMaxPageIDForSiteInstance(instance1)); EXPECT_EQ(-1, contents()->GetMaxPageIDForSiteInstance(instance2.get())); contents()->UpdateMaxPageIDForSiteInstance(instance2.get(), 7); EXPECT_EQ(3, contents()->GetMaxPageID()); EXPECT_EQ(3, contents()->GetMaxPageIDForSiteInstance(instance1)); EXPECT_EQ(7, contents()->GetMaxPageIDForSiteInstance(instance2.get())); } // Test simple same-SiteInstance navigation. TEST_F(WebContentsImplTest, SimpleNavigation) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); SiteInstance* instance1 = contents()->GetSiteInstance(); EXPECT_EQ(nullptr, contents()->GetPendingMainFrame()); // Navigate to URL const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); main_test_rfh()->PrepareForCommit(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(instance1, orig_rfh->GetSiteInstance()); // Controller's pending entry will have a null site instance until we assign // it in DidNavigate. EXPECT_EQ( nullptr, NavigationEntryImpl::FromNavigationEntry(controller().GetVisibleEntry())-> site_instance()); // DidNavigate from the page contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); EXPECT_EQ(instance1, orig_rfh->GetSiteInstance()); // Controller's entry should now have the SiteInstance, or else we won't be // able to find it later. EXPECT_EQ( instance1, NavigationEntryImpl::FromNavigationEntry(controller().GetVisibleEntry())-> site_instance()); } // Test that we reject NavigateToEntry if the url is over kMaxURLChars. TEST_F(WebContentsImplTest, NavigateToExcessivelyLongURL) { // Construct a URL that's kMaxURLChars + 1 long of all 'a's. const GURL url(std::string("http://example.org/").append( url::kMaxURLChars + 1, 'a')); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string()); EXPECT_EQ(nullptr, controller().GetPendingEntry()); } // Test that we reject NavigateToEntry if the url is invalid. TEST_F(WebContentsImplTest, NavigateToInvalidURL) { // Invalid URLs should not trigger a navigation. const GURL invalid_url("view-source:http://example.org/%00"); controller().LoadURL( invalid_url, Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string()); EXPECT_EQ(nullptr, controller().GetPendingEntry()); // Empty URLs are supported and should start a navigation. controller().LoadURL( GURL(), Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string()); EXPECT_NE(nullptr, controller().GetPendingEntry()); } // Test that navigating across a site boundary creates a new RenderViewHost // with a new SiteInstance. Going back should do the same. TEST_F(WebContentsImplTest, CrossSiteBoundaries) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); int orig_rvh_delete_count = 0; orig_rfh->GetRenderViewHost()->set_delete_counter(&orig_rvh_delete_count); SiteInstance* instance1 = contents()->GetSiteInstance(); // Navigate to URL. First URL should use first RenderViewHost. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); // Keep the number of active frames in orig_rfh's SiteInstance non-zero so // that orig_rfh doesn't get deleted when it gets swapped out. orig_rfh->GetSiteInstance()->IncrementActiveFrameCount(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh->GetRenderViewHost(), contents()->GetRenderViewHost()); EXPECT_EQ(url, contents()->GetLastCommittedURL()); EXPECT_EQ(url, contents()->GetVisibleURL()); // Navigate to new site const GURL url2("http://www.yahoo.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); if (IsBrowserSideNavigationEnabled()) orig_rfh->PrepareForCommit(); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(url, contents()->GetLastCommittedURL()); EXPECT_EQ(url2, contents()->GetVisibleURL()); TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame(); EXPECT_TRUE(pending_rfh->GetLastCommittedURL().is_empty()); int pending_rvh_delete_count = 0; pending_rfh->GetRenderViewHost()->set_delete_counter( &pending_rvh_delete_count); // Navigations should be suspended in pending_rfh until BeforeUnloadACK. if (!IsBrowserSideNavigationEnabled()) { EXPECT_TRUE(pending_rfh->are_navigations_suspended()); orig_rfh->SendBeforeUnloadACK(true); EXPECT_FALSE(pending_rfh->are_navigations_suspended()); } // DidNavigate from the pending page contents()->TestDidNavigate(pending_rfh, 1, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); SiteInstance* instance2 = contents()->GetSiteInstance(); // Keep the number of active frames in pending_rfh's SiteInstance // non-zero so that orig_rfh doesn't get deleted when it gets // swapped out. pending_rfh->GetSiteInstance()->IncrementActiveFrameCount(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(pending_rfh, contents()->GetMainFrame()); EXPECT_EQ(url2, contents()->GetLastCommittedURL()); EXPECT_EQ(url2, contents()->GetVisibleURL()); EXPECT_NE(instance1, instance2); EXPECT_EQ(nullptr, contents()->GetPendingMainFrame()); // We keep a proxy for the original RFH's SiteInstance. EXPECT_TRUE(contents()->GetRenderManagerForTesting()->GetRenderFrameProxyHost( orig_rfh->GetSiteInstance())); EXPECT_EQ(orig_rvh_delete_count, 0); // Going back should switch SiteInstances again. The first SiteInstance is // stored in the NavigationEntry, so it should be the same as at the start. // We should use the same RFH as before, swapping it back in. controller().GoBack(); entry_id = controller().GetPendingEntry()->GetUniqueID(); if (IsBrowserSideNavigationEnabled()) contents()->GetMainFrame()->PrepareForCommit(); TestRenderFrameHost* goback_rfh = contents()->GetPendingMainFrame(); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); // Navigations should be suspended in goback_rfh until BeforeUnloadACK. if (!IsBrowserSideNavigationEnabled()) { EXPECT_TRUE(goback_rfh->are_navigations_suspended()); pending_rfh->SendBeforeUnloadACK(true); EXPECT_FALSE(goback_rfh->are_navigations_suspended()); } // DidNavigate from the back action contents()->TestDidNavigate(goback_rfh, 1, entry_id, false, url2, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(goback_rfh, contents()->GetMainFrame()); EXPECT_EQ(instance1, contents()->GetSiteInstance()); // There should be a proxy for the pending RFH SiteInstance. EXPECT_TRUE(contents()->GetRenderManagerForTesting()-> GetRenderFrameProxyHost(pending_rfh->GetSiteInstance())); EXPECT_EQ(pending_rvh_delete_count, 0); pending_rfh->OnSwappedOut(); // Close contents and ensure RVHs are deleted. DeleteContents(); EXPECT_EQ(orig_rvh_delete_count, 1); EXPECT_EQ(pending_rvh_delete_count, 1); } // Test that navigating across a site boundary after a crash creates a new // RFH without requiring a cross-site transition (i.e., PENDING state). TEST_F(WebContentsImplTest, CrossSiteBoundariesAfterCrash) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); int orig_rvh_delete_count = 0; orig_rfh->GetRenderViewHost()->set_delete_counter(&orig_rvh_delete_count); SiteInstance* instance1 = contents()->GetSiteInstance(); // Navigate to URL. First URL should use first RenderViewHost. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh->GetRenderViewHost(), contents()->GetRenderViewHost()); // Simulate a renderer crash. EXPECT_TRUE(orig_rfh->IsRenderFrameLive()); orig_rfh->GetProcess()->SimulateCrash(); EXPECT_FALSE(orig_rfh->IsRenderFrameLive()); // Navigate to new site. We should not go into PENDING. const GURL url2("http://www.yahoo.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); TestRenderFrameHost* new_rfh = contents()->GetMainFrame(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(nullptr, contents()->GetPendingMainFrame()); EXPECT_NE(orig_rfh, new_rfh); EXPECT_EQ(orig_rvh_delete_count, 1); // DidNavigate from the new page contents()->TestDidNavigate(new_rfh, 1, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); SiteInstance* instance2 = contents()->GetSiteInstance(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(new_rfh, main_rfh()); EXPECT_NE(instance1, instance2); EXPECT_EQ(nullptr, contents()->GetPendingMainFrame()); // Close contents and ensure RVHs are deleted. DeleteContents(); EXPECT_EQ(orig_rvh_delete_count, 1); } // Test that opening a new contents in the same SiteInstance and then navigating // both contentses to a new site will place both contentses in a single // SiteInstance. TEST_F(WebContentsImplTest, NavigateTwoTabsCrossSite) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); SiteInstance* instance1 = contents()->GetSiteInstance(); // Navigate to URL. First URL should use first RenderViewHost. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); // Open a new contents with the same SiteInstance, navigated to the same site. scoped_ptr contents2( TestWebContents::Create(browser_context(), instance1)); contents2->GetController().LoadURL(url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = contents2->GetController().GetPendingEntry()->GetUniqueID(); contents2->GetMainFrame()->PrepareForCommit(); // Need this page id to be 2 since the site instance is the same (which is the // scope of page IDs) and we want to consider this a new page. contents2->TestDidNavigate(contents2->GetMainFrame(), 2, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); // Navigate first contents to a new site. const GURL url2a("http://www.yahoo.com"); controller().LoadURL( url2a, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); orig_rfh->PrepareForCommit(); TestRenderFrameHost* pending_rfh_a = contents()->GetPendingMainFrame(); contents()->TestDidNavigate(pending_rfh_a, 1, entry_id, true, url2a, ui::PAGE_TRANSITION_TYPED); SiteInstance* instance2a = contents()->GetSiteInstance(); EXPECT_NE(instance1, instance2a); // Navigate second contents to the same site as the first tab. const GURL url2b("http://mail.yahoo.com"); contents2->GetController().LoadURL(url2b, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = contents2->GetController().GetPendingEntry()->GetUniqueID(); TestRenderFrameHost* rfh2 = contents2->GetMainFrame(); rfh2->PrepareForCommit(); TestRenderFrameHost* pending_rfh_b = contents2->GetPendingMainFrame(); EXPECT_NE(nullptr, pending_rfh_b); EXPECT_TRUE(contents2->CrossProcessNavigationPending()); // NOTE(creis): We used to be in danger of showing a crash page here if the // second contents hadn't navigated somewhere first (bug 1145430). That case // is now covered by the CrossSiteBoundariesAfterCrash test. contents2->TestDidNavigate(pending_rfh_b, 2, entry_id, true, url2b, ui::PAGE_TRANSITION_TYPED); SiteInstance* instance2b = contents2->GetSiteInstance(); EXPECT_NE(instance1, instance2b); // Both contentses should now be in the same SiteInstance. EXPECT_EQ(instance2a, instance2b); } // The embedder can request sites for certain urls not be be assigned to the // SiteInstance through ShouldAssignSiteForURL() in content browser client, // allowing to reuse the renderer backing certain chrome urls for subsequent // navigation. The test verifies that the override is honored. TEST_F(WebContentsImplTest, NavigateFromSitelessUrl) { WebContentsImplTestBrowserClient browser_client; SetBrowserClientForTesting(&browser_client); TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); int orig_rvh_delete_count = 0; orig_rfh->GetRenderViewHost()->set_delete_counter(&orig_rvh_delete_count); SiteInstanceImpl* orig_instance = contents()->GetSiteInstance(); browser_client.set_assign_site_for_url(false); // Navigate to an URL that will not assign a new SiteInstance. const GURL native_url("non-site-url://stuffandthings"); controller().LoadURL( native_url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, native_url, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); EXPECT_EQ(native_url, contents()->GetLastCommittedURL()); EXPECT_EQ(native_url, contents()->GetVisibleURL()); EXPECT_EQ(orig_instance, contents()->GetSiteInstance()); EXPECT_EQ(GURL(), contents()->GetSiteInstance()->GetSiteURL()); EXPECT_FALSE(orig_instance->HasSite()); browser_client.set_assign_site_for_url(true); // Navigate to new site (should keep same site instance). const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(native_url, contents()->GetLastCommittedURL()); EXPECT_EQ(url, contents()->GetVisibleURL()); EXPECT_FALSE(contents()->GetPendingMainFrame()); contents()->TestDidNavigate(orig_rfh, 2, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); // Keep the number of active frames in orig_rfh's SiteInstance // non-zero so that orig_rfh doesn't get deleted when it gets // swapped out. orig_rfh->GetSiteInstance()->IncrementActiveFrameCount(); EXPECT_EQ(orig_instance, contents()->GetSiteInstance()); EXPECT_TRUE( contents()->GetSiteInstance()->GetSiteURL().DomainIs("google.com")); EXPECT_EQ(url, contents()->GetLastCommittedURL()); // Navigate to another new site (should create a new site instance). const GURL url2("http://www.yahoo.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); if (IsBrowserSideNavigationEnabled()) orig_rfh->PrepareForCommit(); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(url, contents()->GetLastCommittedURL()); EXPECT_EQ(url2, contents()->GetVisibleURL()); TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame(); int pending_rvh_delete_count = 0; pending_rfh->GetRenderViewHost()->set_delete_counter( &pending_rvh_delete_count); // Navigations should be suspended in pending_rvh until BeforeUnloadACK. if (!IsBrowserSideNavigationEnabled()) { EXPECT_TRUE(pending_rfh->are_navigations_suspended()); orig_rfh->SendBeforeUnloadACK(true); EXPECT_FALSE(pending_rfh->are_navigations_suspended()); } // DidNavigate from the pending page. contents()->TestDidNavigate(pending_rfh, 1, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); SiteInstance* new_instance = contents()->GetSiteInstance(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(pending_rfh, contents()->GetMainFrame()); EXPECT_EQ(url2, contents()->GetLastCommittedURL()); EXPECT_EQ(url2, contents()->GetVisibleURL()); EXPECT_NE(new_instance, orig_instance); EXPECT_FALSE(contents()->GetPendingMainFrame()); // We keep a proxy for the original RFH's SiteInstance. EXPECT_TRUE(contents()->GetRenderManagerForTesting()->GetRenderFrameProxyHost( orig_rfh->GetSiteInstance())); EXPECT_EQ(orig_rvh_delete_count, 0); orig_rfh->OnSwappedOut(); // Close contents and ensure RVHs are deleted. DeleteContents(); EXPECT_EQ(orig_rvh_delete_count, 1); EXPECT_EQ(pending_rvh_delete_count, 1); } // Regression test for http://crbug.com/386542 - variation of // NavigateFromSitelessUrl in which the original navigation is a session // restore. TEST_F(WebContentsImplTest, NavigateFromRestoredSitelessUrl) { WebContentsImplTestBrowserClient browser_client; SetBrowserClientForTesting(&browser_client); SiteInstanceImpl* orig_instance = contents()->GetSiteInstance(); TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); // Restore a navigation entry for URL that should not assign site to the // SiteInstance. browser_client.set_assign_site_for_url(false); const GURL native_url("non-site-url://stuffandthings"); std::vector> entries; scoped_ptr new_entry = NavigationControllerImpl::CreateNavigationEntry( native_url, Referrer(), ui::PAGE_TRANSITION_LINK, false, std::string(), browser_context()); new_entry->SetPageID(0); entries.push_back(std::move(new_entry)); controller().Restore( 0, NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY, &entries); ASSERT_EQ(0u, entries.size()); ASSERT_EQ(1, controller().GetEntryCount()); controller().GoToIndex(0); NavigationEntry* entry = controller().GetPendingEntry(); orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 0, entry->GetUniqueID(), false, native_url, ui::PAGE_TRANSITION_RELOAD); EXPECT_EQ(orig_instance, contents()->GetSiteInstance()); EXPECT_EQ(GURL(), contents()->GetSiteInstance()->GetSiteURL()); EXPECT_FALSE(orig_instance->HasSite()); // Navigate to a regular site and verify that the SiteInstance was kept. browser_client.set_assign_site_for_url(true); const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry = controller().GetPendingEntry(); orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 2, entry->GetUniqueID(), true, url, ui::PAGE_TRANSITION_TYPED); EXPECT_EQ(orig_instance, contents()->GetSiteInstance()); // Cleanup. DeleteContents(); } // Complement for NavigateFromRestoredSitelessUrl, verifying that when a regular // tab is restored, the SiteInstance will change upon navigation. TEST_F(WebContentsImplTest, NavigateFromRestoredRegularUrl) { WebContentsImplTestBrowserClient browser_client; SetBrowserClientForTesting(&browser_client); SiteInstanceImpl* orig_instance = contents()->GetSiteInstance(); TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); // Restore a navigation entry for a regular URL ensuring that the embedder // ShouldAssignSiteForUrl override is disabled (i.e. returns true). browser_client.set_assign_site_for_url(true); const GURL regular_url("http://www.yahoo.com"); std::vector> entries; scoped_ptr new_entry = NavigationControllerImpl::CreateNavigationEntry( regular_url, Referrer(), ui::PAGE_TRANSITION_LINK, false, std::string(), browser_context()); new_entry->SetPageID(0); entries.push_back(std::move(new_entry)); controller().Restore( 0, NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY, &entries); ASSERT_EQ(0u, entries.size()); ASSERT_EQ(1, controller().GetEntryCount()); controller().GoToIndex(0); NavigationEntry* entry = controller().GetPendingEntry(); orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 0, entry->GetUniqueID(), false, regular_url, ui::PAGE_TRANSITION_RELOAD); EXPECT_EQ(orig_instance, contents()->GetSiteInstance()); EXPECT_TRUE(orig_instance->HasSite()); // Navigate to another site and verify that a new SiteInstance was created. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry = controller().GetPendingEntry(); orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(contents()->GetPendingMainFrame(), 2, entry->GetUniqueID(), true, url, ui::PAGE_TRANSITION_TYPED); EXPECT_NE(orig_instance, contents()->GetSiteInstance()); // Cleanup. DeleteContents(); } // Test that we can find an opener RVH even if it's pending. // http://crbug.com/176252. TEST_F(WebContentsImplTest, FindOpenerRVHWhenPending) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); // Navigate to a URL. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); // Start to navigate first tab to a new site, so that it has a pending RVH. const GURL url2("http://www.yahoo.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); orig_rfh->PrepareForCommit(); TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame(); SiteInstance* instance = pending_rfh->GetSiteInstance(); // While it is still pending, simulate opening a new tab with the first tab // as its opener. This will call CreateOpenerProxies on the opener to ensure // that an RVH exists. scoped_ptr popup( TestWebContents::Create(browser_context(), instance)); popup->SetOpener(contents()); contents()->GetRenderManager()->CreateOpenerProxies(instance, nullptr); // If swapped out is forbidden, a new proxy should be created for the opener // in |instance|, and we should ensure that its routing ID is returned here. // Otherwise, we should find the pending RFH and not create a new proxy. int opener_frame_routing_id = popup->GetRenderManager()->GetOpenerRoutingID(instance); RenderFrameProxyHost* proxy = contents()->GetRenderManager()->GetRenderFrameProxyHost(instance); EXPECT_TRUE(proxy); EXPECT_EQ(proxy->GetRoutingID(), opener_frame_routing_id); // Ensure that committing the navigation removes the proxy. entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->TestDidNavigate(pending_rfh, 2, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE( contents()->GetRenderManager()->GetRenderFrameProxyHost(instance)); } // Tests that WebContentsImpl uses the current URL, not the SiteInstance's site, // to determine whether a navigation is cross-site. TEST_F(WebContentsImplTest, CrossSiteComparesAgainstCurrentPage) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); SiteInstance* instance1 = contents()->GetSiteInstance(); // Navigate to URL. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); // Open a related contents to a second site. scoped_ptr contents2( TestWebContents::Create(browser_context(), instance1)); const GURL url2("http://www.yahoo.com"); contents2->GetController().LoadURL(url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = contents2->GetController().GetPendingEntry()->GetUniqueID(); contents2->GetMainFrame()->PrepareForCommit(); // The first RVH in contents2 isn't live yet, so we shortcut the cross site // pending. TestRenderFrameHost* rfh2 = contents2->GetMainFrame(); EXPECT_FALSE(contents2->CrossProcessNavigationPending()); contents2->TestDidNavigate(rfh2, 2, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); SiteInstance* instance2 = contents2->GetSiteInstance(); EXPECT_NE(instance1, instance2); EXPECT_FALSE(contents2->CrossProcessNavigationPending()); // Simulate a link click in first contents to second site. Doesn't switch // SiteInstances, because we don't intercept Blink navigations. orig_rfh->SendRendererInitiatedNavigationRequest(url2, true); orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 2, 0, true, url2, ui::PAGE_TRANSITION_TYPED); SiteInstance* instance3 = contents()->GetSiteInstance(); EXPECT_EQ(instance1, instance3); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); // Navigate to the new site. Doesn't switch SiteInstancees, because we // compare against the current URL, not the SiteInstance's site. const GURL url3("http://mail.yahoo.com"); controller().LoadURL( url3, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 3, entry_id, true, url3, ui::PAGE_TRANSITION_TYPED); SiteInstance* instance4 = contents()->GetSiteInstance(); EXPECT_EQ(instance1, instance4); } // Test that the onbeforeunload and onunload handlers run when navigating // across site boundaries. TEST_F(WebContentsImplTest, CrossSiteUnloadHandlers) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); SiteInstance* instance1 = contents()->GetSiteInstance(); // Navigate to URL. First URL should use first RenderViewHost. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); // Navigate to new site, but simulate an onbeforeunload denial. const GURL url2("http://www.yahoo.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_ack()); base::TimeTicks now = base::TimeTicks::Now(); orig_rfh->OnMessageReceived( FrameHostMsg_BeforeUnload_ACK(0, false, now, now)); EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_ack()); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); // Navigate again, but simulate an onbeforeunload approval. controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_ack()); now = base::TimeTicks::Now(); orig_rfh->PrepareForCommit(); EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_ack()); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame(); // We won't hear DidNavigate until the onunload handler has finished running. // DidNavigate from the pending page. contents()->TestDidNavigate(pending_rfh, 1, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); SiteInstance* instance2 = contents()->GetSiteInstance(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(pending_rfh, contents()->GetMainFrame()); EXPECT_NE(instance1, instance2); EXPECT_EQ(nullptr, contents()->GetPendingMainFrame()); } // Test that during a slow cross-site navigation, the original renderer can // navigate to a different URL and have it displayed, canceling the slow // navigation. TEST_F(WebContentsImplTest, CrossSiteNavigationPreempted) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); SiteInstance* instance1 = contents()->GetSiteInstance(); // Navigate to URL. First URL should use first RenderFrameHost. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); // Navigate to new site, simulating an onbeforeunload approval. const GURL url2("http://www.yahoo.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_ack()); orig_rfh->PrepareForCommit(); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); // Suppose the original renderer navigates before the new one is ready. orig_rfh->SendNavigate(2, 0, true, GURL("http://www.google.com/foo")); // Verify that the pending navigation is cancelled. EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_ack()); SiteInstance* instance2 = contents()->GetSiteInstance(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); EXPECT_EQ(instance1, instance2); EXPECT_EQ(nullptr, contents()->GetPendingMainFrame()); } // Tests that if we go back twice (same-site then cross-site), and the same-site // RFH commits first, the cross-site RFH's navigation is canceled. // TODO(avi,creis): Consider changing this behavior to better match the user's // intent. TEST_F(WebContentsImplTest, CrossSiteNavigationBackPreempted) { // Start with a web ui page, which gets a new RVH with WebUI bindings. GURL url1(std::string(kChromeUIScheme) + "://" + std::string(kChromeUIGpuHost)); controller().LoadURL( url1, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); TestRenderFrameHost* webui_rfh = contents()->GetMainFrame(); webui_rfh->PrepareForCommit(); contents()->TestDidNavigate(webui_rfh, 1, entry_id, true, url1, ui::PAGE_TRANSITION_TYPED); NavigationEntry* entry1 = controller().GetLastCommittedEntry(); SiteInstance* instance1 = contents()->GetSiteInstance(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(webui_rfh, contents()->GetMainFrame()); EXPECT_EQ(url1, entry1->GetURL()); EXPECT_EQ(instance1, NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance()); EXPECT_TRUE(webui_rfh->GetRenderViewHost()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI); // Navigate to new site. const GURL url2("http://www.google.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); TestRenderFrameHost* google_rfh = contents()->GetPendingMainFrame(); // Simulate beforeunload approval. EXPECT_TRUE(webui_rfh->is_waiting_for_beforeunload_ack()); webui_rfh->PrepareForCommit(); // DidNavigate from the pending page. contents()->TestDidNavigate(google_rfh, 1, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); NavigationEntry* entry2 = controller().GetLastCommittedEntry(); SiteInstance* instance2 = contents()->GetSiteInstance(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(google_rfh, contents()->GetMainFrame()); EXPECT_NE(instance1, instance2); EXPECT_FALSE(contents()->GetPendingMainFrame()); EXPECT_EQ(url2, entry2->GetURL()); EXPECT_EQ(instance2, NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance()); EXPECT_FALSE(google_rfh->GetRenderViewHost()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI); // Navigate to third page on same site. const GURL url3("http://news.google.com"); controller().LoadURL( url3, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(google_rfh, 2, entry_id, true, url3, ui::PAGE_TRANSITION_TYPED); NavigationEntry* entry3 = controller().GetLastCommittedEntry(); SiteInstance* instance3 = contents()->GetSiteInstance(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(google_rfh, contents()->GetMainFrame()); EXPECT_EQ(instance2, instance3); EXPECT_FALSE(contents()->GetPendingMainFrame()); EXPECT_EQ(url3, entry3->GetURL()); EXPECT_EQ(instance3, NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance()); // Go back within the site. controller().GoBack(); NavigationEntry* goback_entry = controller().GetPendingEntry(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(entry2, controller().GetPendingEntry()); // Before that commits, go back again. controller().GoBack(); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); EXPECT_TRUE(contents()->GetPendingMainFrame()); EXPECT_EQ(entry1, controller().GetPendingEntry()); // Simulate beforeunload approval. EXPECT_TRUE(google_rfh->is_waiting_for_beforeunload_ack()); base::TimeTicks now = base::TimeTicks::Now(); google_rfh->PrepareForCommit(); google_rfh->OnMessageReceived( FrameHostMsg_BeforeUnload_ACK(0, true, now, now)); // DidNavigate from the first back. This aborts the second back's pending RFH. contents()->TestDidNavigate(google_rfh, 1, goback_entry->GetUniqueID(), false, url2, ui::PAGE_TRANSITION_TYPED); // We should commit this page and forget about the second back. EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_FALSE(controller().GetPendingEntry()); EXPECT_EQ(google_rfh, contents()->GetMainFrame()); EXPECT_EQ(url2, controller().GetLastCommittedEntry()->GetURL()); // We should not have corrupted the NTP entry. EXPECT_EQ(instance3, NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance()); EXPECT_EQ(instance2, NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance()); EXPECT_EQ(instance1, NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance()); EXPECT_EQ(url1, entry1->GetURL()); } // Tests that if we go back twice (same-site then cross-site), and the cross- // site RFH commits first, we ignore the now-swapped-out RFH's commit. TEST_F(WebContentsImplTest, CrossSiteNavigationBackOldNavigationIgnored) { // Start with a web ui page, which gets a new RVH with WebUI bindings. GURL url1(std::string(kChromeUIScheme) + "://" + std::string(kChromeUIGpuHost)); controller().LoadURL(url1, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); TestRenderFrameHost* webui_rfh = contents()->GetMainFrame(); webui_rfh->PrepareForCommit(); contents()->TestDidNavigate(webui_rfh, 1, entry_id, true, url1, ui::PAGE_TRANSITION_TYPED); NavigationEntry* entry1 = controller().GetLastCommittedEntry(); SiteInstance* instance1 = contents()->GetSiteInstance(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(webui_rfh, contents()->GetMainFrame()); EXPECT_EQ(url1, entry1->GetURL()); EXPECT_EQ(instance1, NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance()); EXPECT_TRUE(webui_rfh->GetRenderViewHost()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI); // Navigate to new site. const GURL url2("http://www.google.com"); controller().LoadURL(url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); TestRenderFrameHost* google_rfh = contents()->GetPendingMainFrame(); // Simulate beforeunload approval. EXPECT_TRUE(webui_rfh->is_waiting_for_beforeunload_ack()); webui_rfh->PrepareForCommit(); // DidNavigate from the pending page. contents()->TestDidNavigate(google_rfh, 1, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); NavigationEntry* entry2 = controller().GetLastCommittedEntry(); SiteInstance* instance2 = contents()->GetSiteInstance(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(google_rfh, contents()->GetMainFrame()); EXPECT_NE(instance1, instance2); EXPECT_FALSE(contents()->GetPendingMainFrame()); EXPECT_EQ(url2, entry2->GetURL()); EXPECT_EQ(instance2, NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance()); EXPECT_FALSE(google_rfh->GetRenderViewHost()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI); // Navigate to third page on same site. const GURL url3("http://news.google.com"); controller().LoadURL(url3, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(google_rfh, 2, entry_id, true, url3, ui::PAGE_TRANSITION_TYPED); NavigationEntry* entry3 = controller().GetLastCommittedEntry(); SiteInstance* instance3 = contents()->GetSiteInstance(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(google_rfh, contents()->GetMainFrame()); EXPECT_EQ(instance2, instance3); EXPECT_FALSE(contents()->GetPendingMainFrame()); EXPECT_EQ(url3, entry3->GetURL()); EXPECT_EQ(instance3, NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance()); // Go back within the site. controller().GoBack(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(entry2, controller().GetPendingEntry()); // Before that commits, go back again. controller().GoBack(); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); EXPECT_TRUE(contents()->GetPendingMainFrame()); EXPECT_EQ(entry1, controller().GetPendingEntry()); webui_rfh = contents()->GetPendingMainFrame(); // DidNavigate from the second back. contents()->TestDidNavigate(webui_rfh, 1, entry1->GetUniqueID(), false, url1, ui::PAGE_TRANSITION_TYPED); // That should have landed us on the first entry. EXPECT_EQ(entry1, controller().GetLastCommittedEntry()); // When the second back commits, it should be ignored. contents()->TestDidNavigate(google_rfh, 1, entry2->GetUniqueID(), false, url2, ui::PAGE_TRANSITION_TYPED); EXPECT_EQ(entry1, controller().GetLastCommittedEntry()); } // Test that during a slow cross-site navigation, a sub-frame navigation in the // original renderer will not cancel the slow navigation (bug 42029). TEST_F(WebContentsImplTest, CrossSiteNavigationNotPreemptedByFrame) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); // Navigate to URL. First URL should use the original RenderFrameHost. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); // Start navigating to new site. const GURL url2("http://www.yahoo.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); // Simulate a sub-frame navigation arriving and ensure the RVH is still // waiting for a before unload response. TestRenderFrameHost* child_rfh = orig_rfh->AppendChild("subframe"); child_rfh->SendNavigateWithTransition(1, 0, false, GURL("http://google.com/frame"), ui::PAGE_TRANSITION_AUTO_SUBFRAME); EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_ack()); // Now simulate the onbeforeunload approval and verify the navigation is // not canceled. orig_rfh->PrepareForCommit(); EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_ack()); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); } namespace { void SetAsNonUserGesture(FrameHostMsg_DidCommitProvisionalLoad_Params* params) { params->gesture = NavigationGestureAuto; } } // Test that a cross-site navigation is not preempted if the previous // renderer sends a FrameNavigate message just before being told to stop. // We should only preempt the cross-site navigation if the previous renderer // has started a new navigation. See http://crbug.com/79176. TEST_F(WebContentsImplTest, CrossSiteNotPreemptedDuringBeforeUnload) { // Navigate to WebUI URL. const GURL url("chrome://gpu"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry1_id = controller().GetPendingEntry()->GetUniqueID(); TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); // Navigate to new site, with the beforeunload request in flight. const GURL url2("http://www.yahoo.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry2_id = controller().GetPendingEntry()->GetUniqueID(); TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame(); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_ack()); EXPECT_NE(orig_rfh, pending_rfh); // Suppose the first navigation tries to commit now, with a // FrameMsg_Stop in flight. This should not cancel the pending navigation, // but it should act as if the beforeunload ack arrived. orig_rfh->SendNavigateWithModificationCallback( 1, entry1_id, true, url, base::Bind(SetAsNonUserGesture)); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_ack()); // It should commit. ASSERT_EQ(1, controller().GetEntryCount()); EXPECT_EQ(url, controller().GetLastCommittedEntry()->GetURL()); // The pending navigation should be able to commit successfully. contents()->TestDidNavigate(pending_rfh, 1, entry2_id, true, url2, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(pending_rfh, contents()->GetMainFrame()); EXPECT_EQ(2, controller().GetEntryCount()); } // Test that NavigationEntries have the correct page state after going // forward and back. Prevents regression for bug 1116137. TEST_F(WebContentsImplTest, NavigationEntryContentState) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); // Navigate to URL. There should be no committed entry yet. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); NavigationEntry* entry = controller().GetLastCommittedEntry(); EXPECT_EQ(nullptr, entry); // Committed entry should have page state after DidNavigate. orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); entry = controller().GetLastCommittedEntry(); EXPECT_TRUE(entry->GetPageState().IsValid()); // Navigate to same site. const GURL url2("http://images.google.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); entry = controller().GetLastCommittedEntry(); EXPECT_TRUE(entry->GetPageState().IsValid()); // Committed entry should have page state after DidNavigate. orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 2, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); entry = controller().GetLastCommittedEntry(); EXPECT_TRUE(entry->GetPageState().IsValid()); // Now go back. Committed entry should still have page state. controller().GoBack(); entry_id = controller().GetPendingEntry()->GetUniqueID(); orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, false, url, ui::PAGE_TRANSITION_TYPED); entry = controller().GetLastCommittedEntry(); EXPECT_TRUE(entry->GetPageState().IsValid()); } // Test that NavigationEntries have the correct page state and SiteInstance // state after opening a new window to about:blank. Prevents regression for // bugs b/1116137 and http://crbug.com/111975. TEST_F(WebContentsImplTest, NavigationEntryContentStateNewWindow) { TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); // Navigate to about:blank. const GURL url(url::kAboutBlankURL); orig_rfh->SendRendererInitiatedNavigationRequest(url, false); contents()->TestDidNavigate(orig_rfh, 1, 0, true, url, ui::PAGE_TRANSITION_TYPED); // Should have a page state here. NavigationEntry* entry = controller().GetLastCommittedEntry(); EXPECT_TRUE(entry->GetPageState().IsValid()); // The SiteInstance should be available for other navigations to use. NavigationEntryImpl* entry_impl = NavigationEntryImpl::FromNavigationEntry(entry); EXPECT_FALSE(entry_impl->site_instance()->HasSite()); int32_t site_instance_id = entry_impl->site_instance()->GetId(); // Navigating to a normal page should not cause a process swap. const GURL new_url("http://www.google.com"); controller().LoadURL(new_url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry = controller().GetPendingEntry(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 2, entry->GetUniqueID(), true, new_url, ui::PAGE_TRANSITION_TYPED); NavigationEntryImpl* entry_impl2 = NavigationEntryImpl::FromNavigationEntry( controller().GetLastCommittedEntry()); EXPECT_EQ(site_instance_id, entry_impl2->site_instance()->GetId()); EXPECT_TRUE(entry_impl2->site_instance()->HasSite()); } // Tests that fullscreen is exited throughout the object hierarchy when // navigating to a new page. TEST_F(WebContentsImplTest, NavigationExitsFullscreen) { FakeFullscreenDelegate fake_delegate; contents()->SetDelegate(&fake_delegate); TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); // Navigate to a site. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); // Toggle fullscreen mode on (as if initiated via IPC from renderer). EXPECT_FALSE(contents()->IsFullscreenForCurrentTab(test_rvh()->GetWidget())); EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents())); orig_rfh->OnMessageReceived( FrameHostMsg_ToggleFullscreen(orig_rfh->GetRoutingID(), true)); EXPECT_TRUE(contents()->IsFullscreenForCurrentTab(test_rvh()->GetWidget())); EXPECT_TRUE(fake_delegate.IsFullscreenForTabOrPending(contents())); // Navigate to a new site. const GURL url2("http://www.yahoo.com"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); contents()->GetMainFrame()->PrepareForCommit(); TestRenderFrameHost* const pending_rfh = contents()->GetPendingMainFrame(); contents()->TestDidNavigate(pending_rfh, 1, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); // Confirm fullscreen has exited. EXPECT_FALSE(contents()->IsFullscreenForCurrentTab(test_rvh()->GetWidget())); EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents())); contents()->SetDelegate(nullptr); } // Tests that fullscreen is exited throughout the object hierarchy when // instructing NavigationController to GoBack() or GoForward(). TEST_F(WebContentsImplTest, HistoryNavigationExitsFullscreen) { FakeFullscreenDelegate fake_delegate; contents()->SetDelegate(&fake_delegate); TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); // Navigate to a site. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); orig_rfh->PrepareForCommit(); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); // Now, navigate to another page on the same site. const GURL url2("http://www.google.com/search?q=kittens"); controller().LoadURL( url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); entry_id = controller().GetPendingEntry()->GetUniqueID(); orig_rfh->PrepareForCommit(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); contents()->TestDidNavigate(orig_rfh, 2, entry_id, true, url2, ui::PAGE_TRANSITION_TYPED); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); // Sanity-check: Confirm we're not starting out in fullscreen mode. EXPECT_FALSE(contents()->IsFullscreenForCurrentTab(test_rvh()->GetWidget())); EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents())); for (int i = 0; i < 2; ++i) { // Toggle fullscreen mode on (as if initiated via IPC from renderer). orig_rfh->OnMessageReceived( FrameHostMsg_ToggleFullscreen(orig_rfh->GetRoutingID(), true)); EXPECT_TRUE(contents()->IsFullscreenForCurrentTab(test_rvh()->GetWidget())); EXPECT_TRUE(fake_delegate.IsFullscreenForTabOrPending(contents())); // Navigate backward (or forward). if (i == 0) controller().GoBack(); else controller().GoForward(); entry_id = controller().GetPendingEntry()->GetUniqueID(); orig_rfh->PrepareForCommit(); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); contents()->TestDidNavigate(orig_rfh, i + 1, entry_id, false, url, ui::PAGE_TRANSITION_FORWARD_BACK); orig_rfh->SimulateNavigationStop(); // Confirm fullscreen has exited. EXPECT_FALSE( contents()->IsFullscreenForCurrentTab(test_rvh()->GetWidget())); EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents())); } contents()->SetDelegate(nullptr); } TEST_F(WebContentsImplTest, TerminateHidesValidationMessage) { FakeValidationMessageDelegate fake_delegate; contents()->SetDelegate(&fake_delegate); EXPECT_FALSE(fake_delegate.hide_validation_message_was_called()); // Initialize the RenderFrame and then simulate crashing the renderer // process. contents()->GetMainFrame()->InitializeRenderFrameIfNeeded(); contents()->GetMainFrame()->GetProcess()->SimulateCrash(); // Confirm HideValidationMessage was called. EXPECT_TRUE(fake_delegate.hide_validation_message_was_called()); contents()->SetDelegate(nullptr); } // Tests that fullscreen is exited throughout the object hierarchy on a renderer // crash. TEST_F(WebContentsImplTest, CrashExitsFullscreen) { FakeFullscreenDelegate fake_delegate; contents()->SetDelegate(&fake_delegate); // Navigate to a site. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); main_test_rfh()->PrepareForCommit(); contents()->TestDidNavigate(contents()->GetMainFrame(), 1, entry_id, true, url, ui::PAGE_TRANSITION_TYPED); // Toggle fullscreen mode on (as if initiated via IPC from renderer). EXPECT_FALSE(contents()->IsFullscreenForCurrentTab(test_rvh()->GetWidget())); EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents())); contents()->GetMainFrame()->OnMessageReceived(FrameHostMsg_ToggleFullscreen( contents()->GetMainFrame()->GetRoutingID(), true)); EXPECT_TRUE(contents()->IsFullscreenForCurrentTab(test_rvh()->GetWidget())); EXPECT_TRUE(fake_delegate.IsFullscreenForTabOrPending(contents())); // Crash the renderer. main_test_rfh()->GetProcess()->SimulateCrash(); // Confirm fullscreen has exited. EXPECT_FALSE(contents()->IsFullscreenForCurrentTab(test_rvh()->GetWidget())); EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents())); contents()->SetDelegate(nullptr); } //////////////////////////////////////////////////////////////////////////////// // Interstitial Tests //////////////////////////////////////////////////////////////////////////////// // Test navigating to a page (with the navigation initiated from the browser, // as when a URL is typed in the location bar) that shows an interstitial and // creates a new navigation entry, then hiding it without proceeding. TEST_F(WebContentsImplTest, ShowInterstitialFromBrowserWithNewNavigationDontProceed) { // Navigate to a page. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); // Initiate a browser navigation that will trigger the interstitial. controller().LoadURL(GURL("http://www.evil.com"), Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); NavigationEntry* entry = controller().GetPendingEntry(); // Show an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url2("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, url2, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); // The interstitial should not show until its navigation has committed. EXPECT_FALSE(interstitial->is_showing()); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); // Let's commit the interstitial navigation. interstitial->TestDidNavigate(1, interstitial_entry_id, true, url2); EXPECT_TRUE(interstitial->is_showing()); EXPECT_TRUE(contents()->ShowingInterstitialPage()); EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial); entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == url2); // Now don't proceed. interstitial->DontProceed(); EXPECT_EQ(TestInterstitialPage::CANCELED, state); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == url1); EXPECT_EQ(1, controller().GetEntryCount()); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test navigating to a page (with the navigation initiated from the renderer, // as when clicking on a link in the page) that shows an interstitial and // creates a new navigation entry, then hiding it without proceeding. TEST_F(WebContentsImplTest, ShowInterstitialFromRendererWithNewNavigationDontProceed) { // Navigate to a page. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); // Show an interstitial (no pending entry, the interstitial would have been // triggered by clicking on a link). TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url2("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, url2, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); // The interstitial should not show until its navigation has committed. EXPECT_FALSE(interstitial->is_showing()); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); // Let's commit the interstitial navigation. interstitial->TestDidNavigate(1, interstitial_entry_id, true, url2); EXPECT_TRUE(interstitial->is_showing()); EXPECT_TRUE(contents()->ShowingInterstitialPage()); EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial); NavigationEntry* entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == url2); // Now don't proceed. interstitial->DontProceed(); EXPECT_EQ(TestInterstitialPage::CANCELED, state); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == url1); EXPECT_EQ(1, controller().GetEntryCount()); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test navigating to a page that shows an interstitial without creating a new // navigation entry (this happens when the interstitial is triggered by a // sub-resource in the page), then hiding it without proceeding. TEST_F(WebContentsImplTest, ShowInterstitialNoNewNavigationDontProceed) { // Navigate to a page. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); // Show an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url2("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), false, url2, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); // The interstitial should not show until its navigation has committed. EXPECT_FALSE(interstitial->is_showing()); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); // Let's commit the interstitial navigation. interstitial->TestDidNavigate(1, 0, true, url2); EXPECT_TRUE(interstitial->is_showing()); EXPECT_TRUE(contents()->ShowingInterstitialPage()); EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial); NavigationEntry* entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); // The URL specified to the interstitial should have been ignored. EXPECT_TRUE(entry->GetURL() == url1); // Now don't proceed. interstitial->DontProceed(); EXPECT_EQ(TestInterstitialPage::CANCELED, state); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == url1); EXPECT_EQ(1, controller().GetEntryCount()); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test navigating to a page (with the navigation initiated from the browser, // as when a URL is typed in the location bar) that shows an interstitial and // creates a new navigation entry, then proceeding. TEST_F(WebContentsImplTest, ShowInterstitialFromBrowserNewNavigationProceed) { // Navigate to a page. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); // Initiate a browser navigation that will trigger the interstitial controller().LoadURL(GURL("http://www.evil.com"), Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); // Show an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url2("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, url2, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); // The interstitial should not show until its navigation has committed. EXPECT_FALSE(interstitial->is_showing()); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); // Let's commit the interstitial navigation. interstitial->TestDidNavigate(1, interstitial_entry_id, true, url2); EXPECT_TRUE(interstitial->is_showing()); EXPECT_TRUE(contents()->ShowingInterstitialPage()); EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial); NavigationEntry* entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == url2); // Then proceed. interstitial->Proceed(); // The interstitial should show until the new navigation commits. RunAllPendingInMessageLoop(); ASSERT_FALSE(deleted); EXPECT_EQ(TestInterstitialPage::OKED, state); EXPECT_TRUE(contents()->ShowingInterstitialPage()); EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial); // Simulate the navigation to the page, that's when the interstitial gets // hidden. GURL url3("http://www.thepage.com"); contents()->GetMainFrame()->PrepareForCommit(); contents()->GetMainFrame()->SendNavigate(2, 0, true, url3); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == url3); EXPECT_EQ(2, controller().GetEntryCount()); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test navigating to a page (with the navigation initiated from the renderer, // as when clicking on a link in the page) that shows an interstitial and // creates a new navigation entry, then proceeding. TEST_F(WebContentsImplTest, ShowInterstitialFromRendererNewNavigationProceed) { // Navigate to a page. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); // Show an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url2("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, url2, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); // The interstitial should not show until its navigation has committed. EXPECT_FALSE(interstitial->is_showing()); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); // Let's commit the interstitial navigation. interstitial->TestDidNavigate(1, interstitial_entry_id, true, url2); EXPECT_TRUE(interstitial->is_showing()); EXPECT_TRUE(contents()->ShowingInterstitialPage()); EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial); NavigationEntry* entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == url2); // Then proceed. interstitial->Proceed(); // The interstitial should show until the new navigation commits. RunAllPendingInMessageLoop(); ASSERT_FALSE(deleted); EXPECT_EQ(TestInterstitialPage::OKED, state); EXPECT_TRUE(contents()->ShowingInterstitialPage()); EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial); // Simulate the navigation to the page, that's when the interstitial gets // hidden. GURL url3("http://www.thepage.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(2, true, url3); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == url3); EXPECT_EQ(2, controller().GetEntryCount()); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test navigating to a page that shows an interstitial without creating a new // navigation entry (this happens when the interstitial is triggered by a // sub-resource in the page), then proceeding. TEST_F(WebContentsImplTest, ShowInterstitialNoNewNavigationProceed) { // Navigate to a page so we have a navigation entry in the controller. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); // Show an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url2("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), false, url2, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); // The interstitial should not show until its navigation has committed. EXPECT_FALSE(interstitial->is_showing()); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); // Let's commit the interstitial navigation. interstitial->TestDidNavigate(1, 0, true, url2); EXPECT_TRUE(interstitial->is_showing()); EXPECT_TRUE(contents()->ShowingInterstitialPage()); EXPECT_TRUE(contents()->GetInterstitialPage() == interstitial); NavigationEntry* entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); // The URL specified to the interstitial should have been ignored. EXPECT_TRUE(entry->GetURL() == url1); // Then proceed. interstitial->Proceed(); // Since this is not a new navigation, the previous page is dismissed right // away and shows the original page. EXPECT_EQ(TestInterstitialPage::OKED, state); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == url1); EXPECT_EQ(1, controller().GetEntryCount()); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test navigating to a page that shows an interstitial, then navigating away. TEST_F(WebContentsImplTest, ShowInterstitialThenNavigate) { // Show interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, url, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(1, interstitial_entry_id, true, url); // While interstitial showing, navigate to a new URL. const GURL url2("http://www.yahoo.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url2); EXPECT_EQ(TestInterstitialPage::CANCELED, state); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test navigating to a page that shows an interstitial, then going back. TEST_F(WebContentsImplTest, ShowInterstitialThenGoBack) { // Navigate to a page so we have a navigation entry in the controller. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); // Show interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL interstitial_url("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, interstitial_url, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(2, interstitial_entry_id, true, interstitial_url); EXPECT_EQ(2, controller().GetEntryCount()); // While the interstitial is showing, go back. This will dismiss the // interstitial and not initiate a navigation, but just show the existing // RenderFrameHost. controller().GoBack(); // Make sure we are back to the original page and that the interstitial is // gone. EXPECT_EQ(TestInterstitialPage::CANCELED, state); NavigationEntry* entry = controller().GetVisibleEntry(); ASSERT_TRUE(entry); EXPECT_EQ(url1.spec(), entry->GetURL().spec()); EXPECT_EQ(1, controller().GetEntryCount()); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test navigating to a page that shows an interstitial, has a renderer crash, // and then goes back. TEST_F(WebContentsImplTest, ShowInterstitialCrashRendererThenGoBack) { // Navigate to a page so we have a navigation entry in the controller. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); NavigationEntry* entry = controller().GetLastCommittedEntry(); // Show interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL interstitial_url("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, interstitial_url, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(2, interstitial_entry_id, true, interstitial_url); // Crash the renderer contents()->GetMainFrame()->GetProcess()->SimulateCrash(); // While the interstitial is showing, go back. This will dismiss the // interstitial and not initiate a navigation, but just show the existing // RenderFrameHost. controller().GoBack(); // Make sure we are back to the original page and that the interstitial is // gone. EXPECT_EQ(TestInterstitialPage::CANCELED, state); entry = controller().GetVisibleEntry(); ASSERT_TRUE(entry); EXPECT_EQ(url1.spec(), entry->GetURL().spec()); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test navigating to a page that shows an interstitial, has the renderer crash, // and then navigates to the interstitial. TEST_F(WebContentsImplTest, ShowInterstitialCrashRendererThenNavigate) { // Navigate to a page so we have a navigation entry in the controller. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); // Show interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL interstitial_url("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, interstitial_url, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); // Crash the renderer contents()->GetMainFrame()->GetProcess()->SimulateCrash(); interstitial->TestDidNavigate(2, interstitial_entry_id, true, interstitial_url); } // Test navigating to a page that shows an interstitial, then close the // contents. TEST_F(WebContentsImplTest, ShowInterstitialThenCloseTab) { // Show interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, url, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(1, interstitial_entry_id, true, url); // Now close the contents. DeleteContents(); EXPECT_EQ(TestInterstitialPage::CANCELED, state); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test navigating to a page that shows an interstitial, then close the // contents. TEST_F(WebContentsImplTest, ShowInterstitialThenCloseAndShutdown) { // Show interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, url, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(1, interstitial_entry_id, true, url); TestRenderFrameHost* rfh = static_cast(interstitial->GetMainFrame()); // Now close the contents. DeleteContents(); EXPECT_EQ(TestInterstitialPage::CANCELED, state); // Before the interstitial has a chance to process its shutdown task, // simulate quitting the browser. This goes through all processes and // tells them to destruct. rfh->GetProcess()->SimulateCrash(); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test that after Proceed is called and an interstitial is still shown, no more // commands get executed. TEST_F(WebContentsImplTest, ShowInterstitialProceedMultipleCommands) { // Navigate to a page so we have a navigation entry in the controller. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); // Show an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url2("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, url2, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(1, interstitial_entry_id, true, url2); // Run a command. EXPECT_EQ(0, interstitial->command_received_count()); interstitial->TestDomOperationResponse("toto"); EXPECT_EQ(1, interstitial->command_received_count()); // Then proceed. interstitial->Proceed(); RunAllPendingInMessageLoop(); ASSERT_FALSE(deleted); // While the navigation to the new page is pending, send other commands, they // should be ignored. interstitial->TestDomOperationResponse("hello"); interstitial->TestDomOperationResponse("hi"); EXPECT_EQ(1, interstitial->command_received_count()); } // Test showing an interstitial while another interstitial is already showing. TEST_F(WebContentsImplTest, ShowInterstitialOnInterstitial) { // Navigate to a page so we have a navigation entry in the controller. GURL start_url("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, start_url); EXPECT_EQ(1, controller().GetEntryCount()); // Show an interstitial. TestInterstitialPage::InterstitialState state1 = TestInterstitialPage::INVALID; bool deleted1 = false; GURL url1("http://interstitial1"); TestInterstitialPage* interstitial1 = new TestInterstitialPage(contents(), true, url1, &state1, &deleted1); TestInterstitialPageStateGuard state_guard1(interstitial1); interstitial1->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial1->TestDidNavigate(1, interstitial_entry_id, true, url1); // Now show another interstitial. TestInterstitialPage::InterstitialState state2 = TestInterstitialPage::INVALID; bool deleted2 = false; GURL url2("http://interstitial2"); TestInterstitialPage* interstitial2 = new TestInterstitialPage(contents(), true, url2, &state2, &deleted2); TestInterstitialPageStateGuard state_guard2(interstitial2); interstitial2->Show(); interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial2->TestDidNavigate(1, interstitial_entry_id, true, url2); // Showing interstitial2 should have caused interstitial1 to go away. EXPECT_EQ(TestInterstitialPage::CANCELED, state1); EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted1); ASSERT_FALSE(deleted2); // Let's make sure interstitial2 is working as intended. interstitial2->Proceed(); GURL landing_url("http://www.thepage.com"); contents()->GetMainFrame()->SendNavigate(2, 0, true, landing_url); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); NavigationEntry* entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == landing_url); EXPECT_EQ(2, controller().GetEntryCount()); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted2); } // Test showing an interstitial, proceeding and then navigating to another // interstitial. TEST_F(WebContentsImplTest, ShowInterstitialProceedShowInterstitial) { // Navigate to a page so we have a navigation entry in the controller. GURL start_url("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, start_url); EXPECT_EQ(1, controller().GetEntryCount()); // Show an interstitial. TestInterstitialPage::InterstitialState state1 = TestInterstitialPage::INVALID; bool deleted1 = false; GURL url1("http://interstitial1"); TestInterstitialPage* interstitial1 = new TestInterstitialPage(contents(), true, url1, &state1, &deleted1); TestInterstitialPageStateGuard state_guard1(interstitial1); interstitial1->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial1->TestDidNavigate(1, interstitial_entry_id, true, url1); // Take action. The interstitial won't be hidden until the navigation is // committed. interstitial1->Proceed(); EXPECT_EQ(TestInterstitialPage::OKED, state1); // Now show another interstitial (simulating the navigation causing another // interstitial). TestInterstitialPage::InterstitialState state2 = TestInterstitialPage::INVALID; bool deleted2 = false; GURL url2("http://interstitial2"); TestInterstitialPage* interstitial2 = new TestInterstitialPage(contents(), true, url2, &state2, &deleted2); TestInterstitialPageStateGuard state_guard2(interstitial2); interstitial2->Show(); interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial2->TestDidNavigate(1, interstitial_entry_id, true, url2); // Showing interstitial2 should have caused interstitial1 to go away. EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted1); ASSERT_FALSE(deleted2); // Let's make sure interstitial2 is working as intended. interstitial2->Proceed(); GURL landing_url("http://www.thepage.com"); contents()->GetMainFrame()->SendNavigate(2, 0, true, landing_url); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted2); EXPECT_FALSE(contents()->ShowingInterstitialPage()); EXPECT_EQ(nullptr, contents()->GetInterstitialPage()); NavigationEntry* entry = controller().GetVisibleEntry(); ASSERT_NE(nullptr, entry); EXPECT_TRUE(entry->GetURL() == landing_url); EXPECT_EQ(2, controller().GetEntryCount()); } // Test that navigating away from an interstitial while it's loading cause it // not to show. TEST_F(WebContentsImplTest, NavigateBeforeInterstitialShows) { // Show an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL interstitial_url("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, interstitial_url, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); // Let's simulate a navigation initiated from the browser before the // interstitial finishes loading. const GURL url("http://www.google.com"); controller().LoadURL( url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); EXPECT_FALSE(interstitial->is_showing()); RunAllPendingInMessageLoop(); ASSERT_FALSE(deleted); // Now let's make the interstitial navigation commit. interstitial->TestDidNavigate(1, interstitial_entry_id, true, interstitial_url); // After it loaded the interstitial should be gone. EXPECT_EQ(TestInterstitialPage::CANCELED, state); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Test that a new request to show an interstitial while an interstitial is // pending does not cause problems. htp://crbug/29655 and htp://crbug/9442. TEST_F(WebContentsImplTest, TwoQuickInterstitials) { GURL interstitial_url("http://interstitial"); // Show a first interstitial. TestInterstitialPage::InterstitialState state1 = TestInterstitialPage::INVALID; bool deleted1 = false; TestInterstitialPage* interstitial1 = new TestInterstitialPage(contents(), true, interstitial_url, &state1, &deleted1); TestInterstitialPageStateGuard state_guard1(interstitial1); interstitial1->Show(); // Show another interstitial on that same contents before the first one had // time to load. TestInterstitialPage::InterstitialState state2 = TestInterstitialPage::INVALID; bool deleted2 = false; TestInterstitialPage* interstitial2 = new TestInterstitialPage(contents(), true, interstitial_url, &state2, &deleted2); TestInterstitialPageStateGuard state_guard2(interstitial2); interstitial2->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); // The first interstitial should have been closed and deleted. EXPECT_EQ(TestInterstitialPage::CANCELED, state1); // The 2nd one should still be OK. EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted1); ASSERT_FALSE(deleted2); // Make the interstitial navigation commit it should be showing. interstitial2->TestDidNavigate(1, interstitial_entry_id, true, interstitial_url); EXPECT_EQ(interstitial2, contents()->GetInterstitialPage()); } // Test showing an interstitial and have its renderer crash. TEST_F(WebContentsImplTest, InterstitialCrasher) { // Show an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, url, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); // Simulate a renderer crash before the interstitial is shown. interstitial->TestRenderViewTerminated( base::TERMINATION_STATUS_PROCESS_CRASHED, -1); // The interstitial should have been dismissed. EXPECT_EQ(TestInterstitialPage::CANCELED, state); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); // Now try again but this time crash the intersitial after it was shown. interstitial = new TestInterstitialPage(contents(), true, url, &state, &deleted); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(1, interstitial_entry_id, true, url); // Simulate a renderer crash. interstitial->TestRenderViewTerminated( base::TERMINATION_STATUS_PROCESS_CRASHED, -1); // The interstitial should have been dismissed. EXPECT_EQ(TestInterstitialPage::CANCELED, state); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); } // Tests that showing an interstitial as a result of a browser initiated // navigation while an interstitial is showing does not remove the pending // entry (see http://crbug.com/9791). TEST_F(WebContentsImplTest, NewInterstitialDoesNotCancelPendingEntry) { const char kUrl[] = "http://www.badguys.com/"; const GURL kGURL(kUrl); // Start a navigation to a page contents()->GetController().LoadURL( kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); // Simulate that navigation triggering an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, kGURL, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(1, interstitial_entry_id, true, kGURL); // Initiate a new navigation from the browser that also triggers an // interstitial. contents()->GetController().LoadURL( kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); TestInterstitialPage::InterstitialState state2 = TestInterstitialPage::INVALID; bool deleted2 = false; TestInterstitialPage* interstitial2 = new TestInterstitialPage(contents(), true, kGURL, &state2, &deleted2); TestInterstitialPageStateGuard state_guard2(interstitial2); interstitial2->Show(); interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial2->TestDidNavigate(1, interstitial_entry_id, true, kGURL); // Make sure we still have an entry. NavigationEntry* entry = contents()->GetController().GetPendingEntry(); ASSERT_TRUE(entry); EXPECT_EQ(kUrl, entry->GetURL().spec()); // And that the first interstitial is gone, but not the second. EXPECT_EQ(TestInterstitialPage::CANCELED, state); EXPECT_EQ(TestInterstitialPage::UNDECIDED, state2); RunAllPendingInMessageLoop(); EXPECT_TRUE(deleted); EXPECT_FALSE(deleted2); } // Tests that Javascript messages are not shown while an interstitial is // showing. TEST_F(WebContentsImplTest, NoJSMessageOnInterstitials) { const char kUrl[] = "http://www.badguys.com/"; const GURL kGURL(kUrl); // Start a navigation to a page contents()->GetController().LoadURL( kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); main_test_rfh()->PrepareForCommit(); // DidNavigate from the page contents()->TestDidNavigate(contents()->GetMainFrame(), 1, entry_id, true, kGURL, ui::PAGE_TRANSITION_TYPED); // Simulate showing an interstitial while the page is showing. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, kGURL, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(1, interstitial_entry_id, true, kGURL); // While the interstitial is showing, let's simulate the hidden page // attempting to show a JS message. IPC::Message* dummy_message = new IPC::Message; contents()->RunJavaScriptMessage(contents()->GetMainFrame(), base::ASCIIToUTF16("This is an informative message"), base::ASCIIToUTF16("OK"), kGURL, JAVASCRIPT_MESSAGE_TYPE_ALERT, dummy_message); EXPECT_TRUE(contents()->last_dialog_suppressed_); } // Makes sure that if the source passed to CopyStateFromAndPrune has an // interstitial it isn't copied over to the destination. TEST_F(WebContentsImplTest, CopyStateFromAndPruneSourceInterstitial) { // Navigate to a page. GURL url1("http://www.google.com"); main_test_rfh()->NavigateAndCommitRendererInitiated(1, true, url1); EXPECT_EQ(1, controller().GetEntryCount()); // Initiate a browser navigation that will trigger the interstitial controller().LoadURL(GURL("http://www.evil.com"), Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); // Show an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url2("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(contents(), true, url2, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(1, interstitial_entry_id, true, url2); EXPECT_TRUE(interstitial->is_showing()); EXPECT_EQ(2, controller().GetEntryCount()); // Create another NavigationController. GURL url3("http://foo2"); scoped_ptr other_contents( static_cast(CreateTestWebContents())); NavigationControllerImpl& other_controller = other_contents->GetController(); other_contents->NavigateAndCommit(url3); other_contents->ExpectSetHistoryOffsetAndLength(1, 2); other_controller.CopyStateFromAndPrune(&controller(), false); // The merged controller should only have two entries: url1 and url2. ASSERT_EQ(2, other_controller.GetEntryCount()); EXPECT_EQ(1, other_controller.GetCurrentEntryIndex()); EXPECT_EQ(url1, other_controller.GetEntryAtIndex(0)->GetURL()); EXPECT_EQ(url3, other_controller.GetEntryAtIndex(1)->GetURL()); // And the merged controller shouldn't be showing an interstitial. EXPECT_FALSE(other_contents->ShowingInterstitialPage()); } // Makes sure that CopyStateFromAndPrune cannot be called if the target is // showing an interstitial. TEST_F(WebContentsImplTest, CopyStateFromAndPruneTargetInterstitial) { // Navigate to a page. GURL url1("http://www.google.com"); contents()->NavigateAndCommit(url1); // Create another NavigationController. scoped_ptr other_contents( static_cast(CreateTestWebContents())); NavigationControllerImpl& other_controller = other_contents->GetController(); // Navigate it to url2. GURL url2("http://foo2"); other_contents->NavigateAndCommit(url2); // Show an interstitial. TestInterstitialPage::InterstitialState state = TestInterstitialPage::INVALID; bool deleted = false; GURL url3("http://interstitial"); TestInterstitialPage* interstitial = new TestInterstitialPage(other_contents.get(), true, url3, &state, &deleted); TestInterstitialPageStateGuard state_guard(interstitial); interstitial->Show(); int interstitial_entry_id = other_controller.GetTransientEntry()->GetUniqueID(); interstitial->TestDidNavigate(1, interstitial_entry_id, true, url3); EXPECT_TRUE(interstitial->is_showing()); EXPECT_EQ(2, other_controller.GetEntryCount()); // Ensure that we do not allow calling CopyStateFromAndPrune when an // interstitial is showing in the target. EXPECT_FALSE(other_controller.CanPruneAllButLastCommitted()); } // Regression test for http://crbug.com/168611 - the URLs passed by the // DidFinishLoad and DidFailLoadWithError IPCs should get filtered. TEST_F(WebContentsImplTest, FilterURLs) { TestWebContentsObserver observer(contents()); // A navigation to about:whatever should always look like a navigation to // about:blank GURL url_normalized(url::kAboutBlankURL); GURL url_from_ipc("about:whatever"); // We navigate the test WebContents to about:blank, since NavigateAndCommit // will use the given URL to create the NavigationEntry as well, and that // entry should contain the filtered URL. contents()->NavigateAndCommit(url_normalized); // Check that an IPC with about:whatever is correctly normalized. contents()->TestDidFinishLoad(url_from_ipc); EXPECT_EQ(url_normalized, observer.last_url()); // Create and navigate another WebContents. scoped_ptr other_contents( static_cast(CreateTestWebContents())); TestWebContentsObserver other_observer(other_contents.get()); other_contents->NavigateAndCommit(url_normalized); // Check that an IPC with about:whatever is correctly normalized. other_contents->TestDidFailLoadWithError( url_from_ipc, 1, base::string16(), false); EXPECT_EQ(url_normalized, other_observer.last_url()); } // Test that if a pending contents is deleted before it is shown, we don't // crash. TEST_F(WebContentsImplTest, PendingContents) { scoped_ptr other_contents( static_cast(CreateTestWebContents())); contents()->AddPendingContents(other_contents.get()); int route_id = other_contents->GetRenderViewHost()->GetRoutingID(); other_contents.reset(); EXPECT_EQ(nullptr, contents()->GetCreatedWindow(route_id)); } TEST_F(WebContentsImplTest, CapturerOverridesPreferredSize) { const gfx::Size original_preferred_size(1024, 768); contents()->UpdatePreferredSize(original_preferred_size); // With no capturers, expect the preferred size to be the one propagated into // WebContentsImpl via the RenderViewHostDelegate interface. EXPECT_EQ(contents()->GetCapturerCount(), 0); EXPECT_EQ(original_preferred_size, contents()->GetPreferredSize()); // Increment capturer count, but without specifying a capture size. Expect // a "not set" preferred size. contents()->IncrementCapturerCount(gfx::Size()); EXPECT_EQ(1, contents()->GetCapturerCount()); EXPECT_EQ(gfx::Size(), contents()->GetPreferredSize()); // Increment capturer count again, but with an overriding capture size. // Expect preferred size to now be overridden to the capture size. const gfx::Size capture_size(1280, 720); contents()->IncrementCapturerCount(capture_size); EXPECT_EQ(2, contents()->GetCapturerCount()); EXPECT_EQ(capture_size, contents()->GetPreferredSize()); // Increment capturer count a third time, but the expect that the preferred // size is still the first capture size. const gfx::Size another_capture_size(720, 480); contents()->IncrementCapturerCount(another_capture_size); EXPECT_EQ(3, contents()->GetCapturerCount()); EXPECT_EQ(capture_size, contents()->GetPreferredSize()); // Decrement capturer count twice, but expect the preferred size to still be // overridden. contents()->DecrementCapturerCount(); contents()->DecrementCapturerCount(); EXPECT_EQ(1, contents()->GetCapturerCount()); EXPECT_EQ(capture_size, contents()->GetPreferredSize()); // Decrement capturer count, and since the count has dropped to zero, the // original preferred size should be restored. contents()->DecrementCapturerCount(); EXPECT_EQ(0, contents()->GetCapturerCount()); EXPECT_EQ(original_preferred_size, contents()->GetPreferredSize()); } TEST_F(WebContentsImplTest, CapturerPreventsHiding) { const gfx::Size original_preferred_size(1024, 768); contents()->UpdatePreferredSize(original_preferred_size); TestRenderWidgetHostView* view = static_cast( contents()->GetMainFrame()->GetRenderViewHost()->GetWidget()->GetView()); // With no capturers, setting and un-setting occlusion should change the // view's occlusion state. EXPECT_FALSE(view->is_showing()); contents()->WasShown(); EXPECT_TRUE(view->is_showing()); contents()->WasHidden(); EXPECT_FALSE(view->is_showing()); contents()->WasShown(); EXPECT_TRUE(view->is_showing()); // Add a capturer and try to hide the contents. The view will remain visible. contents()->IncrementCapturerCount(gfx::Size()); contents()->WasHidden(); EXPECT_TRUE(view->is_showing()); // Remove the capturer, and the WasHidden should take effect. contents()->DecrementCapturerCount(); EXPECT_FALSE(view->is_showing()); } TEST_F(WebContentsImplTest, CapturerPreventsOcclusion) { const gfx::Size original_preferred_size(1024, 768); contents()->UpdatePreferredSize(original_preferred_size); TestRenderWidgetHostView* view = static_cast( contents()->GetMainFrame()->GetRenderViewHost()->GetWidget()->GetView()); // With no capturers, setting and un-setting occlusion should change the // view's occlusion state. EXPECT_FALSE(view->is_occluded()); contents()->WasOccluded(); EXPECT_TRUE(view->is_occluded()); contents()->WasUnOccluded(); EXPECT_FALSE(view->is_occluded()); contents()->WasOccluded(); EXPECT_TRUE(view->is_occluded()); // Add a capturer. This should cause the view to be un-occluded. contents()->IncrementCapturerCount(gfx::Size()); EXPECT_FALSE(view->is_occluded()); // Try to occlude the view. This will fail to propagate because of the // active capturer. contents()->WasOccluded(); EXPECT_FALSE(view->is_occluded()); // Remove the capturer and try again. contents()->DecrementCapturerCount(); EXPECT_FALSE(view->is_occluded()); contents()->WasOccluded(); EXPECT_TRUE(view->is_occluded()); } // Tests that GetLastActiveTime starts with a real, non-zero time and updates // on activity. TEST_F(WebContentsImplTest, GetLastActiveTime) { // The WebContents starts with a valid creation time. EXPECT_FALSE(contents()->GetLastActiveTime().is_null()); // Reset the last active time to a known-bad value. contents()->last_active_time_ = base::TimeTicks(); ASSERT_TRUE(contents()->GetLastActiveTime().is_null()); // Simulate activating the WebContents. The active time should update. contents()->WasShown(); EXPECT_FALSE(contents()->GetLastActiveTime().is_null()); } class ContentsZoomChangedDelegate : public WebContentsDelegate { public: ContentsZoomChangedDelegate() : contents_zoom_changed_call_count_(0), last_zoom_in_(false) { } int GetAndResetContentsZoomChangedCallCount() { int count = contents_zoom_changed_call_count_; contents_zoom_changed_call_count_ = 0; return count; } bool last_zoom_in() const { return last_zoom_in_; } // WebContentsDelegate: void ContentsZoomChange(bool zoom_in) override { contents_zoom_changed_call_count_++; last_zoom_in_ = zoom_in; } private: int contents_zoom_changed_call_count_; bool last_zoom_in_; DISALLOW_COPY_AND_ASSIGN(ContentsZoomChangedDelegate); }; // Tests that some mouseehweel events get turned into browser zoom requests. TEST_F(WebContentsImplTest, HandleWheelEvent) { using blink::WebInputEvent; scoped_ptr delegate( new ContentsZoomChangedDelegate()); contents()->SetDelegate(delegate.get()); int modifiers = 0; // Verify that normal mouse wheel events do nothing to change the zoom level. blink::WebMouseWheelEvent event = SyntheticWebMouseWheelEventBuilder::Build(0, 0, 0, 1, modifiers, false); EXPECT_FALSE(contents()->HandleWheelEvent(event)); EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount()); modifiers = WebInputEvent::ShiftKey | WebInputEvent::AltKey | WebInputEvent::ControlKey; event = SyntheticWebMouseWheelEventBuilder::Build(0, 0, 0, 1, modifiers, false); EXPECT_FALSE(contents()->HandleWheelEvent(event)); EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount()); // But whenever the ctrl modifier is applied with canScroll=false, they can // increase/decrease zoom. Except on MacOS where we never want to adjust zoom // with mousewheel. modifiers = WebInputEvent::ControlKey; event = SyntheticWebMouseWheelEventBuilder::Build(0, 0, 0, 1, modifiers, false); event.canScroll = false; bool handled = contents()->HandleWheelEvent(event); #if defined(OS_MACOSX) EXPECT_FALSE(handled); EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount()); #else EXPECT_TRUE(handled); EXPECT_EQ(1, delegate->GetAndResetContentsZoomChangedCallCount()); EXPECT_TRUE(delegate->last_zoom_in()); #endif modifiers = WebInputEvent::ControlKey | WebInputEvent::ShiftKey | WebInputEvent::AltKey; event = SyntheticWebMouseWheelEventBuilder::Build(0, 0, 2, -5, modifiers, false); event.canScroll = false; handled = contents()->HandleWheelEvent(event); #if defined(OS_MACOSX) EXPECT_FALSE(handled); EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount()); #else EXPECT_TRUE(handled); EXPECT_EQ(1, delegate->GetAndResetContentsZoomChangedCallCount()); EXPECT_FALSE(delegate->last_zoom_in()); #endif // Unless there is no vertical movement. event = SyntheticWebMouseWheelEventBuilder::Build(0, 0, 2, 0, modifiers, false); EXPECT_FALSE(contents()->HandleWheelEvent(event)); EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount()); // Events containing precise scrolling deltas also shouldn't result in the // zoom being adjusted, to avoid accidental adjustments caused by // two-finger-scrolling on a touchpad. modifiers = WebInputEvent::ControlKey; event = SyntheticWebMouseWheelEventBuilder::Build(0, 0, 0, 5, modifiers, true); EXPECT_FALSE(contents()->HandleWheelEvent(event)); EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount()); // Ensure pointers to the delegate aren't kept beyond its lifetime. contents()->SetDelegate(nullptr); } // Tests that GetRelatedActiveContentsCount is shared between related // SiteInstances and includes WebContents that have not navigated yet. TEST_F(WebContentsImplTest, ActiveContentsCountBasic) { scoped_refptr instance1( SiteInstance::CreateForURL(browser_context(), GURL("http://a.com"))); scoped_refptr instance2( instance1->GetRelatedSiteInstance(GURL("http://b.com"))); EXPECT_EQ(0u, instance1->GetRelatedActiveContentsCount()); EXPECT_EQ(0u, instance2->GetRelatedActiveContentsCount()); scoped_ptr contents1( TestWebContents::Create(browser_context(), instance1.get())); EXPECT_EQ(1u, instance1->GetRelatedActiveContentsCount()); EXPECT_EQ(1u, instance2->GetRelatedActiveContentsCount()); scoped_ptr contents2( TestWebContents::Create(browser_context(), instance1.get())); EXPECT_EQ(2u, instance1->GetRelatedActiveContentsCount()); EXPECT_EQ(2u, instance2->GetRelatedActiveContentsCount()); contents1.reset(); EXPECT_EQ(1u, instance1->GetRelatedActiveContentsCount()); EXPECT_EQ(1u, instance2->GetRelatedActiveContentsCount()); contents2.reset(); EXPECT_EQ(0u, instance1->GetRelatedActiveContentsCount()); EXPECT_EQ(0u, instance2->GetRelatedActiveContentsCount()); } // Tests that GetRelatedActiveContentsCount is preserved correctly across // same-site and cross-site navigations. TEST_F(WebContentsImplTest, ActiveContentsCountNavigate) { scoped_refptr instance( SiteInstance::Create(browser_context())); EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount()); scoped_ptr contents( TestWebContents::Create(browser_context(), instance.get())); EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); // Navigate to a URL. contents->GetController().LoadURL(GURL("http://a.com/1"), Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); contents->CommitPendingNavigation(); EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); // Navigate to a URL in the same site. contents->GetController().LoadURL(GURL("http://a.com/2"), Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); contents->CommitPendingNavigation(); EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); // Navigate to a URL in a different site. const GURL kUrl = GURL("http://b.com"); contents->GetController().LoadURL(kUrl, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = contents->GetController().GetPendingEntry()->GetUniqueID(); if (IsBrowserSideNavigationEnabled()) contents->GetMainFrame()->PrepareForCommit(); EXPECT_TRUE(contents->CrossProcessNavigationPending()); EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); contents->GetPendingMainFrame()->SendNavigate(1, entry_id, true, kUrl); EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); contents.reset(); EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount()); } // Tests that GetRelatedActiveContentsCount tracks BrowsingInstance changes // from WebUI. TEST_F(WebContentsImplTest, ActiveContentsCountChangeBrowsingInstance) { scoped_refptr instance( SiteInstance::Create(browser_context())); EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount()); scoped_ptr contents( TestWebContents::Create(browser_context(), instance.get())); EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); // Navigate to a URL. contents->NavigateAndCommit(GURL("http://a.com")); EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); // Navigate to a URL which sort of looks like a chrome:// url. contents->NavigateAndCommit(GURL("http://gpu")); EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); // Navigate to a URL with WebUI. This will change BrowsingInstances. const GURL kWebUIUrl = GURL("chrome://gpu"); contents->GetController().LoadURL(kWebUIUrl, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = contents->GetController().GetPendingEntry()->GetUniqueID(); contents->GetMainFrame()->PrepareForCommit(); EXPECT_TRUE(contents->CrossProcessNavigationPending()); scoped_refptr instance_webui( contents->GetPendingMainFrame()->GetSiteInstance()); EXPECT_FALSE(instance->IsRelatedSiteInstance(instance_webui.get())); // At this point, contents still counts for the old BrowsingInstance. EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount()); EXPECT_EQ(0u, instance_webui->GetRelatedActiveContentsCount()); // Commit and contents counts for the new one. contents->GetPendingMainFrame()->SendNavigate(1, entry_id, true, kWebUIUrl); EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount()); EXPECT_EQ(1u, instance_webui->GetRelatedActiveContentsCount()); contents.reset(); EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount()); EXPECT_EQ(0u, instance_webui->GetRelatedActiveContentsCount()); } class LoadingWebContentsObserver : public WebContentsObserver { public: explicit LoadingWebContentsObserver(WebContents* contents) : WebContentsObserver(contents), is_loading_(false) { } ~LoadingWebContentsObserver() override {} void DidStartLoading() override { is_loading_ = true; } void DidStopLoading() override { is_loading_ = false; } bool is_loading() const { return is_loading_; } private: bool is_loading_; DISALLOW_COPY_AND_ASSIGN(LoadingWebContentsObserver); }; // Subclass of WebContentsImplTest for cases that need out-of-process iframes. class WebContentsImplTestWithSiteIsolation : public WebContentsImplTest { public: WebContentsImplTestWithSiteIsolation() { IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); } }; // Ensure that DidStartLoading/DidStopLoading events balance out properly with // interleaving cross-process navigations in multiple subframes. // See https://crbug.com/448601 for details of the underlying issue. The // sequence of events that reproduce it are as follows: // * Navigate top-level frame with one subframe. // * Subframe navigates more than once before the top-level frame has had a // chance to complete the load. // The subframe navigations cause the loading_frames_in_progress_ to drop down // to 0, while the loading_progresses_ map is not reset. TEST_F(WebContentsImplTestWithSiteIsolation, StartStopEventsBalance) { // The bug manifests itself in regular mode as well, but browser-initiated // navigation of subframes is only possible in --site-per-process mode within // unit tests. const GURL initial_url("about:blank"); const GURL main_url("http://www.chromium.org"); const GURL foo_url("http://foo.chromium.org"); const GURL bar_url("http://bar.chromium.org"); TestRenderFrameHost* orig_rfh = contents()->GetMainFrame(); // Use a WebContentsObserver to approximate the behavior of the tab's spinner. LoadingWebContentsObserver observer(contents()); // Navigate the main RenderFrame, simulate the DidStartLoading, and commit. // The frame should still be loading. controller().LoadURL( main_url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); orig_rfh->PrepareForCommit(); orig_rfh->OnMessageReceived( FrameHostMsg_DidStartLoading(orig_rfh->GetRoutingID(), false)); contents()->TestDidNavigate(orig_rfh, 1, entry_id, true, main_url, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE(contents()->CrossProcessNavigationPending()); EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); EXPECT_TRUE(contents()->IsLoading()); EXPECT_TRUE(observer.is_loading()); // Create a child frame to navigate multiple times. TestRenderFrameHost* subframe = orig_rfh->AppendChild("subframe"); // Navigate the child frame to about:blank, which will send both // DidStartLoading and DidStopLoading messages. { subframe->SendRendererInitiatedNavigationRequest(initial_url, false); subframe->OnMessageReceived( FrameHostMsg_DidStartLoading(subframe->GetRoutingID(), true)); subframe->SendNavigateWithTransition(1, 0, false, initial_url, ui::PAGE_TRANSITION_AUTO_SUBFRAME); subframe->OnMessageReceived( FrameHostMsg_DidStopLoading(subframe->GetRoutingID())); } // Navigate the frame to another URL, which will send again // DidStartLoading and DidStopLoading messages. { subframe->SendRendererInitiatedNavigationRequest(foo_url, false); subframe->PrepareForCommit(); subframe->OnMessageReceived( FrameHostMsg_DidStartLoading(subframe->GetRoutingID(), true)); subframe->SendNavigateWithTransition(1, 0, false, foo_url, ui::PAGE_TRANSITION_AUTO_SUBFRAME); subframe->OnMessageReceived( FrameHostMsg_DidStopLoading(subframe->GetRoutingID())); } // Since the main frame hasn't sent any DidStopLoading messages, it is // expected that the WebContents is still in loading state. EXPECT_TRUE(contents()->IsLoading()); EXPECT_TRUE(observer.is_loading()); // Navigate the frame again, this time using LoadURLWithParams. This causes // RenderFrameHost to call into WebContents::DidStartLoading, which starts // the spinner. { NavigationController::LoadURLParams load_params(bar_url); load_params.referrer = Referrer(GURL("http://referrer"), blink::WebReferrerPolicyDefault); load_params.transition_type = ui::PAGE_TRANSITION_GENERATED; load_params.extra_headers = "content-type: text/plain"; load_params.load_type = NavigationController::LOAD_TYPE_DEFAULT; load_params.is_renderer_initiated = false; load_params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE; load_params.frame_tree_node_id = subframe->frame_tree_node()->frame_tree_node_id(); controller().LoadURLWithParams(load_params); entry_id = controller().GetPendingEntry()->GetUniqueID(); subframe->OnMessageReceived( FrameHostMsg_DidStartLoading(subframe->GetRoutingID(), true)); // Commit the navigation in the child frame and send the DidStopLoading // message. subframe->PrepareForCommit(); contents()->TestDidNavigate(subframe, 3, entry_id, true, bar_url, ui::PAGE_TRANSITION_MANUAL_SUBFRAME); subframe->OnMessageReceived( FrameHostMsg_DidStopLoading(subframe->GetRoutingID())); } // At this point the status should still be loading, since the main frame // hasn't sent the DidstopLoading message yet. EXPECT_TRUE(contents()->IsLoading()); EXPECT_TRUE(observer.is_loading()); // Send the DidStopLoading for the main frame and ensure it isn't loading // anymore. orig_rfh->OnMessageReceived( FrameHostMsg_DidStopLoading(orig_rfh->GetRoutingID())); EXPECT_FALSE(contents()->IsLoading()); EXPECT_FALSE(observer.is_loading()); } // Ensure that WebContentsImpl does not stop loading too early when there still // is a pending renderer. This can happen if a same-process non user-initiated // navigation completes while there is an ongoing cross-process navigation. // TODO(fdegans): Rewrite the test for PlzNavigate when DidStartLoading and // DidStopLoading are properly called. TEST_F(WebContentsImplTest, NoEarlyStop) { const GURL kUrl1("http://www.chromium.org"); const GURL kUrl2("http://www.google.com"); const GURL kUrl3("http://www.wikipedia.org"); contents()->NavigateAndCommit(kUrl1); TestRenderFrameHost* current_rfh = contents()->GetMainFrame(); // Start a browser-initiated cross-process navigation to |kUrl2|. There should // be a pending RenderFrameHost and the WebContents should be loading. controller().LoadURL( kUrl2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); int entry_id = controller().GetPendingEntry()->GetUniqueID(); EXPECT_TRUE(contents()->CrossProcessNavigationPending()); TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame(); ASSERT_TRUE(pending_rfh); EXPECT_TRUE(contents()->IsLoading()); // The current RenderFrameHost starts a non user-initiated render-initiated // navigation and sends a DidStartLoading IPC. The WebContents should still be // loading. current_rfh->OnMessageReceived( FrameHostMsg_DidStartLoading(current_rfh->GetRoutingID(), false)); EXPECT_TRUE(contents()->IsLoading()); // Simulate the pending RenderFrameHost DidStartLoading. There should still be // a pending RenderFrameHost and the WebContents should still be loading. pending_rfh->PrepareForCommit(); pending_rfh->OnMessageReceived( FrameHostMsg_DidStartLoading(pending_rfh->GetRoutingID(), false)); EXPECT_EQ(contents()->GetPendingMainFrame(), pending_rfh); EXPECT_TRUE(contents()->IsLoading()); // Simulate the commit and DidStopLoading from the renderer-initiated // navigation in the current RenderFrameHost. There should still be a pending // RenderFrameHost and the WebContents should still be loading. current_rfh->SendNavigateWithModificationCallback( 1, 0, true, kUrl3, base::Bind(SetAsNonUserGesture)); current_rfh->OnMessageReceived( FrameHostMsg_DidStopLoading(current_rfh->GetRoutingID())); EXPECT_EQ(contents()->GetPendingMainFrame(), pending_rfh); EXPECT_TRUE(contents()->IsLoading()); // It should commit. ASSERT_EQ(2, controller().GetEntryCount()); EXPECT_EQ(kUrl3, controller().GetLastCommittedEntry()->GetURL()); // Commit the navigation. The formerly pending RenderFrameHost should now be // the current RenderFrameHost and the WebContents should still be loading. contents()->TestDidNavigate(pending_rfh, 1, entry_id, true, kUrl2, ui::PAGE_TRANSITION_TYPED); EXPECT_FALSE(contents()->GetPendingMainFrame()); TestRenderFrameHost* new_current_rfh = contents()->GetMainFrame(); EXPECT_EQ(new_current_rfh, pending_rfh); EXPECT_TRUE(contents()->IsLoading()); EXPECT_EQ(3, controller().GetEntryCount()); // Simulate the new current RenderFrameHost DidStopLoading. The WebContents // should now have stopped loading. new_current_rfh->OnMessageReceived( FrameHostMsg_DidStopLoading(new_current_rfh->GetRoutingID())); EXPECT_EQ(contents()->GetMainFrame(), new_current_rfh); EXPECT_FALSE(contents()->IsLoading()); } TEST_F(WebContentsImplTest, MediaPowerSaveBlocking) { // Verify that both negative and positive player ids don't blow up. const int kPlayerAudioVideoId = 15; const int kPlayerAudioOnlyId = -15; const int kPlayerVideoOnlyId = 30; const int kPlayerRemoteId = -30; EXPECT_FALSE(has_audio_power_save_blocker()); EXPECT_FALSE(has_video_power_save_blocker()); TestRenderFrameHost* rfh = contents()->GetMainFrame(); AudioStreamMonitor* monitor = contents()->audio_stream_monitor(); // Ensure RenderFrame is initialized before simulating events coming from it. main_test_rfh()->InitializeRenderFrameIfNeeded(); // The audio power save blocker should not be based on having a media player // when audio stream monitoring is available. if (AudioStreamMonitor::monitoring_available()) { // Send a fake audio stream monitor notification. The audio power save // blocker should be created. monitor->set_was_recently_audible_for_testing(true); contents()->NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB); EXPECT_TRUE(has_audio_power_save_blocker()); // Send another fake notification, this time when WasRecentlyAudible() will // be false. The power save blocker should be released. monitor->set_was_recently_audible_for_testing(false); contents()->NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB); EXPECT_FALSE(has_audio_power_save_blocker()); } // Start a player with both audio and video. A video power save blocker // should be created. If audio stream monitoring is available, an audio power // save blocker should be created too. rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaPlaying( 0, kPlayerAudioVideoId, true, true, false, base::TimeDelta())); EXPECT_TRUE(has_video_power_save_blocker()); EXPECT_EQ(has_audio_power_save_blocker(), !AudioStreamMonitor::monitoring_available()); // Upon hiding the video power save blocker should be released. contents()->WasHidden(); EXPECT_FALSE(has_video_power_save_blocker()); // Start another player that only has video. There should be no change in // the power save blockers. The notification should take into account the // visibility state of the WebContents. rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaPlaying( 0, kPlayerVideoOnlyId, true, false, false, base::TimeDelta())); EXPECT_FALSE(has_video_power_save_blocker()); EXPECT_EQ(has_audio_power_save_blocker(), !AudioStreamMonitor::monitoring_available()); // Showing the WebContents should result in the creation of the blocker. contents()->WasShown(); EXPECT_TRUE(has_video_power_save_blocker()); // Start another player that only has audio. There should be no change in // the power save blockers. rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaPlaying( 0, kPlayerAudioOnlyId, false, true, false, base::TimeDelta())); EXPECT_TRUE(has_video_power_save_blocker()); EXPECT_EQ(has_audio_power_save_blocker(), !AudioStreamMonitor::monitoring_available()); // Start a remote player. There should be no change in the power save // blockers. rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaPlaying( 0, kPlayerRemoteId, true, true, true, base::TimeDelta())); EXPECT_TRUE(has_video_power_save_blocker()); EXPECT_EQ(has_audio_power_save_blocker(), !AudioStreamMonitor::monitoring_available()); // Destroy the original audio video player. Both power save blockers should // remain. rfh->OnMessageReceived( MediaPlayerDelegateHostMsg_OnMediaPaused(0, kPlayerAudioVideoId, false)); EXPECT_TRUE(has_video_power_save_blocker()); EXPECT_EQ(has_audio_power_save_blocker(), !AudioStreamMonitor::monitoring_available()); // Destroy the audio only player. The video power save blocker should remain. rfh->OnMessageReceived( MediaPlayerDelegateHostMsg_OnMediaPaused(0, kPlayerAudioOnlyId, false)); EXPECT_TRUE(has_video_power_save_blocker()); EXPECT_FALSE(has_audio_power_save_blocker()); // Destroy the video only player. No power save blockers should remain. rfh->OnMessageReceived( MediaPlayerDelegateHostMsg_OnMediaPaused(0, kPlayerVideoOnlyId, false)); EXPECT_FALSE(has_video_power_save_blocker()); EXPECT_FALSE(has_audio_power_save_blocker()); // Destroy the remote player. No power save blockers should remain. rfh->OnMessageReceived( MediaPlayerDelegateHostMsg_OnMediaPaused(0, kPlayerRemoteId, false)); EXPECT_FALSE(has_video_power_save_blocker()); EXPECT_FALSE(has_audio_power_save_blocker()); // Start a player with both audio and video. A video power save blocker // should be created. If audio stream monitoring is available, an audio power // save blocker should be created too. rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaPlaying( 0, kPlayerAudioVideoId, true, true, false, base::TimeDelta())); EXPECT_TRUE(has_video_power_save_blocker()); EXPECT_EQ(has_audio_power_save_blocker(), !AudioStreamMonitor::monitoring_available()); // Crash the renderer. contents()->GetMainFrame()->GetProcess()->SimulateCrash(); // Verify that all the power save blockers have been released. EXPECT_FALSE(has_video_power_save_blocker()); EXPECT_FALSE(has_audio_power_save_blocker()); } TEST_F(WebContentsImplTest, ThemeColorChangeDependingOnFirstVisiblePaint) { TestWebContentsObserver observer(contents()); TestRenderFrameHost* rfh = contents()->GetMainFrame(); rfh->InitializeRenderFrameIfNeeded(); SkColor transparent = SK_ColorTRANSPARENT; EXPECT_EQ(transparent, contents()->GetThemeColor()); EXPECT_EQ(transparent, observer.last_theme_color()); // Theme color changes should not propagate past the WebContentsImpl before // the first visually non-empty paint has occurred. RenderViewHostTester::TestOnMessageReceived( test_rvh(), FrameHostMsg_DidChangeThemeColor(rfh->GetRoutingID(), SK_ColorRED)); EXPECT_EQ(SK_ColorRED, contents()->GetThemeColor()); EXPECT_EQ(transparent, observer.last_theme_color()); // Simulate that the first visually non-empty paint has occurred. This will // propagate the current theme color to the delegates. RenderViewHostTester::TestOnMessageReceived( test_rvh(), ViewHostMsg_DidFirstVisuallyNonEmptyPaint(rfh->GetRoutingID())); EXPECT_EQ(SK_ColorRED, contents()->GetThemeColor()); EXPECT_EQ(SK_ColorRED, observer.last_theme_color()); // Additional changes made by the web contents should propagate as well. RenderViewHostTester::TestOnMessageReceived( test_rvh(), FrameHostMsg_DidChangeThemeColor(rfh->GetRoutingID(), SK_ColorGREEN)); EXPECT_EQ(SK_ColorGREEN, contents()->GetThemeColor()); EXPECT_EQ(SK_ColorGREEN, observer.last_theme_color()); } // Test that if a renderer reports that it has loaded a resource from // memory cache with bad security info (i.e. can't be deserialized), the // renderer gets killed. TEST_F(WebContentsImplTest, LoadResourceFromMemoryCacheWithBadSecurityInfo) { MockRenderProcessHost* rph = contents()->GetMainFrame()->GetProcess(); EXPECT_EQ(0, rph->bad_msg_count()); contents()->OnDidLoadResourceFromMemoryCache( GURL("http://example.test"), "not valid security info", "GET", "mime type", RESOURCE_TYPE_MAIN_FRAME); EXPECT_EQ(1, rph->bad_msg_count()); } // Test that if a resource is loaded with empty security info, the SSLManager // does not mistakenly think it has seen a good certificate and thus forget any // user exceptions for that host. See https://crbug.com/516808. TEST_F(WebContentsImplTest, LoadResourceFromMemoryCacheWithEmptySecurityInfo) { scoped_refptr cert = net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem"); SSLPolicyBackend* backend = contents()->controller_.ssl_manager()->backend(); const GURL test_url("https://example.test"); backend->AllowCertForHost(*cert, test_url.host(), 1); EXPECT_TRUE(backend->HasAllowException(test_url.host())); contents()->OnDidLoadResourceFromMemoryCache(test_url, "", "GET", "mime type", RESOURCE_TYPE_MAIN_FRAME); EXPECT_TRUE(backend->HasAllowException(test_url.host())); } class TestJavaScriptDialogManager : public JavaScriptDialogManager { public: TestJavaScriptDialogManager() {} ~TestJavaScriptDialogManager() override {} size_t reset_count() { return reset_count_; } // JavaScriptDialogManager void RunJavaScriptDialog(WebContents* web_contents, const GURL& origin_url, const std::string& accept_lang, JavaScriptMessageType javascript_message_type, const base::string16& message_text, const base::string16& default_prompt_text, const DialogClosedCallback& callback, bool* did_suppress_message) override { *did_suppress_message = true; }; void RunBeforeUnloadDialog(WebContents* web_contents, bool is_reload, const DialogClosedCallback& callback) override {} bool HandleJavaScriptDialog(WebContents* web_contents, bool accept, const base::string16* prompt_override) override { return true; } void CancelActiveAndPendingDialogs(WebContents* web_contents) override {} void ResetDialogState(WebContents* web_contents) override { ++reset_count_; } private: size_t reset_count_ = 0; DISALLOW_COPY_AND_ASSIGN(TestJavaScriptDialogManager); }; TEST_F(WebContentsImplTest, ResetJavaScriptDialogOnUserNavigate) { scoped_ptr delegate( new TestJavaScriptDialogManager()); contents()->SetJavaScriptDialogManagerForTesting(delegate.get()); // A user-initiated navigation. contents()->TestDidNavigate(contents()->GetMainFrame(), 1, 0, true, GURL("about:whatever"), ui::PAGE_TRANSITION_TYPED); EXPECT_EQ(1u, delegate->reset_count()); // An automatic navigation. contents()->GetMainFrame()->SendNavigateWithModificationCallback( 2, 0, true, GURL(url::kAboutBlankURL), base::Bind(SetAsNonUserGesture)); EXPECT_EQ(1u, delegate->reset_count()); contents()->SetJavaScriptDialogManagerForTesting(nullptr); } } // namespace content