text: // 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 <stdint.h>
  #include <utility>
  
  #include "base/command_line.h"
  #include "base/logging.h"
  #include "base/macros.h"
  #include "base/strings/utf_string_conversions.h"
  #include "build/build_config.h"
  #include "content/browser/frame_host/interstitial_page_impl.h"
  #include "content/browser/frame_host/navigation_entry_impl.h"
  #include "content/browser/frame_host/render_frame_host_impl.h"
  #include "content/browser/frame_host/render_frame_proxy_host.h"
  #include "content/browser/media/audio_stream_monitor.h"
  #include "content/browser/media/media_web_contents_observer.h"
  #include "content/browser/renderer_host/render_view_host_impl.h"
  #include "content/browser/site_instance_impl.h"
  #include "content/browser/webui/content_web_ui_controller_factory.h"
  #include "content/browser/webui/web_ui_controller_factory_registry.h"
  #include "content/common/frame_messages.h"
  #include "content/common/input/synthetic_web_input_event_builders.h"
  #include "content/common/media/media_player_delegate_messages.h"
  #include "content/common/site_isolation_policy.h"
  #include "content/common/view_messages.h"
  #include "content/public/browser/content_browser_client.h"
  #include "content/public/browser/global_request_id.h"
  #include "content/public/browser/interstitial_page_delegate.h"
  #include "content/public/browser/javascript_dialog_manager.h"
  #include "content/public/browser/navigation_details.h"
  #include "content/public/browser/notification_details.h"
  #include "content/public/browser/notification_source.h"
  #include "content/public/browser/render_widget_host_view.h"
  #include "content/public/browser/resource_request_details.h"
  #include "content/public/browser/ssl_host_state_delegate.h"
  #include "content/public/browser/storage_partition.h"
  #include "content/public/browser/web_contents_delegate.h"
  #include "content/public/browser/web_contents_observer.h"
  #include "content/public/browser/web_ui_controller.h"
  #include "content/public/common/bindings_policy.h"
  #include "content/public/common/browser_side_navigation_policy.h"
  #include "content/public/common/content_constants.h"
  #include "content/public/common/url_constants.h"
  #include "content/public/test/mock_render_process_host.h"
  #include "content/public/test/test_browser_context.h"
  #include "content/public/test/test_utils.h"
  #include "content/test/test_content_browser_client.h"
  #include "content/test/test_content_client.h"
  #include "content/test/test_render_frame_host.h"
  #include "content/test/test_render_view_host.h"
  #include "content/test/test_web_contents.h"
  #include "net/test/cert_test_util.h"
  #include "net/test/test_data_directory.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "third_party/skia/include/core/SkColor.h"
  #include "url/url_constants.h"
  
  namespace content {
  namespace {
  
  class TestInterstitialPage;
  
  class TestInterstitialPageDelegate : public InterstitialPageDelegate {
   public:
    explicit TestInterstitialPageDelegate(TestInterstitialPage* interstitial_page)
        : interstitial_page_(interstitial_page) {}
    void CommandReceived(const std::string& command) override;
    std::string GetHTMLContents() override { return std::string(); }
    void OnDontProceed() override;
    void OnProceed() override;
  
   private:
    TestInterstitialPage* interstitial_page_;
  };
  
  class TestInterstitialPage : public InterstitialPageImpl {
   public:
    enum InterstitialState {
      INVALID = 0,    // Hasn't yet been initialized.
      UNDECIDED,      // Initialized, but no decision taken yet.
      OKED,           // Proceed was called.
      CANCELED        // DontProceed was called.
    };
  
    class Delegate {
     public:
      virtual void TestInterstitialPageDeleted(
          TestInterstitialPage* interstitial) = 0;
  
     protected:
      virtual ~Delegate() {}
    };
  
    // IMPORTANT NOTE: if you pass stack allocated values for |state| and
    // |deleted| (like all interstitial related tests do at this point), make sure
    // to create an instance of the TestInterstitialPageStateGuard class on the
    // stack in your test.  This will ensure that the TestInterstitialPage states
    // are cleared when the test finishes.
    // Not doing so will cause stack trashing if your test does not hide the
    // interstitial, as in such a case it will be destroyed in the test TearDown
    // method and will dereference the |deleted| local variable which by then is
    // out of scope.
    TestInterstitialPage(WebContentsImpl* contents,
                         bool new_navigation,
                         const GURL& url,
                         InterstitialState* state,
                         bool* deleted)
        : InterstitialPageImpl(
              contents,
              static_cast<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 nav_entry_id,
                         bool did_create_new_entry,
                         const GURL& url) {
      FrameHostMsg_DidCommitProvisionalLoad_Params params;
      InitNavigateParams(&params, 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()->GetWidget()->GetView())
          ->is_showing();
    }
  
    void ClearStates() {
      state_ = nullptr;
      deleted_ = nullptr;
      delegate_ = nullptr;
    }
  
    void CommandReceived() {
      command_received_count_++;
    }
  
    void set_delegate(Delegate* delegate) {
      delegate_ = delegate;
    }
  
   protected:
    WebContentsView* CreateWebContentsView() override { return nullptr; }
  
   private:
    InterstitialState* state_;
    bool* deleted_;
    int command_received_count_;
    Delegate* delegate_;
  };
  
  void TestInterstitialPageDelegate::CommandReceived(const std::string& command) {
    interstitial_page_->CommandReceived();
  }
  
  void TestInterstitialPageDelegate::OnDontProceed() {
    interstitial_page_->OnDontProceed();
  }
  
  void TestInterstitialPageDelegate::OnProceed() {
    interstitial_page_->OnProceed();
  }
  
  class TestInterstitialPageStateGuard : public TestInterstitialPage::Delegate {
   public:
    explicit TestInterstitialPageStateGuard(
        TestInterstitialPage* interstitial_page)
        : interstitial_page_(interstitial_page) {
      DCHECK(interstitial_page_);
      interstitial_page_->set_delegate(this);
    }
    ~TestInterstitialPageStateGuard() override {
      if (interstitial_page_)
        interstitial_page_->ClearStates();
    }
  
    void TestInterstitialPageDeleted(
        TestInterstitialPage* interstitial) override {
      DCHECK(interstitial_page_ == interstitial);
      interstitial_page_ = nullptr;
    }
  
   private:
    TestInterstitialPage* interstitial_page_;
  };
  
  class WebContentsImplTestBrowserClient : public TestContentBrowserClient {
   public:
    WebContentsImplTestBrowserClient()
        : assign_site_for_url_(false) {}
  
    ~WebContentsImplTestBrowserClient() override {}
  
    bool ShouldAssignSiteForURL(const GURL& url) override {
      return assign_site_for_url_;
    }
  
    void set_assign_site_for_url(bool assign) {
      assign_site_for_url_ = assign;
    }
  
   private:
    bool assign_site_for_url_;
  };
  
  class WebContentsImplTest : public RenderViewHostImplTestHarness {
   public:
    void SetUp() override {
      RenderViewHostImplTestHarness::SetUp();
      WebUIControllerFactory::RegisterFactory(
          ContentWebUIControllerFactory::GetInstance());
    }
  
    void TearDown() override {
      WebUIControllerFactory::UnregisterFactoryForTesting(
          ContentWebUIControllerFactory::GetInstance());
      RenderViewHostImplTestHarness::TearDown();
    }
  
    bool has_audio_power_save_blocker() {
      return contents()
          ->media_web_contents_observer()
          ->has_audio_power_save_blocker_for_testing();
    }
  
    bool has_video_power_save_blocker() {
      return contents()
          ->media_web_contents_observer()
          ->has_video_power_save_blocker_for_testing();
    }
  };
  
  class TestWebContentsObserver : public WebContentsObserver {
   public:
    explicit TestWebContentsObserver(WebContents* contents)
        : WebContentsObserver(contents),
          last_theme_color_(SK_ColorTRANSPARENT) {
    }
    ~TestWebContentsObserver() override {}
  
    void DidFinishLoad(RenderFrameHost* render_frame_host,
                       const GURL& validated_url) override {
      last_url_ = validated_url;
    }
    void DidFailLoad(RenderFrameHost* render_frame_host,
                     const GURL& validated_url,
                     int error_code,
                     const base::string16& error_description,
                     bool was_ignored_by_handler) override {
      last_url_ = validated_url;
    }
  
    void DidChangeThemeColor(SkColor theme_color) override {
      last_theme_color_ = theme_color;
    }
  
    const GURL& last_url() const { return last_url_; }
    SkColor last_theme_color() const { return last_theme_color_; }
  
   private:
    GURL last_url_;
    SkColor last_theme_color_;
  
    DISALLOW_COPY_AND_ASSIGN(TestWebContentsObserver);
  };
  
  // Pretends to be a normal browser that receives toggles and transitions to/from
  // a fullscreened state.
  class FakeFullscreenDelegate : public WebContentsDelegate {
   public:
    FakeFullscreenDelegate() : fullscreened_contents_(nullptr) {}
    ~FakeFullscreenDelegate() override {}
  
    void EnterFullscreenModeForTab(WebContents* web_contents,
                                   const GURL& origin) override {
      fullscreened_contents_ = web_contents;
    }
  
    void ExitFullscreenModeForTab(WebContents* web_contents) override {
      fullscreened_contents_ = nullptr;
    }
  
    bool IsFullscreenForTabOrPending(
        const WebContents* web_contents) const override {
      return fullscreened_contents_ && web_contents == fullscreened_contents_;
    }
  
   private:
    WebContents* fullscreened_contents_;
  
    DISALLOW_COPY_AND_ASSIGN(FakeFullscreenDelegate);
  };
  
  class FakeValidationMessageDelegate : public WebContentsDelegate {
   public:
    FakeValidationMessageDelegate()
        : hide_validation_message_was_called_(false) {}
    ~FakeValidationMessageDelegate() override {}
  
    void HideValidationMessage(WebContents* web_contents) override {
      hide_validation_message_was_called_ = true;
    }
  
    bool hide_validation_message_was_called() const {
      return hide_validation_message_was_called_;
    }
  
   private:
    bool hide_validation_message_was_called_;
  
    DISALLOW_COPY_AND_ASSIGN(FakeValidationMessageDelegate);
  };
  
  }  // namespace
  
  // Test to make sure that title updates get stripped of whitespace.
  TEST_F(WebContentsImplTest, UpdateTitle) {
    NavigationControllerImpl& cont =
        static_cast<NavigationControllerImpl&>(controller());
    cont.LoadURL(GURL(url::kAboutBlankURL), Referrer(), ui::PAGE_TRANSITION_TYPED,
                 std::string());
  
    FrameHostMsg_DidCommitProvisionalLoad_Params params;
    InitNavigateParams(&params, 0, true, GURL(url::kAboutBlankURL),
                       ui::PAGE_TRANSITION_TYPED);
  
    main_test_rfh()->SendNavigateWithParams(&params);
  
    contents()->UpdateTitle(main_test_rfh(),
                            base::ASCIIToUTF16("    Lots O' Whitespace\n"),
                            base::i18n::LEFT_TO_RIGHT);
    EXPECT_EQ(base::ASCIIToUTF16("Lots O' Whitespace"), contents()->GetTitle());
  }
  
  TEST_F(WebContentsImplTest, UpdateTitleBeforeFirstNavigation) {
    ASSERT_TRUE(controller().IsInitialNavigation());
    const base::string16 title = base::ASCIIToUTF16("Initial Entry Title");
    contents()->UpdateTitle(main_test_rfh(), title, base::i18n::LEFT_TO_RIGHT);
    EXPECT_EQ(title, contents()->GetTitle());
  }
  
  TEST_F(WebContentsImplTest, DontUseTitleFromPendingEntry) {
    const GURL kGURL("chrome://blah");
    controller().LoadURL(
        kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    EXPECT_EQ(base::string16(), contents()->GetTitle());
  
    // Also test setting title while the first navigation is still pending.
    const base::string16 title = base::ASCIIToUTF16("Initial Entry Title");
    contents()->UpdateTitle(main_test_rfh(), title, base::i18n::LEFT_TO_RIGHT);
    EXPECT_EQ(title, contents()->GetTitle());
  }
  
  TEST_F(WebContentsImplTest, UseTitleFromPendingEntryIfSet) {
    const GURL kGURL("chrome://blah");
    const base::string16 title = base::ASCIIToUTF16("My Title");
    controller().LoadURL(
        kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
  
    NavigationEntry* entry = controller().GetVisibleEntry();
    ASSERT_EQ(kGURL, entry->GetURL());
    entry->SetTitle(title);
  
    EXPECT_EQ(title, contents()->GetTitle());
  }
  
  // Browser initiated navigations to view-source URLs of WebUI pages should work.
  TEST_F(WebContentsImplTest, DirectNavigationToViewSourceWebUI) {
    NavigationControllerImpl& cont =
        static_cast<NavigationControllerImpl&>(controller());
    const GURL kGURL("view-source:chrome://blah");
    // NavigationControllerImpl rewrites view-source URLs, simulating that here.
    const GURL kRewrittenURL("chrome://blah");
  
    process()->sink().ClearMessages();
  
    // Use LoadURLWithParams instead of LoadURL, because the former properly
    // rewrites view-source:chrome://blah URLs to chrome://blah.
    NavigationController::LoadURLParams load_params(kGURL);
    load_params.transition_type = ui::PAGE_TRANSITION_TYPED;
    load_params.extra_headers = "content-type: text/plain";
    load_params.load_type = NavigationController::LOAD_TYPE_DEFAULT;
    load_params.is_renderer_initiated = false;
    controller().LoadURLWithParams(load_params);
  
    int entry_id = cont.GetPendingEntry()->GetUniqueID();
    // Did we get the expected message?
    EXPECT_TRUE(process()->sink().GetFirstMessageMatching(
        FrameMsg_EnableViewSourceMode::ID));
  
    FrameHostMsg_DidCommitProvisionalLoad_Params params;
    InitNavigateParams(&params, entry_id, true, kRewrittenURL,
                       ui::PAGE_TRANSITION_TYPED);
    main_test_rfh()->PrepareForCommit();
    main_test_rfh()->OnMessageReceived(
        FrameHostMsg_DidStartProvisionalLoad(1, kRewrittenURL,
                                             base::TimeTicks::Now()));
    main_test_rfh()->SendNavigateWithParams(&params);
  
    // This is the virtual URL.
    EXPECT_EQ(base::ASCIIToUTF16("view-source:chrome://blah"),
              contents()->GetTitle());
  
    // The actual URL navigated to.
    EXPECT_EQ(kRewrittenURL,
              contents()->GetController().GetLastCommittedEntry()->GetURL());
  }
  
  // Test simple same-SiteInstance navigation.
  TEST_F(WebContentsImplTest, SimpleNavigation) {
    TestRenderFrameHost* orig_rfh = main_test_rfh();
    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, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh, main_test_rfh());
    EXPECT_EQ(instance1, orig_rfh->GetSiteInstance());
    // Controller's entry should now have the SiteInstance, or else we won't be
    // able to find it later.
    EXPECT_EQ(
        instance1,
        NavigationEntryImpl::FromNavigationEntry(controller().GetVisibleEntry())->
            site_instance());
  }
  
  // Test that we reject NavigateToEntry if the url is over kMaxURLChars.
  TEST_F(WebContentsImplTest, NavigateToExcessivelyLongURL) {
    // Construct a URL that's kMaxURLChars + 1 long of all 'a's.
    const GURL url(std::string("http://example.org/").append(
        url::kMaxURLChars + 1, 'a'));
  
    controller().LoadURL(
        url, Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string());
    EXPECT_EQ(nullptr, controller().GetPendingEntry());
  }
  
  // Test that we reject NavigateToEntry if the url is invalid.
  TEST_F(WebContentsImplTest, NavigateToInvalidURL) {
    // Invalid URLs should not trigger a navigation.
    const GURL invalid_url("view-source:http://example.org/%00");
    controller().LoadURL(
        invalid_url, Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string());
    EXPECT_EQ(nullptr, controller().GetPendingEntry());
  
    // Empty URLs are supported and should start a navigation.
    controller().LoadURL(
        GURL(), Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string());
    EXPECT_NE(nullptr, controller().GetPendingEntry());
  }
  
  // Test that navigating across a site boundary creates a new RenderViewHost
  // with a new SiteInstance.  Going back should do the same.
  TEST_F(WebContentsImplTest, CrossSiteBoundaries) {
    TestRenderFrameHost* orig_rfh = main_test_rfh();
    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, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
  
    // Keep the number of active frames in orig_rfh's SiteInstance non-zero so
    // that orig_rfh doesn't get deleted when it gets swapped out.
    orig_rfh->GetSiteInstance()->IncrementActiveFrameCount();
  
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh->GetRenderViewHost(), contents()->GetRenderViewHost());
    EXPECT_EQ(url, contents()->GetLastCommittedURL());
    EXPECT_EQ(url, contents()->GetVisibleURL());
  
    // Navigate to new site
    const GURL url2("http://www.yahoo.com");
    controller().LoadURL(
        url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    entry_id = controller().GetPendingEntry()->GetUniqueID();
    if (IsBrowserSideNavigationEnabled())
      orig_rfh->PrepareForCommit();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(url, contents()->GetLastCommittedURL());
    EXPECT_EQ(url2, contents()->GetVisibleURL());
    TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame();
    EXPECT_TRUE(pending_rfh->GetLastCommittedURL().is_empty());
    int pending_rvh_delete_count = 0;
    pending_rfh->GetRenderViewHost()->set_delete_counter(
        &pending_rvh_delete_count);
  
    // Navigations should be suspended in pending_rfh until BeforeUnloadACK.
    if (!IsBrowserSideNavigationEnabled()) {
      EXPECT_TRUE(pending_rfh->are_navigations_suspended());
      orig_rfh->SendBeforeUnloadACK(true);
      EXPECT_FALSE(pending_rfh->are_navigations_suspended());
    }
  
    // DidNavigate from the pending page
    contents()->TestDidNavigate(pending_rfh, entry_id, true, url2,
                                ui::PAGE_TRANSITION_TYPED);
    SiteInstance* instance2 = contents()->GetSiteInstance();
  
    // Keep the number of active frames in pending_rfh's SiteInstance
    // non-zero so that orig_rfh doesn't get deleted when it gets
    // swapped out.
    pending_rfh->GetSiteInstance()->IncrementActiveFrameCount();
  
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(pending_rfh, main_test_rfh());
    EXPECT_EQ(url2, contents()->GetLastCommittedURL());
    EXPECT_EQ(url2, contents()->GetVisibleURL());
    EXPECT_NE(instance1, instance2);
    EXPECT_EQ(nullptr, contents()->GetPendingMainFrame());
    // We keep a proxy for the original RFH's SiteInstance.
    EXPECT_TRUE(contents()->GetRenderManagerForTesting()->GetRenderFrameProxyHost(
        orig_rfh->GetSiteInstance()));
    EXPECT_EQ(orig_rvh_delete_count, 0);
  
    // Going back should switch SiteInstances again.  The first SiteInstance is
    // stored in the NavigationEntry, so it should be the same as at the start.
    // We should use the same RFH as before, swapping it back in.
    controller().GoBack();
    entry_id = controller().GetPendingEntry()->GetUniqueID();
    if (IsBrowserSideNavigationEnabled())
      main_test_rfh()->PrepareForCommit();
    TestRenderFrameHost* goback_rfh = contents()->GetPendingMainFrame();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
  
    // Navigations should be suspended in goback_rfh until BeforeUnloadACK.
    if (!IsBrowserSideNavigationEnabled()) {
      EXPECT_TRUE(goback_rfh->are_navigations_suspended());
      pending_rfh->SendBeforeUnloadACK(true);
      EXPECT_FALSE(goback_rfh->are_navigations_suspended());
    }
  
    // DidNavigate from the back action
    contents()->TestDidNavigate(goback_rfh, entry_id, false, url2,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(goback_rfh, main_test_rfh());
    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 = main_test_rfh();
  
    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();
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, 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();
    main_test_rfh()->PrepareForCommit();
    TestRenderFrameHost* new_rfh = main_test_rfh();
    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, 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 = main_test_rfh();
    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();
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
  
    // Open a new contents with the same SiteInstance, navigated to the same site.
    std::unique_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(), 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, 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, 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 = main_test_rfh();
    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();
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, entry_id, true, native_url,
                                ui::PAGE_TRANSITION_TYPED);
  
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh, main_test_rfh());
    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();
    main_test_rfh()->PrepareForCommit();
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(native_url, contents()->GetLastCommittedURL());
    EXPECT_EQ(url, contents()->GetVisibleURL());
    EXPECT_FALSE(contents()->GetPendingMainFrame());
    contents()->TestDidNavigate(orig_rfh, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
  
    // Keep the number of active frames in orig_rfh's SiteInstance
    // non-zero so that orig_rfh doesn't get deleted when it gets
    // swapped out.
    orig_rfh->GetSiteInstance()->IncrementActiveFrameCount();
  
    EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
    EXPECT_TRUE(
        contents()->GetSiteInstance()->GetSiteURL().DomainIs("google.com"));
    EXPECT_EQ(url, contents()->GetLastCommittedURL());
  
    // Navigate to another new site (should create a new site instance).
    const GURL url2("http://www.yahoo.com");
    controller().LoadURL(
        url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    entry_id = controller().GetPendingEntry()->GetUniqueID();
    if (IsBrowserSideNavigationEnabled())
      orig_rfh->PrepareForCommit();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(url, contents()->GetLastCommittedURL());
    EXPECT_EQ(url2, contents()->GetVisibleURL());
    TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame();
    int pending_rvh_delete_count = 0;
    pending_rfh->GetRenderViewHost()->set_delete_counter(
        &pending_rvh_delete_count);
  
    // Navigations should be suspended in pending_rvh until BeforeUnloadACK.
    if (!IsBrowserSideNavigationEnabled()) {
      EXPECT_TRUE(pending_rfh->are_navigations_suspended());
      orig_rfh->SendBeforeUnloadACK(true);
      EXPECT_FALSE(pending_rfh->are_navigations_suspended());
    }
  
    // DidNavigate from the pending page.
    contents()->TestDidNavigate(pending_rfh, entry_id, true, url2,
                                ui::PAGE_TRANSITION_TYPED);
    SiteInstance* new_instance = contents()->GetSiteInstance();
  
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(pending_rfh, main_test_rfh());
    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 = main_test_rfh();
  
    // 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<std::unique_ptr<NavigationEntry>> entries;
    std::unique_ptr<NavigationEntry> new_entry =
        NavigationControllerImpl::CreateNavigationEntry(
            native_url, Referrer(), ui::PAGE_TRANSITION_LINK, false,
            std::string(), browser_context());
    entries.push_back(std::move(new_entry));
    controller().Restore(0, RestoreType::LAST_SESSION_EXITED_CLEANLY, &entries);
    ASSERT_EQ(0u, entries.size());
    ASSERT_EQ(1, controller().GetEntryCount());
  
    controller().GoToIndex(0);
    NavigationEntry* entry = controller().GetPendingEntry();
    orig_rfh->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, 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, 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 = main_test_rfh();
  
    // 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<std::unique_ptr<NavigationEntry>> entries;
    std::unique_ptr<NavigationEntry> new_entry =
        NavigationControllerImpl::CreateNavigationEntry(
            regular_url, Referrer(), ui::PAGE_TRANSITION_LINK, false,
            std::string(), browser_context());
    entries.push_back(std::move(new_entry));
    controller().Restore(0, RestoreType::LAST_SESSION_EXITED_CLEANLY, &entries);
    ASSERT_EQ(0u, entries.size());
  
    ASSERT_EQ(1, controller().GetEntryCount());
    controller().GoToIndex(0);
    NavigationEntry* entry = controller().GetPendingEntry();
    orig_rfh->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, 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(),
                                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 = main_test_rfh();
  
    // 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, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
  
    // Start to navigate first tab to a new site, so that it has a pending RVH.
    const GURL url2("http://www.yahoo.com");
    controller().LoadURL(
        url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    orig_rfh->PrepareForCommit();
    TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame();
    SiteInstance* instance = pending_rfh->GetSiteInstance();
  
    // While it is still pending, simulate opening a new tab with the first tab
    // as its opener.  This will call CreateOpenerProxies on the opener to ensure
    // that an RVH exists.
    std::unique_ptr<TestWebContents> popup(
        TestWebContents::Create(browser_context(), instance));
    popup->SetOpener(contents());
    contents()->GetRenderManager()->CreateOpenerProxies(instance, nullptr);
  
    // If swapped out is forbidden, a new proxy should be created for the opener
    // in |instance|, and we should ensure that its routing ID is returned here.
    // Otherwise, we should find the pending RFH and not create a new proxy.
    int opener_frame_routing_id =
        popup->GetRenderManager()->GetOpenerRoutingID(instance);
    RenderFrameProxyHost* proxy =
        contents()->GetRenderManager()->GetRenderFrameProxyHost(instance);
    EXPECT_TRUE(proxy);
    EXPECT_EQ(proxy->GetRoutingID(), opener_frame_routing_id);
  
    // Ensure that committing the navigation removes the proxy.
    entry_id = controller().GetPendingEntry()->GetUniqueID();
    contents()->TestDidNavigate(pending_rfh, entry_id, true, url2,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(
        contents()->GetRenderManager()->GetRenderFrameProxyHost(instance));
  }
  
  // Tests that WebContentsImpl uses the current URL, not the SiteInstance's site,
  // to determine whether a navigation is cross-site.
  TEST_F(WebContentsImplTest, CrossSiteComparesAgainstCurrentPage) {
    TestRenderFrameHost* orig_rfh = main_test_rfh();
    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();
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
  
    // Open a related contents to a second site.
    std::unique_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, 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, 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());
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, 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 = main_test_rfh();
    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();
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh, main_test_rfh());
  
    // Navigate to new site, but simulate an onbeforeunload denial.
    const GURL url2("http://www.yahoo.com");
    controller().LoadURL(
        url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_ack());
    base::TimeTicks now = base::TimeTicks::Now();
    orig_rfh->OnMessageReceived(
        FrameHostMsg_BeforeUnload_ACK(0, false, now, now));
    EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_ack());
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh, main_test_rfh());
  
    // Navigate again, but simulate an onbeforeunload approval.
    controller().LoadURL(
        url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    entry_id = controller().GetPendingEntry()->GetUniqueID();
    EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_ack());
    now = base::TimeTicks::Now();
    orig_rfh->PrepareForCommit();
    EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_ack());
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame();
  
    // We won't hear DidNavigate until the onunload handler has finished running.
  
    // DidNavigate from the pending page.
    contents()->TestDidNavigate(pending_rfh, entry_id, true, url2,
                                ui::PAGE_TRANSITION_TYPED);
    SiteInstance* instance2 = contents()->GetSiteInstance();
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(pending_rfh, main_test_rfh());
    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 = main_test_rfh();
    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();
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh, main_test_rfh());
  
    // Navigate to new site, simulating an onbeforeunload approval.
    const GURL url2("http://www.yahoo.com");
    controller().LoadURL(
        url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_ack());
    orig_rfh->PrepareForCommit();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
  
    // Suppose the original renderer navigates before the new one is ready.
    orig_rfh->SendNavigate(0, true, GURL("http://www.google.com/foo"));
  
    // Verify that the pending navigation is cancelled.
    EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_ack());
    SiteInstance* instance2 = contents()->GetSiteInstance();
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh, main_test_rfh());
    EXPECT_EQ(instance1, instance2);
    EXPECT_EQ(nullptr, contents()->GetPendingMainFrame());
  }
  
  // Tests that if we go back twice (same-site then cross-site), and the same-site
  // RFH commits first, the cross-site RFH's navigation is canceled.
  // TODO(avi,creis): Consider changing this behavior to better match the user's
  // intent.
  TEST_F(WebContentsImplTest, CrossSiteNavigationBackPreempted) {
    // Start with a web ui page, which gets a new RVH with WebUI bindings.
    GURL url1(std::string(kChromeUIScheme) + "://" +
              std::string(kChromeUIGpuHost));
    controller().LoadURL(
        url1, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    int entry_id = controller().GetPendingEntry()->GetUniqueID();
    TestRenderFrameHost* webui_rfh = main_test_rfh();
    webui_rfh->PrepareForCommit();
    contents()->TestDidNavigate(webui_rfh, entry_id, true, url1,
                                ui::PAGE_TRANSITION_TYPED);
    NavigationEntry* entry1 = controller().GetLastCommittedEntry();
    SiteInstance* instance1 = contents()->GetSiteInstance();
  
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(webui_rfh, main_test_rfh());
    EXPECT_EQ(url1, entry1->GetURL());
    EXPECT_EQ(instance1,
              NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance());
    EXPECT_TRUE(webui_rfh->GetRenderViewHost()->GetEnabledBindings() &
                BINDINGS_POLICY_WEB_UI);
  
    // Navigate to new site.
    const GURL url2("http://www.google.com");
    controller().LoadURL(
        url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    entry_id = controller().GetPendingEntry()->GetUniqueID();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    TestRenderFrameHost* google_rfh = contents()->GetPendingMainFrame();
  
    // Simulate beforeunload approval.
    EXPECT_TRUE(webui_rfh->is_waiting_for_beforeunload_ack());
    webui_rfh->PrepareForCommit();
  
    // DidNavigate from the pending page.
    contents()->TestDidNavigate(google_rfh, entry_id, true, url2,
                                ui::PAGE_TRANSITION_TYPED);
    NavigationEntry* entry2 = controller().GetLastCommittedEntry();
    SiteInstance* instance2 = contents()->GetSiteInstance();
  
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(google_rfh, main_test_rfh());
    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());
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(google_rfh, entry_id, true, url3,
                                ui::PAGE_TRANSITION_TYPED);
    NavigationEntry* entry3 = controller().GetLastCommittedEntry();
    SiteInstance* instance3 = contents()->GetSiteInstance();
  
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(google_rfh, main_test_rfh());
    EXPECT_EQ(instance2, instance3);
    EXPECT_FALSE(contents()->GetPendingMainFrame());
    EXPECT_EQ(url3, entry3->GetURL());
    EXPECT_EQ(instance3,
              NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance());
  
    // Go back within the site.
    controller().GoBack();
    NavigationEntry* goback_entry = controller().GetPendingEntry();
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(entry2, controller().GetPendingEntry());
  
    // Before that commits, go back again.
    controller().GoBack();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    EXPECT_TRUE(contents()->GetPendingMainFrame());
    EXPECT_EQ(entry1, controller().GetPendingEntry());
  
    // Simulate beforeunload approval.
    EXPECT_TRUE(google_rfh->is_waiting_for_beforeunload_ack());
    base::TimeTicks now = base::TimeTicks::Now();
    google_rfh->PrepareForCommit();
    google_rfh->OnMessageReceived(
        FrameHostMsg_BeforeUnload_ACK(0, true, now, now));
  
    // DidNavigate from the first back. This aborts the second back's pending RFH.
    contents()->TestDidNavigate(google_rfh, 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, main_test_rfh());
    EXPECT_EQ(url2, controller().GetLastCommittedEntry()->GetURL());
  
    // We should not have corrupted the NTP entry.
    EXPECT_EQ(instance3,
              NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance());
    EXPECT_EQ(instance2,
              NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance());
    EXPECT_EQ(instance1,
              NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance());
    EXPECT_EQ(url1, entry1->GetURL());
  }
  
  // Tests that if we go back twice (same-site then cross-site), and the cross-
  // site RFH commits first, we ignore the now-swapped-out RFH's commit.
  TEST_F(WebContentsImplTest, CrossSiteNavigationBackOldNavigationIgnored) {
    // Start with a web ui page, which gets a new RVH with WebUI bindings.
    GURL url1(std::string(kChromeUIScheme) + "://" +
              std::string(kChromeUIGpuHost));
    controller().LoadURL(url1, Referrer(), ui::PAGE_TRANSITION_TYPED,
                         std::string());
    int entry_id = controller().GetPendingEntry()->GetUniqueID();
    TestRenderFrameHost* webui_rfh = main_test_rfh();
    webui_rfh->PrepareForCommit();
    contents()->TestDidNavigate(webui_rfh, entry_id, true, url1,
                                ui::PAGE_TRANSITION_TYPED);
    NavigationEntry* entry1 = controller().GetLastCommittedEntry();
    SiteInstance* instance1 = contents()->GetSiteInstance();
  
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(webui_rfh, main_test_rfh());
    EXPECT_EQ(url1, entry1->GetURL());
    EXPECT_EQ(instance1,
              NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance());
    EXPECT_TRUE(webui_rfh->GetRenderViewHost()->GetEnabledBindings() &
                BINDINGS_POLICY_WEB_UI);
  
    // Navigate to new site.
    const GURL url2("http://www.google.com");
    controller().LoadURL(url2, Referrer(), ui::PAGE_TRANSITION_TYPED,
                         std::string());
    entry_id = controller().GetPendingEntry()->GetUniqueID();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    TestRenderFrameHost* google_rfh = contents()->GetPendingMainFrame();
  
    // Simulate beforeunload approval.
    EXPECT_TRUE(webui_rfh->is_waiting_for_beforeunload_ack());
    webui_rfh->PrepareForCommit();
  
    // DidNavigate from the pending page.
    contents()->TestDidNavigate(google_rfh, entry_id, true, url2,
                                ui::PAGE_TRANSITION_TYPED);
    NavigationEntry* entry2 = controller().GetLastCommittedEntry();
    SiteInstance* instance2 = contents()->GetSiteInstance();
  
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(google_rfh, main_test_rfh());
    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());
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(google_rfh, entry_id, true, url3,
                                ui::PAGE_TRANSITION_TYPED);
    NavigationEntry* entry3 = controller().GetLastCommittedEntry();
    SiteInstance* instance3 = contents()->GetSiteInstance();
  
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(google_rfh, main_test_rfh());
    EXPECT_EQ(instance2, instance3);
    EXPECT_FALSE(contents()->GetPendingMainFrame());
    EXPECT_EQ(url3, entry3->GetURL());
    EXPECT_EQ(instance3,
              NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance());
  
    // Go back within the site.
    controller().GoBack();
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(entry2, controller().GetPendingEntry());
  
    // Before that commits, go back again.
    controller().GoBack();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    EXPECT_TRUE(contents()->GetPendingMainFrame());
    EXPECT_EQ(entry1, controller().GetPendingEntry());
    webui_rfh = contents()->GetPendingMainFrame();
  
    // DidNavigate from the second back.
    contents()->TestDidNavigate(webui_rfh, entry1->GetUniqueID(), false, url1,
                                ui::PAGE_TRANSITION_TYPED);
  
    // That should have landed us on the first entry.
    EXPECT_EQ(entry1, controller().GetLastCommittedEntry());
  
    // When the second back commits, it should be ignored.
    contents()->TestDidNavigate(google_rfh, entry2->GetUniqueID(), false, url2,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_EQ(entry1, controller().GetLastCommittedEntry());
  }
  
  // Test that during a slow cross-site navigation, a sub-frame navigation in the
  // original renderer will not cancel the slow navigation (bug 42029).
  TEST_F(WebContentsImplTest, CrossSiteNavigationNotPreemptedByFrame) {
    TestRenderFrameHost* orig_rfh = main_test_rfh();
  
    // 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();
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh, main_test_rfh());
  
    // 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(0, false,
                                          GURL("http://google.com/frame"),
                                          ui::PAGE_TRANSITION_AUTO_SUBFRAME);
    EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_ack());
  
    // Now simulate the onbeforeunload approval and verify the navigation is
    // not canceled.
    orig_rfh->PrepareForCommit();
    EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_ack());
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
  }
  
  namespace {
  void SetAsNonUserGesture(FrameHostMsg_DidCommitProvisionalLoad_Params* params) {
    params->gesture = NavigationGestureAuto;
  }
  }
  
  // Test that a cross-site navigation is not preempted if the previous
  // renderer sends a FrameNavigate message just before being told to stop.
  // We should only preempt the cross-site navigation if the previous renderer
  // has started a new navigation.  See http://crbug.com/79176.
  TEST_F(WebContentsImplTest, CrossSiteNotPreemptedDuringBeforeUnload) {
    // Navigate to WebUI URL.
    const GURL url("chrome://gpu");
    controller().LoadURL(
        url, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    int entry1_id = controller().GetPendingEntry()->GetUniqueID();
    TestRenderFrameHost* orig_rfh = main_test_rfh();
    orig_rfh->PrepareForCommit();
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
  
    // Navigate to new site, with the beforeunload request in flight.
    const GURL url2("http://www.yahoo.com");
    controller().LoadURL(
        url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    int entry2_id = controller().GetPendingEntry()->GetUniqueID();
    TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_ack());
    EXPECT_NE(orig_rfh, pending_rfh);
  
    // Suppose the first navigation tries to commit now, with a
    // FrameMsg_Stop in flight.  This should not cancel the pending navigation,
    // but it should act as if the beforeunload ack arrived.
    orig_rfh->SendNavigateWithModificationCallback(
        entry1_id, true, url, base::Bind(SetAsNonUserGesture));
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh, main_test_rfh());
    EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_ack());
    // It should commit.
    ASSERT_EQ(1, controller().GetEntryCount());
    EXPECT_EQ(url, controller().GetLastCommittedEntry()->GetURL());
  
    // The pending navigation should be able to commit successfully.
    contents()->TestDidNavigate(pending_rfh, entry2_id, true, url2,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(pending_rfh, main_test_rfh());
    EXPECT_EQ(2, controller().GetEntryCount());
  }
  
  // Test that NavigationEntries have the correct page state after going
  // forward and back.  Prevents regression for bug 1116137.
  TEST_F(WebContentsImplTest, NavigationEntryContentState) {
    TestRenderFrameHost* orig_rfh = main_test_rfh();
  
    // 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, 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, 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, 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 = main_test_rfh();
  
    // Navigate to about:blank.
    const GURL url(url::kAboutBlankURL);
    orig_rfh->SendRendererInitiatedNavigationRequest(url, false);
    contents()->TestDidNavigate(orig_rfh, 0, true, url,
                                ui::PAGE_TRANSITION_TYPED);
  
    // Should have a page state here.
    NavigationEntry* entry = controller().GetLastCommittedEntry();
    EXPECT_TRUE(entry->GetPageState().IsValid());
  
    // The SiteInstance should be available for other navigations to use.
    NavigationEntryImpl* entry_impl =
        NavigationEntryImpl::FromNavigationEntry(entry);
    EXPECT_FALSE(entry_impl->site_instance()->HasSite());
    int32_t site_instance_id = entry_impl->site_instance()->GetId();
  
    // Navigating to a normal page should not cause a process swap.
    const GURL new_url("http://www.google.com");
    controller().LoadURL(new_url, Referrer(),
                         ui::PAGE_TRANSITION_TYPED, std::string());
    entry = controller().GetPendingEntry();
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh, main_test_rfh());
    orig_rfh->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, 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 = main_test_rfh();
  
    // 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(orig_rfh, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_EQ(orig_rfh, main_test_rfh());
  
    // Toggle fullscreen mode on (as if initiated via IPC from renderer).
    EXPECT_FALSE(contents()->IsFullscreenForCurrentTab());
    EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
    orig_rfh->OnMessageReceived(
        FrameHostMsg_ToggleFullscreen(orig_rfh->GetRoutingID(), true));
    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();
    main_test_rfh()->PrepareForCommit();
    TestRenderFrameHost* const pending_rfh = contents()->GetPendingMainFrame();
    contents()->TestDidNavigate(pending_rfh, entry_id, true, url2,
                                ui::PAGE_TRANSITION_TYPED);
  
    // Confirm fullscreen has exited.
    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* orig_rfh = main_test_rfh();
  
    // 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, entry_id, true, url,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_EQ(orig_rfh, main_test_rfh());
  
    // 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, entry_id, true, url2,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_EQ(orig_rfh, main_test_rfh());
  
    // Sanity-check: Confirm we're not starting out in fullscreen mode.
    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(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, main_test_rfh());
      contents()->TestDidNavigate(orig_rfh, entry_id, false, url,
                                  ui::PAGE_TRANSITION_FORWARD_BACK);
      orig_rfh->SimulateNavigationStop();
  
      // Confirm fullscreen has exited.
      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.
    main_test_rfh()->InitializeRenderFrameIfNeeded();
    main_test_rfh()->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(main_test_rfh(), entry_id, true,
                                url, ui::PAGE_TRANSITION_TYPED);
  
    // Toggle fullscreen mode on (as if initiated via IPC from renderer).
    EXPECT_FALSE(contents()->IsFullscreenForCurrentTab());
    EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
    main_test_rfh()->OnMessageReceived(FrameHostMsg_ToggleFullscreen(
        main_test_rfh()->GetRoutingID(), true));
    EXPECT_TRUE(contents()->IsFullscreenForCurrentTab());
    EXPECT_TRUE(fake_delegate.IsFullscreenForTabOrPending(contents()));
  
    // Crash the renderer.
    main_test_rfh()->GetProcess()->SimulateCrash();
  
    // Confirm fullscreen has exited.
    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(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(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(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(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(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(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(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(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()->PrepareForCommit();
    main_test_rfh()->SendNavigate(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(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(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(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(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(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(interstitial_entry_id, true, url);
  
    // While interstitial showing, navigate to a new URL.
    const GURL url2("http://www.yahoo.com");
    main_test_rfh()->NavigateAndCommitRendererInitiated(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(true, url1);
    EXPECT_EQ(1, controller().GetEntryCount());
  
    // Show interstitial.
    TestInterstitialPage::InterstitialState state =
        TestInterstitialPage::INVALID;
    bool deleted = false;
    GURL interstitial_url("http://interstitial");
    TestInterstitialPage* interstitial =
        new TestInterstitialPage(contents(), true, interstitial_url,
                                 &state, &deleted);
    TestInterstitialPageStateGuard state_guard(interstitial);
    interstitial->Show();
    int interstitial_entry_id = controller().GetTransientEntry()->GetUniqueID();
    interstitial->TestDidNavigate(interstitial_entry_id, true,
                                  interstitial_url);
    EXPECT_EQ(2, controller().GetEntryCount());
  
    // While the interstitial is showing, go back. This will dismiss the
    // interstitial and not initiate a navigation, but just show the existing
    // RenderFrameHost.
    controller().GoBack();
  
    // Make sure we are back to the original page and that the interstitial is
    // gone.
    EXPECT_EQ(TestInterstitialPage::CANCELED, state);
    NavigationEntry* entry = controller().GetVisibleEntry();
    ASSERT_TRUE(entry);
    EXPECT_EQ(url1.spec(), entry->GetURL().spec());
    EXPECT_EQ(1, controller().GetEntryCount());
  
    RunAllPendingInMessageLoop();
    EXPECT_TRUE(deleted);
  }
  
  // Test navigating to a page that shows an interstitial, has a renderer crash,
  // and then goes back.
  TEST_F(WebContentsImplTest, ShowInterstitialCrashRendererThenGoBack) {
    // Navigate to a page so we have a navigation entry in the controller.
    GURL url1("http://www.google.com");
    main_test_rfh()->NavigateAndCommitRendererInitiated(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(interstitial_entry_id, true,
                                  interstitial_url);
  
    // Crash the renderer
    main_test_rfh()->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(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
    main_test_rfh()->GetProcess()->SimulateCrash();
  
    interstitial->TestDidNavigate(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(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(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(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(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(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(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(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");
    main_test_rfh()->SendNavigate(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(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(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(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");
    main_test_rfh()->SendNavigate(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(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(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(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(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(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(main_test_rfh(), 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(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(main_test_rfh(),
        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(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(interstitial_entry_id, true, url2);
    EXPECT_TRUE(interstitial->is_showing());
    EXPECT_EQ(2, controller().GetEntryCount());
  
    // Create another NavigationController.
    GURL url3("http://foo2");
    std::unique_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.
    std::unique_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(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.
    std::unique_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(), false);
    EXPECT_EQ(url_normalized, other_observer.last_url());
  }
  
  // Test that if a pending contents is deleted before it is shown, we don't
  // crash.
  TEST_F(WebContentsImplTest, PendingContents) {
    std::unique_ptr<TestWebContents> other_contents(
        static_cast<TestWebContents*>(CreateTestWebContents()));
    contents()->AddPendingContents(other_contents.get());
    int process_id = other_contents->GetRenderViewHost()->GetProcess()->GetID();
    int route_id = other_contents->GetRenderViewHost()->GetRoutingID();
    other_contents.reset();
    EXPECT_EQ(nullptr, contents()->GetCreatedWindow(process_id, 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*>(
        main_test_rfh()->GetRenderViewHost()->GetWidget()->GetView());
  
    // With no capturers, setting and un-setting occlusion should change the
    // view's occlusion state.
    EXPECT_FALSE(view->is_showing());
    contents()->WasShown();
    EXPECT_TRUE(view->is_showing());
    contents()->WasHidden();
    EXPECT_FALSE(view->is_showing());
    contents()->WasShown();
    EXPECT_TRUE(view->is_showing());
  
    // Add a capturer and try to hide the contents. The view will remain visible.
    contents()->IncrementCapturerCount(gfx::Size());
    contents()->WasHidden();
    EXPECT_TRUE(view->is_showing());
  
    // Remove the capturer, and the WasHidden should take effect.
    contents()->DecrementCapturerCount();
    EXPECT_FALSE(view->is_showing());
  }
  
  TEST_F(WebContentsImplTest, CapturerPreventsOcclusion) {
    const gfx::Size original_preferred_size(1024, 768);
    contents()->UpdatePreferredSize(original_preferred_size);
  
    TestRenderWidgetHostView* view = static_cast<TestRenderWidgetHostView*>(
        main_test_rfh()->GetRenderViewHost()->GetWidget()->GetView());
  
    // With no capturers, setting and un-setting occlusion should change the
    // view's occlusion state.
    EXPECT_FALSE(view->is_occluded());
    contents()->WasOccluded();
    EXPECT_TRUE(view->is_occluded());
    contents()->WasUnOccluded();
    EXPECT_FALSE(view->is_occluded());
    contents()->WasOccluded();
    EXPECT_TRUE(view->is_occluded());
  
    // Add a capturer. This should cause the view to be un-occluded.
    contents()->IncrementCapturerCount(gfx::Size());
    EXPECT_FALSE(view->is_occluded());
  
    // Try to occlude the view. This will fail to propagate because of the
    // active capturer.
    contents()->WasOccluded();
    EXPECT_FALSE(view->is_occluded());
  
    // Remove the capturer and try again.
    contents()->DecrementCapturerCount();
    EXPECT_FALSE(view->is_occluded());
    contents()->WasOccluded();
    EXPECT_TRUE(view->is_occluded());
  }
  
  // Tests that GetLastActiveTime starts with a real, non-zero time and updates
  // on activity.
  TEST_F(WebContentsImplTest, GetLastActiveTime) {
    // The WebContents starts with a valid creation time.
    EXPECT_FALSE(contents()->GetLastActiveTime().is_null());
  
    // Reset the last active time to a known-bad value.
    contents()->last_active_time_ = base::TimeTicks();
    ASSERT_TRUE(contents()->GetLastActiveTime().is_null());
  
    // Simulate activating the WebContents. The active time should update.
    contents()->WasShown();
    EXPECT_FALSE(contents()->GetLastActiveTime().is_null());
  }
  
  class ContentsZoomChangedDelegate : public WebContentsDelegate {
   public:
    ContentsZoomChangedDelegate() :
      contents_zoom_changed_call_count_(0),
      last_zoom_in_(false) {
    }
  
    int GetAndResetContentsZoomChangedCallCount() {
      int count = contents_zoom_changed_call_count_;
      contents_zoom_changed_call_count_ = 0;
      return count;
    }
  
    bool last_zoom_in() const {
      return last_zoom_in_;
    }
  
    // WebContentsDelegate:
    void ContentsZoomChange(bool zoom_in) override {
      contents_zoom_changed_call_count_++;
      last_zoom_in_ = zoom_in;
    }
  
   private:
    int contents_zoom_changed_call_count_;
    bool last_zoom_in_;
  
    DISALLOW_COPY_AND_ASSIGN(ContentsZoomChangedDelegate);
  };
  
  // Tests that some mouseehweel events get turned into browser zoom requests.
  TEST_F(WebContentsImplTest, HandleWheelEvent) {
    using blink::WebInputEvent;
  
    std::unique_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, 0, 0, 1, modifiers, false);
    EXPECT_FALSE(contents()->HandleWheelEvent(event));
    EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
  
    // But whenever the ctrl modifier is applied zoom can be increased or
    // decreased. Except on MacOS where we never want to adjust zoom
    // with mousewheel.
    modifiers = WebInputEvent::ControlKey;
    event =
        SyntheticWebMouseWheelEventBuilder::Build(0, 0, 0, 1, modifiers, false);
    bool handled = contents()->HandleWheelEvent(event);
  #if defined(USE_AURA)
    EXPECT_TRUE(handled);
    EXPECT_EQ(1, delegate->GetAndResetContentsZoomChangedCallCount());
    EXPECT_TRUE(delegate->last_zoom_in());
  #else
    EXPECT_FALSE(handled);
    EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
  #endif
  
    modifiers = WebInputEvent::ControlKey | WebInputEvent::ShiftKey |
        WebInputEvent::AltKey;
    event =
        SyntheticWebMouseWheelEventBuilder::Build(0, 0, 2, -5, modifiers, false);
    handled = contents()->HandleWheelEvent(event);
  #if defined(USE_AURA)
    EXPECT_TRUE(handled);
    EXPECT_EQ(1, delegate->GetAndResetContentsZoomChangedCallCount());
    EXPECT_FALSE(delegate->last_zoom_in());
  #else
    EXPECT_FALSE(handled);
    EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
  #endif
  
    // Unless there is no vertical movement.
    event =
        SyntheticWebMouseWheelEventBuilder::Build(0, 0, 2, 0, modifiers, false);
    EXPECT_FALSE(contents()->HandleWheelEvent(event));
    EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
  
    // Events containing precise scrolling deltas also shouldn't result in the
    // zoom being adjusted, to avoid accidental adjustments caused by
    // two-finger-scrolling on a touchpad.
    modifiers = WebInputEvent::ControlKey;
    event =
        SyntheticWebMouseWheelEventBuilder::Build(0, 0, 0, 5, modifiers, true);
    EXPECT_FALSE(contents()->HandleWheelEvent(event));
    EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
  
    // Ensure pointers to the delegate aren't kept beyond its lifetime.
    contents()->SetDelegate(nullptr);
  }
  
  // Tests that GetRelatedActiveContentsCount is shared between related
  // SiteInstances and includes WebContents that have not navigated yet.
  TEST_F(WebContentsImplTest, ActiveContentsCountBasic) {
    scoped_refptr<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());
  
    std::unique_ptr<TestWebContents> contents1(
        TestWebContents::Create(browser_context(), instance1.get()));
    EXPECT_EQ(1u, instance1->GetRelatedActiveContentsCount());
    EXPECT_EQ(1u, instance2->GetRelatedActiveContentsCount());
  
    std::unique_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());
  
    std::unique_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 (IsBrowserSideNavigationEnabled())
      contents->GetMainFrame()->PrepareForCommit();
    EXPECT_TRUE(contents->CrossProcessNavigationPending());
    EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
    contents->GetPendingMainFrame()->SendNavigate(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());
  
    std::unique_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(entry_id, true, kWebUIUrl);
    EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount());
    EXPECT_EQ(1u, instance_webui->GetRelatedActiveContentsCount());
  
    contents.reset();
    EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount());
    EXPECT_EQ(0u, instance_webui->GetRelatedActiveContentsCount());
  }
  
  class LoadingWebContentsObserver : public WebContentsObserver {
   public:
    explicit LoadingWebContentsObserver(WebContents* contents)
        : WebContentsObserver(contents),
          is_loading_(false) {
    }
    ~LoadingWebContentsObserver() override {}
  
    void DidStartLoading() override { is_loading_ = true; }
    void DidStopLoading() override { is_loading_ = false; }
  
    bool is_loading() const { return is_loading_; }
  
   private:
    bool is_loading_;
  
    DISALLOW_COPY_AND_ASSIGN(LoadingWebContentsObserver);
  };
  
  // Subclass of WebContentsImplTest for cases that need out-of-process iframes.
  class WebContentsImplTestWithSiteIsolation : public WebContentsImplTest {
   public:
    WebContentsImplTestWithSiteIsolation() {
      IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
    }
  };
  
  // Ensure that DidStartLoading/DidStopLoading events balance out properly with
  // interleaving cross-process navigations in multiple subframes.
  // See https://crbug.com/448601 for details of the underlying issue. The
  // sequence of events that reproduce it are as follows:
  // * Navigate top-level frame with one subframe.
  // * Subframe navigates more than once before the top-level frame has had a
  //   chance to complete the load.
  // The subframe navigations cause the loading_frames_in_progress_ to drop down
  // to 0, while the loading_progresses_ map is not reset.
  TEST_F(WebContentsImplTestWithSiteIsolation, StartStopEventsBalance) {
    // The bug manifests itself in regular mode as well, but browser-initiated
    // navigation of subframes is only possible in --site-per-process mode within
    // unit tests.
    const GURL initial_url("about:blank");
    const GURL main_url("http://www.chromium.org");
    const GURL foo_url("http://foo.chromium.org");
    const GURL bar_url("http://bar.chromium.org");
    TestRenderFrameHost* orig_rfh = main_test_rfh();
  
    // 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();
  
    // PlzNavigate: the RenderFrameHost does not expect to receive
    // DidStartLoading IPCs for navigations to different documents.
    if (!IsBrowserSideNavigationEnabled()) {
      orig_rfh->OnMessageReceived(
          FrameHostMsg_DidStartLoading(orig_rfh->GetRoutingID(), false));
    }
    main_test_rfh()->PrepareForCommit();
    contents()->TestDidNavigate(orig_rfh, entry_id, true, main_url,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(orig_rfh, main_test_rfh());
    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.
    {
      if (!IsBrowserSideNavigationEnabled()) {
        subframe->OnMessageReceived(
            FrameHostMsg_DidStartLoading(subframe->GetRoutingID(), true));
      }
      subframe->SendNavigateWithTransition(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();
      if (!IsBrowserSideNavigationEnabled()) {
        subframe->OnMessageReceived(
            FrameHostMsg_DidStartLoading(subframe->GetRoutingID(), true));
      }
      subframe->SendNavigateWithTransition(10, 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();
  
      if (!IsBrowserSideNavigationEnabled()) {
        subframe->OnMessageReceived(
            FrameHostMsg_DidStartLoading(subframe->GetRoutingID(), true));
      }
  
      // Commit the navigation in the child frame and send the DidStopLoading
      // message.
      subframe->PrepareForCommit();
      contents()->TestDidNavigate(subframe, 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 = main_test_rfh();
  
    // Start a browser-initiated cross-process navigation to |kUrl2|. There should
    // be a pending RenderFrameHost and the WebContents should be loading.
    controller().LoadURL(
        kUrl2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    int entry_id = controller().GetPendingEntry()->GetUniqueID();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    TestRenderFrameHost* pending_rfh = contents()->GetPendingMainFrame();
    ASSERT_TRUE(pending_rfh);
    EXPECT_TRUE(contents()->IsLoading());
  
    // The current RenderFrameHost starts a non user-initiated render-initiated
    // navigation and sends a DidStartLoading IPC. The WebContents should still be
    // loading.
    current_rfh->OnMessageReceived(
        FrameHostMsg_DidStartLoading(current_rfh->GetRoutingID(), false));
    EXPECT_TRUE(contents()->IsLoading());
  
    // Simulate the pending RenderFrameHost DidStartLoading. There should still be
    // a pending RenderFrameHost and the WebContents should still be loading.
    pending_rfh->PrepareForCommit();
    pending_rfh->OnMessageReceived(
        FrameHostMsg_DidStartLoading(pending_rfh->GetRoutingID(), false));
    EXPECT_EQ(contents()->GetPendingMainFrame(), pending_rfh);
    EXPECT_TRUE(contents()->IsLoading());
  
    // Simulate the commit and DidStopLoading from the renderer-initiated
    // navigation in the current RenderFrameHost. There should still be a pending
    // RenderFrameHost and the WebContents should still be loading.
    current_rfh->SendNavigateWithModificationCallback(
        0, true, kUrl3, base::Bind(SetAsNonUserGesture));
    current_rfh->OnMessageReceived(
        FrameHostMsg_DidStopLoading(current_rfh->GetRoutingID()));
    EXPECT_EQ(contents()->GetPendingMainFrame(), pending_rfh);
    EXPECT_TRUE(contents()->IsLoading());
    // It should commit.
    ASSERT_EQ(2, controller().GetEntryCount());
    EXPECT_EQ(kUrl3, controller().GetLastCommittedEntry()->GetURL());
  
    // Commit the navigation. The formerly pending RenderFrameHost should now be
    // the current RenderFrameHost and the WebContents should still be loading.
    contents()->TestDidNavigate(pending_rfh, entry_id, true, kUrl2,
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->GetPendingMainFrame());
    TestRenderFrameHost* new_current_rfh = main_test_rfh();
    EXPECT_EQ(new_current_rfh, pending_rfh);
    EXPECT_TRUE(contents()->IsLoading());
    EXPECT_EQ(3, controller().GetEntryCount());
  
    // Simulate the new current RenderFrameHost DidStopLoading. The WebContents
    // should now have stopped loading.
    new_current_rfh->OnMessageReceived(
        FrameHostMsg_DidStopLoading(new_current_rfh->GetRoutingID()));
    EXPECT_EQ(main_test_rfh(), new_current_rfh);
    EXPECT_FALSE(contents()->IsLoading());
  }
  
  TEST_F(WebContentsImplTest, MediaPowerSaveBlocking) {
    // Verify that both negative and positive player ids don't blow up.
    const int kPlayerAudioVideoId = 15;
    const int kPlayerAudioOnlyId = -15;
    const int kPlayerVideoOnlyId = 30;
    const int kPlayerRemoteId = -30;
  
    EXPECT_FALSE(has_audio_power_save_blocker());
    EXPECT_FALSE(has_video_power_save_blocker());
  
    TestRenderFrameHost* rfh = main_test_rfh();
    AudioStreamMonitor* monitor = contents()->audio_stream_monitor();
  
    // Ensure RenderFrame is initialized before simulating events coming from it.
    main_test_rfh()->InitializeRenderFrameIfNeeded();
  
    // Send a fake audio stream monitor notification.  The audio power save
    // blocker should be created.
    monitor->set_was_recently_audible_for_testing(true);
    contents()->NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB);
    EXPECT_TRUE(has_audio_power_save_blocker());
  
    // Send another fake notification, this time when WasRecentlyAudible() will
    // be false.  The power save blocker should be released.
    monitor->set_was_recently_audible_for_testing(false);
    contents()->NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB);
    EXPECT_FALSE(has_audio_power_save_blocker());
  
    // Start a player with both audio and video.  A video power save blocker
    // should be created.  If audio stream monitoring is available, an audio power
    // save blocker should be created too.
    rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaPlaying(
        0, kPlayerAudioVideoId, true, true, false,
        media::MediaContentType::Persistent));
    EXPECT_TRUE(has_video_power_save_blocker());
    EXPECT_FALSE(has_audio_power_save_blocker());
  
    // Upon hiding the video power save blocker should be released.
    contents()->WasHidden();
    EXPECT_FALSE(has_video_power_save_blocker());
  
    // Start another player that only has video.  There should be no change in
    // the power save blockers.  The notification should take into account the
    // visibility state of the WebContents.
    rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaPlaying(
        0, kPlayerVideoOnlyId, true, false, false,
        media::MediaContentType::Persistent));
    EXPECT_FALSE(has_video_power_save_blocker());
    EXPECT_FALSE(has_audio_power_save_blocker());
  
    // Showing the WebContents should result in the creation of the blocker.
    contents()->WasShown();
    EXPECT_TRUE(has_video_power_save_blocker());
  
    // Start another player that only has audio.  There should be no change in
    // the power save blockers.
    rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaPlaying(
        0, kPlayerAudioOnlyId, false, true, false,
        media::MediaContentType::Persistent));
    EXPECT_TRUE(has_video_power_save_blocker());
    EXPECT_FALSE(has_audio_power_save_blocker());
  
    // Start a remote player. There should be no change in the power save
    // blockers.
    rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaPlaying(
        0, kPlayerRemoteId, true, true, true,
        media::MediaContentType::Persistent));
    EXPECT_TRUE(has_video_power_save_blocker());
    EXPECT_FALSE(has_audio_power_save_blocker());
  
    // Destroy the original audio video player.  Both power save blockers should
    // remain.
    rfh->OnMessageReceived(
        MediaPlayerDelegateHostMsg_OnMediaPaused(0, kPlayerAudioVideoId, false));
    EXPECT_TRUE(has_video_power_save_blocker());
    EXPECT_FALSE(has_audio_power_save_blocker());
  
    // Destroy the audio only player.  The video power save blocker should remain.
    rfh->OnMessageReceived(
        MediaPlayerDelegateHostMsg_OnMediaPaused(0, kPlayerAudioOnlyId, false));
    EXPECT_TRUE(has_video_power_save_blocker());
    EXPECT_FALSE(has_audio_power_save_blocker());
  
    // Destroy the video only player.  No power save blockers should remain.
    rfh->OnMessageReceived(
        MediaPlayerDelegateHostMsg_OnMediaPaused(0, kPlayerVideoOnlyId, false));
    EXPECT_FALSE(has_video_power_save_blocker());
    EXPECT_FALSE(has_audio_power_save_blocker());
  
    // Destroy the remote player. No power save blockers should remain.
    rfh->OnMessageReceived(
        MediaPlayerDelegateHostMsg_OnMediaPaused(0, kPlayerRemoteId, false));
    EXPECT_FALSE(has_video_power_save_blocker());
    EXPECT_FALSE(has_audio_power_save_blocker());
  
    // Start a player with both audio and video.  A video power save blocker
    // should be created.  If audio stream monitoring is available, an audio power
    // save blocker should be created too.
    rfh->OnMessageReceived(MediaPlayerDelegateHostMsg_OnMediaPlaying(
        0, kPlayerAudioVideoId, true, true, false,
        media::MediaContentType::Persistent));
    EXPECT_TRUE(has_video_power_save_blocker());
    EXPECT_FALSE(has_audio_power_save_blocker());
  
    // Crash the renderer.
    main_test_rfh()->GetProcess()->SimulateCrash();
  
    // Verify that all the power save blockers have been released.
    EXPECT_FALSE(has_video_power_save_blocker());
    EXPECT_FALSE(has_audio_power_save_blocker());
  }
  
  TEST_F(WebContentsImplTest, ThemeColorChangeDependingOnFirstVisiblePaint) {
    TestWebContentsObserver observer(contents());
    TestRenderFrameHost* rfh = main_test_rfh();
    rfh->InitializeRenderFrameIfNeeded();
  
    SkColor transparent = SK_ColorTRANSPARENT;
  
    EXPECT_EQ(transparent, contents()->GetThemeColor());
    EXPECT_EQ(transparent, observer.last_theme_color());
  
    // Theme color changes should not propagate past the WebContentsImpl before
    // the first visually non-empty paint has occurred.
    RenderViewHostTester::TestOnMessageReceived(
        test_rvh(),
        FrameHostMsg_DidChangeThemeColor(rfh->GetRoutingID(), SK_ColorRED));
  
    EXPECT_EQ(SK_ColorRED, contents()->GetThemeColor());
    EXPECT_EQ(transparent, observer.last_theme_color());
  
    // Simulate that the first visually non-empty paint has occurred. This will
    // propagate the current theme color to the delegates.
    RenderViewHostTester::TestOnMessageReceived(
        test_rvh(),
        ViewHostMsg_DidFirstVisuallyNonEmptyPaint(rfh->GetRoutingID()));
  
    EXPECT_EQ(SK_ColorRED, contents()->GetThemeColor());
    EXPECT_EQ(SK_ColorRED, observer.last_theme_color());
  
    // Additional changes made by the web contents should propagate as well.
    RenderViewHostTester::TestOnMessageReceived(
        test_rvh(),
        FrameHostMsg_DidChangeThemeColor(rfh->GetRoutingID(), SK_ColorGREEN));
  
    EXPECT_EQ(SK_ColorGREEN, contents()->GetThemeColor());
    EXPECT_EQ(SK_ColorGREEN, observer.last_theme_color());
  }
  
  // Test that if a resource is loaded with empty security info, the SSLManager
  // does not mistakenly think it has seen a good certificate and thus forget any
  // user exceptions for that host. See https://crbug.com/516808.
  TEST_F(WebContentsImplTest, LoadResourceWithEmptySecurityInfo) {
    WebContentsImplTestBrowserClient browser_client;
    SetBrowserClientForTesting(&browser_client);
  
    scoped_refptr<net::X509Certificate> cert =
        net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
    const GURL test_url("https://example.test");
  
    SSLHostStateDelegate* state_delegate =
        contents()->controller_.GetBrowserContext()->GetSSLHostStateDelegate();
    ASSERT_TRUE(state_delegate);
    state_delegate->AllowCert(test_url.host(), *cert.get(), 1);
    EXPECT_TRUE(state_delegate->HasAllowException(test_url.host()));
    contents()->controller_.ssl_manager()->DidStartResourceResponse(test_url,
                                                                    false, 0);
  
    EXPECT_TRUE(state_delegate->HasAllowException(test_url.host()));
  
    DeleteContents();
  }
  
  namespace {
  
  class TestJavaScriptDialogManager : public JavaScriptDialogManager {
   public:
    TestJavaScriptDialogManager() {}
    ~TestJavaScriptDialogManager() override {}
  
    size_t reset_count() { return reset_count_; }
  
    // JavaScriptDialogManager
  
    void RunJavaScriptDialog(WebContents* web_contents,
                             const GURL& origin_url,
                             JavaScriptMessageType javascript_message_type,
                             const base::string16& message_text,
                             const base::string16& default_prompt_text,
                             const DialogClosedCallback& callback,
                             bool* did_suppress_message) override {
      *did_suppress_message = true;
    };
  
    void RunBeforeUnloadDialog(WebContents* web_contents,
                               bool is_reload,
                               const DialogClosedCallback& callback) override {}
  
    bool HandleJavaScriptDialog(WebContents* web_contents,
                                bool accept,
                                const base::string16* prompt_override) override {
      return true;
    }
  
    void CancelDialogs(WebContents* web_contents,
                       bool suppress_callbacks,
                       bool reset_state) override {
      if (reset_state)
        ++reset_count_;
    }
  
   private:
    size_t reset_count_ = 0;
  
    DISALLOW_COPY_AND_ASSIGN(TestJavaScriptDialogManager);
  };
  
  }  // namespace
  
  TEST_F(WebContentsImplTest, ResetJavaScriptDialogOnUserNavigate) {
    TestJavaScriptDialogManager dialog_manager;
    contents()->SetJavaScriptDialogManagerForTesting(&dialog_manager);
  
    // A user-initiated navigation.
    contents()->TestDidNavigate(main_test_rfh(), 0, true,
                                GURL("about:whatever"),
                                ui::PAGE_TRANSITION_TYPED);
    EXPECT_EQ(1u, dialog_manager.reset_count());
  
    // An automatic navigation.
    main_test_rfh()->SendNavigateWithModificationCallback(
        0, true, GURL(url::kAboutBlankURL), base::Bind(SetAsNonUserGesture));
  
    EXPECT_EQ(1u, dialog_manager.reset_count());
  
    contents()->SetJavaScriptDialogManagerForTesting(nullptr);
  }
  
  }  // namespace content
  