| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/strings/utf_string_conversions.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/media/audio_state_provider.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/view_messages.h" |
| #include "content/public/browser/global_request_id.h" |
| #include "content/public/browser/interstitial_page_delegate.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/content_constants.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/common/url_utils.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 "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkColor.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<RenderWidgetHostDelegate*>(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<TestRenderWidgetHostView*>( |
| GetMainFrame()->GetRenderViewHost()->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(); |
| } |
| }; |
| |
| 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) 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<NavigationControllerImpl&>(controller()); |
| FrameHostMsg_DidCommitProvisionalLoad_Params params; |
| InitNavigateParams(¶ms, 0, 0, true, GURL(url::kAboutBlankURL), |
| ui::PAGE_TRANSITION_TYPED); |
| |
| LoadCommittedDetails details; |
| cont.RendererDidNavigate(contents()->GetMainFrame(), params, &details); |
| |
| 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, DontUseTitleFromPendingEntry) { |
| const GURL kGURL("chrome://blah"); |
| controller().LoadURL( |
| kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); |
| EXPECT_EQ(base::string16(), 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<NavigationControllerImpl&>(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); |
| LoadCommittedDetails details; |
| cont.RendererDidNavigate(contents()->GetMainFrame(), params, &details); |
| // 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<SiteInstance> 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( |
| GetMaxURLChars() + 1, 'a')); |
| |
| controller().LoadURL( |
| url, Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string()); |
| EXPECT_EQ(nullptr, controller().GetVisibleEntry()); |
| } |
| |
| // 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) { |
| bool is_site_per_process = base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kSitePerProcess); |
| 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()->increment_active_frame_count(); |
| |
| 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 (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableBrowserSideNavigation)) { |
| 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_rfh until BeforeUnloadACK. |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableBrowserSideNavigation)) { |
| 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()->increment_active_frame_count(); |
| |
| 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 (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableBrowserSideNavigation)) { |
| contents()->GetMainFrame()->PrepareForCommit(); |
| } |
| TestRenderFrameHost* goback_rfh = contents()->GetPendingMainFrame(); |
| if (!is_site_per_process) |
| EXPECT_EQ(orig_rfh, goback_rfh); |
| EXPECT_TRUE(contents()->CrossProcessNavigationPending()); |
| |
| // Navigations should be suspended in goback_rfh until BeforeUnloadACK. |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableBrowserSideNavigation)) { |
| 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<TestWebContents> 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()->increment_active_frame_count(); |
| |
| 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 (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableBrowserSideNavigation)) { |
| 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 (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableBrowserSideNavigation)) { |
| 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<NavigationEntry*> entries; |
| NavigationEntry* entry = NavigationControllerImpl::CreateNavigationEntry( |
| native_url, Referrer(), ui::PAGE_TRANSITION_LINK, false, std::string(), |
| browser_context()); |
| entry->SetPageID(0); |
| entries.push_back(entry); |
| controller().Restore( |
| 0, |
| NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY, |
| &entries); |
| ASSERT_EQ(0u, entries.size()); |
| ASSERT_EQ(1, controller().GetEntryCount()); |
| controller().GoToIndex(0); |
| 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<NavigationEntry*> entries; |
| NavigationEntry* entry = NavigationControllerImpl::CreateNavigationEntry( |
| regular_url, Referrer(), ui::PAGE_TRANSITION_LINK, false, std::string(), |
| browser_context()); |
| entry->SetPageID(0); |
| entries.push_back(entry); |
| controller().Restore( |
| 0, |
| NavigationController::RESTORE_LAST_SESSION_EXITED_CLEANLY, |
| &entries); |
| ASSERT_EQ(0u, entries.size()); |
| ASSERT_EQ(1, controller().GetEntryCount()); |
| controller().GoToIndex(0); |
| 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(); |
| |
| // While it is still pending, simulate opening a new tab with the first tab |
| // as its opener. This will call WebContentsImpl::CreateOpenerRenderViews |
| // on the opener to ensure that an RVH exists. |
| int opener_routing_id = |
| contents()->CreateOpenerRenderViews(pending_rfh->GetSiteInstance()); |
| |
| // We should find the pending RVH and not create a new one. |
| EXPECT_EQ(pending_rfh->GetRenderViewHost()->GetRoutingID(), |
| opener_routing_id); |
| } |
| |
| // 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<TestWebContents> 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->IsWaitingForBeforeUnloadACK()); |
| base::TimeTicks now = base::TimeTicks::Now(); |
| orig_rfh->OnMessageReceived( |
| FrameHostMsg_BeforeUnload_ACK(0, false, now, now)); |
| EXPECT_FALSE(orig_rfh->IsWaitingForBeforeUnloadACK()); |
| 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->IsWaitingForBeforeUnloadACK()); |
| now = base::TimeTicks::Now(); |
| orig_rfh->PrepareForCommit(); |
| EXPECT_FALSE(orig_rfh->IsWaitingForBeforeUnloadACK()); |
| 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->IsWaitingForBeforeUnloadACK()); |
| 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->IsWaitingForBeforeUnloadACK()); |
| SiteInstance* instance2 = contents()->GetSiteInstance(); |
| EXPECT_FALSE(contents()->CrossProcessNavigationPending()); |
| EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); |
| EXPECT_EQ(instance1, instance2); |
| EXPECT_EQ(nullptr, contents()->GetPendingMainFrame()); |
| } |
| |
| TEST_F(WebContentsImplTest, CrossSiteNavigationBackPreempted) { |
| // Start with a web ui page, which gets a new RVH with WebUI bindings. |
| const GURL url1("chrome://gpu"); |
| controller().LoadURL( |
| url1, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string()); |
| int entry_id = controller().GetPendingEntry()->GetUniqueID(); |
| TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame(); |
| ntp_rfh->PrepareForCommit(); |
| contents()->TestDidNavigate(ntp_rfh, 1, entry_id, true, url1, |
| ui::PAGE_TRANSITION_TYPED); |
| NavigationEntry* entry1 = controller().GetLastCommittedEntry(); |
| SiteInstance* instance1 = contents()->GetSiteInstance(); |
| |
| EXPECT_FALSE(contents()->CrossProcessNavigationPending()); |
| EXPECT_EQ(ntp_rfh, contents()->GetMainFrame()); |
| EXPECT_EQ(url1, entry1->GetURL()); |
| EXPECT_EQ(instance1, |
| NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance()); |
| EXPECT_TRUE(ntp_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(ntp_rfh->IsWaitingForBeforeUnloadACK()); |
| base::TimeTicks now = base::TimeTicks::Now(); |
| ntp_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->IsWaitingForBeforeUnloadACK()); |
| 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()); |
| } |
| |
| // 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->IsWaitingForBeforeUnloadACK()); |
| |
| // Now simulate the onbeforeunload approval and verify the navigation is |
| // not canceled. |
| orig_rfh->PrepareForCommit(); |
| EXPECT_FALSE(orig_rfh->IsWaitingForBeforeUnloadACK()); |
| EXPECT_TRUE(contents()->CrossProcessNavigationPending()); |
| } |
| |
| // 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->IsWaitingForBeforeUnloadACK()); |
| |
| // 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->SendNavigate(1, entry1_id, true, GURL("chrome://gpu")); |
| EXPECT_TRUE(contents()->CrossProcessNavigationPending()); |
| EXPECT_EQ(orig_rfh, contents()->GetMainFrame()); |
| EXPECT_FALSE(orig_rfh->IsWaitingForBeforeUnloadACK()); |
| |
| // 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()); |
| } |
| |
| // 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); |
| orig_rfh->PrepareForCommit(); |
| 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 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(); |
| TestRenderViewHost* orig_rvh = orig_rfh->GetRenderViewHost(); |
| |
| // 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(orig_rvh->IsFullscreenGranted()); |
| EXPECT_FALSE(contents()->IsFullscreenForCurrentTab()); |
| EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents())); |
| orig_rfh->OnMessageReceived( |
| FrameHostMsg_ToggleFullscreen(orig_rfh->GetRoutingID(), true)); |
| EXPECT_TRUE(orig_rvh->IsFullscreenGranted()); |
| EXPECT_TRUE(contents()->IsFullscreenForCurrentTab()); |
| 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(orig_rvh->IsFullscreenGranted()); |
| EXPECT_FALSE(contents()->IsFullscreenForCurrentTab()); |
| 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* const orig_rfh = contents()->GetMainFrame(); |
| TestRenderViewHost* const orig_rvh = orig_rfh->GetRenderViewHost(); |
| |
| // 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(orig_rvh->IsFullscreenGranted()); |
| EXPECT_FALSE(contents()->IsFullscreenForCurrentTab()); |
| 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(orig_rvh->IsFullscreenGranted()); |
| EXPECT_TRUE(contents()->IsFullscreenForCurrentTab()); |
| 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); |
| |
| // Confirm fullscreen has exited. |
| EXPECT_FALSE(orig_rvh->IsFullscreenGranted()); |
| EXPECT_FALSE(contents()->IsFullscreenForCurrentTab()); |
| 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(test_rvh()->IsFullscreenGranted()); |
| EXPECT_FALSE(contents()->IsFullscreenForCurrentTab()); |
| EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents())); |
| contents()->GetMainFrame()->OnMessageReceived(FrameHostMsg_ToggleFullscreen( |
| contents()->GetMainFrame()->GetRoutingID(), true)); |
| EXPECT_TRUE(test_rvh()->IsFullscreenGranted()); |
| EXPECT_TRUE(contents()->IsFullscreenForCurrentTab()); |
| EXPECT_TRUE(fake_delegate.IsFullscreenForTabOrPending(contents())); |
| |
| // Crash the renderer. |
| main_test_rfh()->GetProcess()->SimulateCrash(); |
| |
| // Confirm fullscreen has exited. |
| EXPECT_FALSE(test_rvh()->IsFullscreenGranted()); |
| EXPECT_FALSE(contents()->IsFullscreenForCurrentTab()); |
| 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()); |
| 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); |
| |
| // While the interstitial is showing, go back. |
| controller().GoBack(); |
| main_test_rfh()->PrepareForCommit(); |
| contents()->GetMainFrame()->SendNavigate(1, entry->GetUniqueID(), false, |
| url1); |
| |
| // 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 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<TestRenderFrameHost*>(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<TestWebContents> other_contents( |
| static_cast<TestWebContents*>(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<TestWebContents> other_contents( |
| static_cast<TestWebContents*>(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<TestWebContents> other_contents( |
| static_cast<TestWebContents*>(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()); |
| 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<TestWebContents> other_contents( |
| static_cast<TestWebContents*>(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<TestRenderWidgetHostView*>( |
| contents()->GetMainFrame()->GetRenderViewHost()->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<TestRenderWidgetHostView*>( |
| contents()->GetMainFrame()->GetRenderViewHost()->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<ContentsZoomChangedDelegate> 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, 1, modifiers, false); |
| EXPECT_FALSE(contents()->HandleWheelEvent(event)); |
| EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount()); |
| |
| modifiers = WebInputEvent::ShiftKey | WebInputEvent::AltKey | |
| WebInputEvent::ControlKey; |
| event = SyntheticWebMouseWheelEventBuilder::Build(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, 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(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(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, 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<SiteInstance> instance1( |
| SiteInstance::CreateForURL(browser_context(), GURL("http://a.com"))); |
| scoped_refptr<SiteInstance> instance2( |
| instance1->GetRelatedSiteInstance(GURL("http://b.com"))); |
| |
| EXPECT_EQ(0u, instance1->GetRelatedActiveContentsCount()); |
| EXPECT_EQ(0u, instance2->GetRelatedActiveContentsCount()); |
| |
| scoped_ptr<TestWebContents> contents1( |
| TestWebContents::Create(browser_context(), instance1.get())); |
| EXPECT_EQ(1u, instance1->GetRelatedActiveContentsCount()); |
| EXPECT_EQ(1u, instance2->GetRelatedActiveContentsCount()); |
| |
| scoped_ptr<TestWebContents> 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<SiteInstance> instance( |
| SiteInstance::Create(browser_context())); |
| |
| EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount()); |
| |
| scoped_ptr<TestWebContents> 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 (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableBrowserSideNavigation)) { |
| 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<SiteInstance> instance( |
| SiteInstance::Create(browser_context())); |
| |
| EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount()); |
| |
| scoped_ptr<TestWebContents> 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<SiteInstance> 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); |
| }; |
| |
| // 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(WebContentsImplTest, 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. |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kSitePerProcess); |
| 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->PrepareForCommit(); |
| 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->SendNavigate(1, 0, true, kUrl3); |
| current_rfh->OnMessageReceived( |
| FrameHostMsg_DidStopLoading(current_rfh->GetRoutingID())); |
| EXPECT_EQ(contents()->GetPendingMainFrame(), pending_rfh); |
| EXPECT_TRUE(contents()->IsLoading()); |
| |
| // 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()); |
| |
| // 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) { |
| // PlayerIDs are actually pointers cast to int64, so 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(contents()->has_audio_power_save_blocker_for_testing()); |
| EXPECT_FALSE(contents()->has_video_power_save_blocker_for_testing()); |
| |
| TestRenderFrameHost* rfh = contents()->GetMainFrame(); |
| AudioStateProvider* audio_state = contents()->audio_state_provider(); |
| |
| // 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 (audio_state->IsAudioStateAvailable()) { |
| // Send a fake audio stream monitor notification. The audio power save |
| // blocker should be created. |
| audio_state->set_was_recently_audible_for_testing(true); |
| contents()->NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB); |
| EXPECT_TRUE(contents()->has_audio_power_save_blocker_for_testing()); |
| |
| // Send another fake notification, this time when WasRecentlyAudible() will |
| // be false. The power save blocker should be released. |
| audio_state->set_was_recently_audible_for_testing(false); |
| contents()->NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB); |
| EXPECT_FALSE(contents()->has_audio_power_save_blocker_for_testing()); |
| } |
| |
| // 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(FrameHostMsg_MediaPlayingNotification( |
| 0, kPlayerAudioVideoId, true, true, false)); |
| EXPECT_TRUE(contents()->has_video_power_save_blocker_for_testing()); |
| EXPECT_EQ(contents()->has_audio_power_save_blocker_for_testing(), |
| !audio_state->IsAudioStateAvailable()); |
| |
| // Upon hiding the video power save blocker should be released. |
| contents()->WasHidden(); |
| EXPECT_FALSE(contents()->has_video_power_save_blocker_for_testing()); |
| |
| // 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(FrameHostMsg_MediaPlayingNotification( |
| 0, kPlayerVideoOnlyId, true, false, false)); |
| EXPECT_FALSE(contents()->has_video_power_save_blocker_for_testing()); |
| EXPECT_EQ(contents()->has_audio_power_save_blocker_for_testing(), |
| !audio_state->IsAudioStateAvailable()); |
| |
| // Showing the WebContents should result in the creation of the blocker. |
| contents()->WasShown(); |
| EXPECT_TRUE(contents()->has_video_power_save_blocker_for_testing()); |
| |
| // Start another player that only has audio. There should be no change in |
| // the power save blockers. |
| rfh->OnMessageReceived(FrameHostMsg_MediaPlayingNotification( |
| 0, kPlayerAudioOnlyId, false, true, false)); |
| EXPECT_TRUE(contents()->has_video_power_save_blocker_for_testing()); |
| EXPECT_EQ(contents()->has_audio_power_save_blocker_for_testing(), |
| !audio_state->IsAudioStateAvailable()); |
| |
| // Start a remote player. There should be no change in the power save |
| // blockers. |
| rfh->OnMessageReceived(FrameHostMsg_MediaPlayingNotification( |
| 0, kPlayerRemoteId, true, true, true)); |
| EXPECT_TRUE(contents()->has_video_power_save_blocker_for_testing()); |
| EXPECT_EQ(contents()->has_audio_power_save_blocker_for_testing(), |
| !audio_state->IsAudioStateAvailable()); |
| |
| // Destroy the original audio video player. Both power save blockers should |
| // remain. |
| rfh->OnMessageReceived( |
| FrameHostMsg_MediaPausedNotification(0, kPlayerAudioVideoId)); |
| EXPECT_TRUE(contents()->has_video_power_save_blocker_for_testing()); |
| EXPECT_EQ(contents()->has_audio_power_save_blocker_for_testing(), |
| !audio_state->IsAudioStateAvailable()); |
| |
| // Destroy the audio only player. The video power save blocker should remain. |
| rfh->OnMessageReceived( |
| FrameHostMsg_MediaPausedNotification(0, kPlayerAudioOnlyId)); |
| EXPECT_TRUE(contents()->has_video_power_save_blocker_for_testing()); |
| EXPECT_FALSE(contents()->has_audio_power_save_blocker_for_testing()); |
| |
| // Destroy the video only player. No power save blockers should remain. |
| rfh->OnMessageReceived( |
| FrameHostMsg_MediaPausedNotification(0, kPlayerVideoOnlyId)); |
| EXPECT_FALSE(contents()->has_video_power_save_blocker_for_testing()); |
| EXPECT_FALSE(contents()->has_audio_power_save_blocker_for_testing()); |
| |
| // Destroy the remote player. No power save blockers should remain. |
| rfh->OnMessageReceived( |
| FrameHostMsg_MediaPausedNotification(0, kPlayerRemoteId)); |
| EXPECT_FALSE(contents()->has_video_power_save_blocker_for_testing()); |
| EXPECT_FALSE(contents()->has_audio_power_save_blocker_for_testing()); |
| |
| // 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(FrameHostMsg_MediaPlayingNotification( |
| 0, kPlayerAudioVideoId, true, true, false)); |
| EXPECT_TRUE(contents()->has_video_power_save_blocker_for_testing()); |
| EXPECT_EQ(contents()->has_audio_power_save_blocker_for_testing(), |
| !audio_state->IsAudioStateAvailable()); |
| |
| // Crash the renderer. |
| contents()->GetMainFrame()->GetProcess()->SimulateCrash(); |
| |
| // Verify that all the power save blockers have been released. |
| EXPECT_FALSE(contents()->has_video_power_save_blocker_for_testing()); |
| EXPECT_FALSE(contents()->has_audio_power_save_blocker_for_testing()); |
| } |
| |
| TEST_F(WebContentsImplTest, ThemeColorChangeDependingOnFirstVisiblePaint) { |
| TestWebContentsObserver observer(contents()); |
| TestRenderFrameHost* rfh = contents()->GetMainFrame(); |
| |
| 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(), |
| FrameHostMsg_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()); |
| } |
| |
| } // namespace content |