text: // Copyright 2013 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 <stddef.h>
  #include <stdint.h>
  
  #include <functional>
  #include <memory>
  #include <set>
  
  #include "base/bind.h"
  #include "base/callback_helpers.h"
  #include "base/command_line.h"
  #include "base/json/json_reader.h"
  #include "base/location.h"
  #include "base/memory/ref_counted.h"
  #include "base/memory/weak_ptr.h"
  #include "base/path_service.h"
  #include "base/run_loop.h"
  #include "base/single_thread_task_runner.h"
  #include "base/strings/string_split.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/synchronization/lock.h"
  #include "base/test/bind.h"
  #include "base/test/scoped_feature_list.h"
  #include "base/thread_annotations.h"
  #include "base/threading/thread_task_runner_handle.h"
  #include "base/time/time.h"
  #include "base/values.h"
  #include "build/build_config.h"
  #include "build/chromeos_buildflags.h"
  #include "components/network_session_configurator/common/network_switches.h"
  #include "content/browser/child_process_security_policy_impl.h"
  #include "content/browser/renderer_host/frame_tree_node.h"
  #include "content/browser/renderer_host/navigation_request.h"
  #include "content/browser/renderer_host/render_frame_host_impl.h"
  #include "content/browser/renderer_host/render_frame_proxy_host.h"
  #include "content/browser/renderer_host/render_view_host_impl.h"
  #include "content/browser/site_instance_impl.h"
  #include "content/browser/web_contents/web_contents_impl.h"
  #include "content/browser/webui/web_ui_controller_factory_registry.h"
  #include "content/browser/webui/web_ui_impl.h"
  #include "content/common/content_constants_internal.h"
  #include "content/common/content_navigation_policy.h"
  #include "content/public/browser/browser_child_process_host.h"
  #include "content/public/browser/child_process_launcher_utils.h"
  #include "content/public/browser/navigation_controller.h"
  #include "content/public/browser/navigation_entry.h"
  #include "content/public/browser/render_process_host.h"
  #include "content/public/browser/site_isolation_policy.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/browser/web_contents_observer.h"
  #include "content/public/browser/web_ui_message_handler.h"
  #include "content/public/common/bindings_policy.h"
  #include "content/public/common/content_client.h"
  #include "content/public/common/content_features.h"
  #include "content/public/common/content_switches.h"
  #include "content/public/common/url_constants.h"
  #include "content/public/test/browser_test.h"
  #include "content/public/test/browser_test_utils.h"
  #include "content/public/test/commit_message_delayer.h"
  #include "content/public/test/content_browser_test.h"
  #include "content/public/test/content_browser_test_utils.h"
  #include "content/public/test/navigation_handle_observer.h"
  #include "content/public/test/test_frame_navigation_observer.h"
  #include "content/public/test/test_navigation_observer.h"
  #include "content/public/test/test_navigation_throttle.h"
  #include "content/public/test/test_navigation_throttle_inserter.h"
  #include "content/public/test/test_utils.h"
  #include "content/public/test/url_loader_interceptor.h"
  #include "content/shell/browser/shell.h"
  #include "content/test/content_browser_test_utils_internal.h"
  #include "content/test/render_document_feature.h"
  #include "content/test/test_content_browser_client.h"
  #include "net/dns/mock_host_resolver.h"
  #include "net/test/embedded_test_server/controllable_http_response.h"
  #include "net/test/embedded_test_server/default_handlers.h"
  #include "net/test/embedded_test_server/embedded_test_server.h"
  #include "net/test/embedded_test_server/request_handler_util.h"
  #include "net/test/url_request/url_request_failed_job.h"
  #include "services/network/public/cpp/features.h"
  #include "testing/gmock/include/gmock/gmock-matchers.h"
  #include "third_party/blink/public/common/action_after_pagehide.h"
  #include "third_party/blink/public/common/web_preferences/web_preferences.h"
  
  using base::ASCIIToUTF16;
  using blink::ActionAfterPagehide;
  
  namespace content {
  
  namespace {
  
  // Helper function that return true in cases where the current process model
  // will return the same SiteInstance for a cross-process navigation.
  bool ExpectSameSiteInstance() {
    return AreDefaultSiteInstancesEnabled() &&
           !CanCrossSiteNavigationsProactivelySwapBrowsingInstances();
  }
  
  const char kOpenUrlViaClickTargetFunc[] =
      "(function(url) {\n"
      "  var lnk = document.createElement(\"a\");\n"
      "  lnk.href = url;\n"
      "  lnk.rel = 'opener';\n"
      "  lnk.target = \"_blank\";\n"
      "  document.body.appendChild(lnk);\n"
      "  lnk.click();\n"
      "})";
  
  // Adds a link with given url and target=_blank, and clicks on it.
  void OpenUrlViaClickTarget(const ToRenderFrameHost& adapter, const GURL& url) {
    EXPECT_TRUE(ExecuteScript(adapter, std::string(kOpenUrlViaClickTargetFunc) +
                                           "(\"" + url.spec() + "\");"));
  }
  
  content::RenderFrameHostChangedCallback GetAsyncScriptExecutorCallback(
      std::string callback_script) {
    return base::BindOnce(
        [](std::string callback_script, RenderFrameHost* old_host,
           RenderFrameHost* new_host) {
          ExecuteScriptAsync(old_host, callback_script);
        },
        callback_script);
  }
  
  // DO NOT USE THIS FUNCTION, use GetAsyncScriptExecutorCallback() instead.
  // GetScriptExecutorCallback must not be used, because it forces waiting for a
  // browser <-> renderer IPC roundtrip while being in the middle of a very
  // complex operation: swapping the current RenderFrameHost
  content::RenderFrameHostChangedCallback GetScriptExecutorCallback(
      std::string callback_script) {
    return base::BindOnce(
        [](std::string callback_script, RenderFrameHost* old_host,
           RenderFrameHost* new_host) {
          EXPECT_TRUE(ExecuteScript(old_host, callback_script));
        },
        callback_script);
  }
  
  class TestWebUIMessageHandler : public WebUIMessageHandler {
   public:
    using WebUIMessageHandler::AllowJavascript;
    using WebUIMessageHandler::IsJavascriptAllowed;
  
   protected:
    void RegisterMessages() override {}
  };
  
  // This class implements waiting for RenderFrameHost destruction. It relies on
  // the fact that RenderFrameDeleted event is fired when RenderFrameHost is
  // destroyed.
  // Note: RenderFrameDeleted is also fired when the process associated with the
  // RenderFrameHost crashes, so this cannot be used in cases where process dying
  // is expected.
  class RenderFrameHostDestructionObserver : public WebContentsObserver {
   public:
    explicit RenderFrameHostDestructionObserver(RenderFrameHost* rfh)
        : WebContentsObserver(WebContents::FromRenderFrameHost(rfh)),
          message_loop_runner_(new MessageLoopRunner),
          deleted_(false),
          render_frame_host_(rfh) {}
    ~RenderFrameHostDestructionObserver() override = default;
  
    bool deleted() const { return deleted_; }
  
    void Wait() {
      if (deleted_)
        return;
  
      message_loop_runner_->Run();
    }
  
    // WebContentsObserver implementation:
    void RenderFrameDeleted(RenderFrameHost* rfh) override {
      if (rfh == render_frame_host_) {
        CHECK(!deleted_);
        deleted_ = true;
      }
  
      if (deleted_ && message_loop_runner_->loop_running()) {
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, message_loop_runner_->QuitClosure());
      }
    }
  
   private:
    scoped_refptr<MessageLoopRunner> message_loop_runner_;
    bool deleted_;
    RenderFrameHost* render_frame_host_;
  };
  
  // A NavigationThrottle implementation that blocks all outgoing navigation
  // requests for a specific WebContents. It is used to block navigations to
  // WebUI URLs in tests.
  class RequestBlockingNavigationThrottle : public NavigationThrottle {
   public:
    explicit RequestBlockingNavigationThrottle(NavigationHandle* handle)
        : NavigationThrottle(handle) {}
  
    static std::unique_ptr<NavigationThrottle> Create(NavigationHandle* handle) {
      return std::make_unique<RequestBlockingNavigationThrottle>(handle);
    }
  
   private:
    ThrottleCheckResult WillStartRequest() override {
      return NavigationThrottle::BLOCK_REQUEST;
    }
  
    const char* GetNameForLogging() override {
      return "RequestBlockingNavigationThrottle";
    }
  
    DISALLOW_COPY_AND_ASSIGN(RequestBlockingNavigationThrottle);
  };
  
  // Helper function for error page navigations that makes sure that the last
  // committed origin on |node| is an opaque origin with a precursor that matches
  // |url|'s origin.
  // Returns true if the frame has an opaque origin with the expected precursor
  // information. Otherwise returns false.
  bool IsOriginOpaqueAndCompatibleWithURL(FrameTreeNode* node, const GURL& url) {
    url::Origin frame_origin =
        node->current_frame_host()->GetLastCommittedOrigin();
  
    if (!frame_origin.opaque()) {
      LOG(ERROR) << "Frame origin was not opaque. " << frame_origin;
      return false;
    }
  
    const GURL url_origin = url.GetOrigin();
    const GURL precursor_origin =
        frame_origin.GetTupleOrPrecursorTupleIfOpaque().GetURL();
    if (url_origin != precursor_origin) {
      LOG(ERROR) << "url_origin '" << url_origin << "' !=  precursor_origin '"
                 << precursor_origin << "'";
      return false;
    }
    return true;
  }
  
  bool IsMainFrameOriginOpaqueAndCompatibleWithURL(Shell* shell,
                                                   const GURL& url) {
    return IsOriginOpaqueAndCompatibleWithURL(
        static_cast<WebContentsImpl*>(shell->web_contents())
            ->GetFrameTree()
            ->root(),
        url);
  }
  
  bool HasErrorPageSiteInfo(SiteInstance* site_instance) {
    auto* site_instance_impl = static_cast<SiteInstanceImpl*>(site_instance);
    return site_instance_impl->GetSiteInfo().is_error_page();
  }
  
  bool HasErrorPageProcessLock(SiteInstance* site_instance) {
    return ChildProcessSecurityPolicyImpl::GetInstance()
        ->GetProcessLock(site_instance->GetProcess()->GetID())
        .is_error_page();
  }
  
  }  // anonymous namespace
  
  class RenderFrameHostManagerTest
      : public ContentBrowserTest,
        public ::testing::WithParamInterface<std::string> {
   public:
    RenderFrameHostManagerTest() : foo_com_("foo.com") {
      replace_host_.SetHostStr(foo_com_);
      InitAndEnableRenderDocumentFeature(&feature_list_, GetParam());
    }
  
    void SetUpOnMainThread() override {
      // Support multiple sites on the test server.
      host_resolver()->AddRule("*", "127.0.0.1");
    }
  
    void DisableBackForwardCache(
        BackForwardCacheImpl::DisableForTestingReason reason) const {
      return static_cast<WebContentsImpl*>(shell()->web_contents())
          ->GetController()
          .GetBackForwardCache()
          .DisableForTesting(reason);
    }
  
    void StartServer() {
      ASSERT_TRUE(embedded_test_server()->Start());
  
      foo_host_port_ = embedded_test_server()->host_port_pair();
      foo_host_port_.set_host(foo_com_);
    }
  
    void StartEmbeddedServer() {
      SetupCrossSiteRedirector(embedded_test_server());
      ASSERT_TRUE(embedded_test_server()->Start());
    }
  
    std::unique_ptr<content::URLLoaderInterceptor> SetupRequestFailForURL(
        const GURL& url) {
      return URLLoaderInterceptor::SetupRequestFailForURL(url,
                                                          net::ERR_DNS_TIMED_OUT);
    }
  
    // Returns a URL on foo.com with the given path.
    GURL GetCrossSiteURL(const std::string& path) {
      GURL cross_site_url(embedded_test_server()->GetURL(path));
      return cross_site_url.ReplaceComponents(replace_host_);
    }
  
    void NavigateToPageWithLinks(Shell* shell) {
      EXPECT_TRUE(NavigateToURL(
          shell, embedded_test_server()->GetURL("/click-noreferrer-links.html")));
  
      // Rewrite selected links on the page to be actual cross-site (bar.com)
      // URLs. This does not use the /cross-site/ redirector, since that creates
      // links that initially look same-site.
      std::string script = "setOriginForLinks('http://bar.com:" +
                           embedded_test_server()->base_url().port() + "/');";
      EXPECT_TRUE(ExecuteScript(shell, script));
    }
  
   protected:
    void AssertCanRemoveSubframeInUnload(bool same_site);
  
    std::string foo_com_;
    GURL::Replacements replace_host_;
    net::HostPortPair foo_host_port_;
  
   private:
    base::test::ScopedFeatureList feature_list_;
  };
  
  // Web pages should not have script access to the unloaded page.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, NoScriptAccessAfterUnload) {
    StartEmbeddedServer();
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Open a same-site link in a new window.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(clickSameSiteTargetedLink());",
        &success));
    EXPECT_TRUE(success);
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Wait for the navigation in the new window to finish, if it hasn't.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
    EXPECT_EQ("/navigate_opener.html",
              new_shell->web_contents()->GetLastCommittedURL().path());
  
    // Should have the same SiteInstance.
    EXPECT_EQ(orig_site_instance, new_shell->web_contents()->GetSiteInstance());
  
    // We should have access to the opened window's location.
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(testScriptAccessToWindow());",
        &success));
    EXPECT_TRUE(success);
  
    // Now navigate the new window to a different site.
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
        new_shell, embedded_test_server()->GetURL("foo.com", "/title1.html")));
    scoped_refptr<SiteInstance> new_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    if (AreDefaultSiteInstancesEnabled()) {
      EXPECT_EQ(orig_site_instance, new_site_instance);
    } else {
      EXPECT_NE(orig_site_instance, new_site_instance);
    }
  
    // We should no longer have script access to the opened window's location.
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(testScriptAccessToWindow());",
        &success));
    EXPECT_FALSE(success);
  
    // We now navigate the window to an about:blank page.
    TestNavigationObserver navigation_observer(new_shell->web_contents());
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(), "window.domAutomationController.send(clickBlankTargetedLink());",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the navigation in the new window to finish.
    navigation_observer.Wait();
  
    GURL blank_url(url::kAboutBlankURL);
    EXPECT_EQ(blank_url, new_shell->web_contents()->GetLastCommittedURL());
    EXPECT_EQ(orig_site_instance, new_shell->web_contents()->GetSiteInstance());
  
    // We should have access to the opened window's location.
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(testScriptAccessToWindow());",
        &success));
    EXPECT_TRUE(success);
  }
  
  // Test for crbug.com/24447.  Following a cross-site link with rel=noreferrer
  // and target=_blank should create a new SiteInstance.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SwapProcessWithRelNoreferrerAndTargetBlank) {
    StartEmbeddedServer();
  
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a rel=noreferrer + target=blank link.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(clickNoRefTargetBlankLink());",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
  
    EXPECT_EQ("/title2.html", new_shell->web_contents()->GetVisibleURL().path());
  
    // Check that `window.opener` is not set.
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell, "window.domAutomationController.send(window.opener == null);",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the cross-site transition in the new tab to finish.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
  
    // Should have a new SiteInstance.
    scoped_refptr<SiteInstance> noref_blank_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, noref_blank_site_instance);
    EXPECT_FALSE(noref_blank_site_instance->IsRelatedSiteInstance(
        orig_site_instance.get()));
  }
  
  // Same as above, but for 'noopener'
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SwapProcessWithRelNoopenerAndTargetBlank) {
    StartEmbeddedServer();
  
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a rel=noreferrer + target=blank link.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(clickNoOpenerTargetBlankLink());",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
  
    EXPECT_EQ("/title2.html", new_shell->web_contents()->GetVisibleURL().path());
  
    // Check that `window.opener` is not set.
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell, "window.domAutomationController.send(window.opener == null);",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the cross-site transition in the new tab to finish.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
  
    // Check that the referrer is set correctly.
    std::string expected_referrer =
        embedded_test_server()->GetURL("/").GetOrigin().spec();
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell,
        "window.domAutomationController.send(document.referrer == '" +
            expected_referrer + "');",
        &success));
    EXPECT_TRUE(success);
  
    // Should have a new SiteInstance.
    scoped_refptr<SiteInstance> noopener_blank_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, noopener_blank_site_instance);
    EXPECT_FALSE(noopener_blank_site_instance->IsRelatedSiteInstance(
        orig_site_instance.get()));
  }
  
  // 'noopener' also works from 'window.open'
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SwapProcessWithWindowOpenAndNoopener) {
    StartEmbeddedServer();
  
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get());
  
    // Test opening a window with the 'noopener' feature.
    ShellAddedObserver new_shell_observer;
    bool hasWindowReference = true;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send("
        "    openWindowWithTargetAndFeatures('/title2.html', '', 'noopener')"
        ");",
        &hasWindowReference));
    // We should not get a reference to the opened window.
    EXPECT_FALSE(hasWindowReference);
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Wait for the cross-site transition in the new tab to finish.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
  
    EXPECT_EQ("/title2.html",
              new_shell->web_contents()->GetLastCommittedURL().path());
  
    // Check that `window.opener` is not set.
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell, "window.domAutomationController.send(window.opener == null);",
        &success));
    EXPECT_TRUE(success);
  
    // Check that the referrer is set correctly.
    std::string expected_referrer =
        embedded_test_server()->GetURL("/click-noreferrer-links.html").spec();
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell,
        "window.domAutomationController.send(document.referrer == '" +
            expected_referrer + "');",
        &success));
    EXPECT_TRUE(success);
  
    // Should have a new SiteInstance.
    scoped_refptr<SiteInstance> noopener_blank_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, noopener_blank_site_instance);
    EXPECT_FALSE(noopener_blank_site_instance->IsRelatedSiteInstance(
        orig_site_instance.get()));
  }
  
  // As of crbug.com/69267, we create a new BrowsingInstance (and SiteInstance)
  // for rel=noreferrer links in new windows, even to same site pages and named
  // targets.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SwapProcessWithSameSiteRelNoreferrer) {
    StartEmbeddedServer();
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a same-site rel=noreferrer + target=foo link.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(clickSameSiteNoRefTargetedLink());",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Opens in new window.
    EXPECT_EQ("/title2.html", new_shell->web_contents()->GetVisibleURL().path());
  
    // Check that `window.opener` is not set.
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell, "window.domAutomationController.send(window.opener == null);",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the cross-site transition in the new tab to finish.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
  
    // Should have a new SiteInstance (in a new BrowsingInstance).
    scoped_refptr<SiteInstance> noref_blank_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, noref_blank_site_instance);
    EXPECT_FALSE(noref_blank_site_instance->IsRelatedSiteInstance(
        orig_site_instance.get()));
  }
  
  // Same as above, but for 'noopener'
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SwapProcessWithSameSiteRelNoopener) {
    StartEmbeddedServer();
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a same-site rel=noopener + target=foo link.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(shell(),
                                            "window.domAutomationController.send("
                                            "clickSameSiteNoOpenerTargetedLink())"
                                            ";",
                                            &success));
    EXPECT_TRUE(success);
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Opens in new window.
    EXPECT_EQ("/title2.html", new_shell->web_contents()->GetVisibleURL().path());
  
    // Check that `window.opener` is not set.
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell, "window.domAutomationController.send(window.opener == null);",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the cross-site transition in the new tab to finish.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
  
    // Should have a new SiteInstance (in a new BrowsingInstance).
    scoped_refptr<SiteInstance> noref_blank_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, noref_blank_site_instance);
    EXPECT_FALSE(noref_blank_site_instance->IsRelatedSiteInstance(
        orig_site_instance.get()));
  }
  
  // Test for crbug.com/24447.  Following a cross-site link with just
  // target=_blank should not create a new SiteInstance, unless we
  // are running in --site-per-process mode.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         DontSwapProcessWithOnlyTargetBlank) {
    StartEmbeddedServer();
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a target=blank link.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(), "window.domAutomationController.send(clickTargetBlankLink());",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Wait for the cross-site transition in the new tab to finish.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
    EXPECT_EQ("/title2.html",
              new_shell->web_contents()->GetLastCommittedURL().path());
  
    // Should have the same SiteInstance unless we're in site-per-process mode.
    scoped_refptr<SiteInstance> blank_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    if (AreDefaultSiteInstancesEnabled())
      EXPECT_EQ(orig_site_instance, blank_site_instance);
    else
      EXPECT_NE(orig_site_instance, blank_site_instance);
  }
  
  // Test for crbug.com/24447.  Following a cross-site link with rel=noreferrer
  // and no target=_blank should not create a new SiteInstance.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         DontSwapProcessWithOnlyRelNoreferrer) {
    StartEmbeddedServer();
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a rel=noreferrer link.
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(), "window.domAutomationController.send(clickNoRefLink());",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the cross-site transition in the current tab to finish.
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  
    // Opens in same window.
    EXPECT_EQ(1u, Shell::windows().size());
    EXPECT_EQ("/title2.html",
              shell()->web_contents()->GetLastCommittedURL().path());
  
    scoped_refptr<SiteInstance> noref_site_instance(
        shell()->web_contents()->GetSiteInstance());
    if (ExpectSameSiteInstance()) {
      EXPECT_EQ(orig_site_instance, noref_site_instance);
    } else {
      EXPECT_NE(orig_site_instance, noref_site_instance);
    }
  }
  
  // Same as above, but for 'noopener'
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         DontSwapProcessWithOnlyRelNoOpener) {
    StartEmbeddedServer();
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a rel=noreferrer link.
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(), "window.domAutomationController.send(clickNoRefLink());",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the cross-site transition in the current tab to finish.
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  
    // Opens in same window.
    EXPECT_EQ(1u, Shell::windows().size());
    EXPECT_EQ("/title2.html",
              shell()->web_contents()->GetLastCommittedURL().path());
  
    scoped_refptr<SiteInstance> noref_site_instance(
        shell()->web_contents()->GetSiteInstance());
    if (ExpectSameSiteInstance()) {
      EXPECT_EQ(orig_site_instance, noref_site_instance);
    } else {
      EXPECT_NE(orig_site_instance, noref_site_instance);
    }
  }
  
  // Test for crbug.com/116192.  Targeted links should still work after the
  // named target window has swapped processes.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         AllowTargetedNavigationsAfterSwap) {
    StartEmbeddedServer();
  
    // Ensure that the first and second page are isolated from each other (even on
    // Android, where site-per-process is not the default).
    IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                             {"foo.com"});
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a target=foo link.
    ShellAddedObserver new_shell_observer;
    EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteTargetedLink()"));
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Wait for the navigation in the new tab to finish, if it hasn't.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
    EXPECT_EQ("/navigate_opener.html",
              new_shell->web_contents()->GetLastCommittedURL().path());
  
    // Should have the same SiteInstance.
    scoped_refptr<SiteInstance> blank_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_EQ(orig_site_instance, blank_site_instance);
  
    // Now navigate the new tab to a different site.
    GURL cross_site_url(
        embedded_test_server()->GetURL("foo.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
    scoped_refptr<SiteInstance> new_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, new_site_instance);
  
    // Clicking the original link in the first tab should cause us to swap back.
    {
      TestNavigationObserver navigation_observer(new_shell->web_contents());
      EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteTargetedLink()"));
      navigation_observer.Wait();
    }
  
    // Should have swapped back and shown the new window again.
    scoped_refptr<SiteInstance> revisit_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_EQ(orig_site_instance, revisit_site_instance);
  
    // If it navigates away to another process, the original window should
    // still be able to close it (using a cross-process close message).
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
    EXPECT_EQ(new_site_instance.get(),
              new_shell->web_contents()->GetSiteInstance());
    WebContentsDestroyedWatcher close_watcher(new_shell->web_contents());
    EXPECT_EQ(true, EvalJs(shell(), "testCloseWindow()"));
    close_watcher.Wait();
  }
  
  // Test that setting the opener to null in a window affects cross-process
  // navigations, including those to existing entries.  http://crbug.com/156669.
  // This test crashes under ThreadSanitizer, http://crbug.com/356758.
  #if defined(THREAD_SANITIZER)
  #define MAYBE_DisownOpener DISABLED_DisownOpener
  #else
  #define MAYBE_DisownOpener DisownOpener
  #endif
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, MAYBE_DisownOpener) {
    StartEmbeddedServer();
  
    if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
      // Isolate "foo.com" so we are guaranteed to get a non-default
      // SiteInstance for navigations to this origin.
      IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                               {"foo.com"});
    }
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a target=_blank link.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(clickSameSiteTargetBlankLink());",
        &success));
    EXPECT_TRUE(success);
    Shell* new_shell = new_shell_observer.GetShell();
    EXPECT_TRUE(new_shell->web_contents()->HasOpener());
  
    // Wait for the navigation in the new tab to finish, if it hasn't.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
    EXPECT_EQ("/title2.html",
              new_shell->web_contents()->GetLastCommittedURL().path());
  
    // Should have the same SiteInstance.
    scoped_refptr<SiteInstance> blank_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_EQ(orig_site_instance, blank_site_instance);
  
    // Now navigate the new tab to a different site.
    GURL cross_site_url(
        embedded_test_server()->GetURL("foo.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
    scoped_refptr<SiteInstance> new_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, new_site_instance);
    EXPECT_TRUE(new_shell->web_contents()->HasOpener());
  
    // Now disown the opener.
    EXPECT_TRUE(ExecuteScript(new_shell, "window.opener = null;"));
    EXPECT_FALSE(new_shell->web_contents()->HasOpener());
  
    // Go back and ensure the opener is still null.
    {
      TestNavigationObserver back_nav_load_observer(new_shell->web_contents());
      new_shell->web_contents()->GetController().GoBack();
      back_nav_load_observer.Wait();
    }
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell, "window.domAutomationController.send(window.opener == null);",
        &success));
    EXPECT_TRUE(success);
    EXPECT_FALSE(new_shell->web_contents()->HasOpener());
  
    // Now navigate forward again (creating a new process) and check opener.
    EXPECT_TRUE(NavigateToURL(new_shell, cross_site_url));
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell, "window.domAutomationController.send(window.opener == null);",
        &success));
    EXPECT_TRUE(success);
    EXPECT_FALSE(new_shell->web_contents()->HasOpener());
  }
  
  // Test that subframes can disown their openers.  http://crbug.com/225528.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, DisownSubframeOpener) {
    const GURL frame_url("data:text/html,<iframe name=\"foo\"></iframe>");
    EXPECT_TRUE(NavigateToURL(shell(), frame_url));
  
    // Give the frame an opener using window.open.
    EXPECT_TRUE(ExecuteScript(shell(), "window.open('about:blank','foo');"));
  
    // Check that the browser process updates the subframe's opener.
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    EXPECT_EQ(root, root->child_at(0)->opener());
    EXPECT_EQ(nullptr, root->child_at(0)->original_opener());
  
    // Now disown the frame's opener.  Shouldn't crash.
    EXPECT_TRUE(ExecuteScript(shell(), "window.frames[0].opener = null;"));
  
    // Check that the subframe's opener in the browser process is disowned.
    EXPECT_EQ(nullptr, root->child_at(0)->opener());
    EXPECT_EQ(nullptr, root->child_at(0)->original_opener());
  }
  
  // Check that window.name is preserved for top frames when they navigate
  // cross-process.  See https://crbug.com/504164.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         PreserveTopFrameWindowNameOnCrossProcessNavigations) {
    StartEmbeddedServer();
    if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
      // Isolate "foo.com" so we are guaranteed it is placed in a different
      // process.
      IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                               {"foo.com"});
    }
  
    GURL main_url(embedded_test_server()->GetURL("/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), main_url));
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Open a popup using window.open with a 'foo' window.name.
    Shell* new_shell = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo");
    EXPECT_TRUE(new_shell);
  
    // The window.name for the new popup should be "foo".
    std::string name;
    EXPECT_TRUE(ExecuteScriptAndExtractString(
        new_shell, "window.domAutomationController.send(window.name);", &name));
    EXPECT_EQ("foo", name);
  
    // Now navigate the new tab to a different site.
    GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title2.html"));
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, foo_url));
    scoped_refptr<SiteInstance> new_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance->GetProcess(), new_site_instance->GetProcess());
  
    // window.name should still be "foo".
    name = "";
    EXPECT_TRUE(ExecuteScriptAndExtractString(
        new_shell, "window.domAutomationController.send(window.name);", &name));
    EXPECT_EQ("foo", name);
  
    // Open another popup from the 'foo' popup and navigate it cross-site.
    Shell* new_shell2 = OpenPopup(new_shell, GURL(url::kAboutBlankURL), "bar");
    EXPECT_TRUE(new_shell2);
    GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title3.html"));
    EXPECT_TRUE(NavigateToURLFromRenderer(new_shell2, bar_url));
  
    // Check that the new popup's window.opener has name "foo", which verifies
    // that new swapped-out RenderViews also propagate window.name.  This has to
    // be done via window.open, since window.name isn't readable cross-origin.
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell2,
        "window.domAutomationController.send("
        "    window.opener === window.open('','foo'));",
        &success));
    EXPECT_TRUE(success);
  }
  
  // Test for crbug.com/99202.  PostMessage calls should still work after
  // navigating the source and target windows to different sites.
  // Specifically:
  // 1) Create 3 windows (opener, "foo", and _blank) and send "foo" cross-process.
  // 2) Fail to post a message from "foo" to opener with the wrong target origin.
  // 3) Post a message from "foo" to opener, which replies back to "foo".
  // 4) Post a message from _blank to "foo".
  // 5) Post a message from "foo" to a subframe of opener, which replies back.
  // 6) Post a message from _blank to a subframe of "foo".
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SupportCrossProcessPostMessage) {
    StartEmbeddedServer();
    if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
      // Isolate "foo.com" so we are guaranteed it is placed in a different
      // process.
      IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                               {"foo.com"});
    }
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance and RVHM for later comparison.
    WebContents* opener_contents = shell()->web_contents();
    scoped_refptr<SiteInstance> orig_site_instance(
        opener_contents->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
    RenderFrameHostManager* opener_manager =
        static_cast<WebContentsImpl*>(opener_contents)
            ->GetRenderManagerForTesting();
  
    // 1) Open two more windows, one named.  These initially have openers but no
    // reference to each other.  We will later post a message between them.
  
    // First, a named target=foo window.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        opener_contents,
        "window.domAutomationController.send(clickSameSiteTargetedLink());",
        &success));
    EXPECT_TRUE(success);
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Wait for the navigation in the new window to finish, if it hasn't, then
    // send it to post_message.html on a different site.
    WebContents* foo_contents = new_shell->web_contents();
    EXPECT_TRUE(WaitForLoadStop(foo_contents));
    EXPECT_EQ("/navigate_opener.html",
              foo_contents->GetLastCommittedURL().path());
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
        new_shell,
        embedded_test_server()->GetURL("foo.com", "/post_message.html")));
    scoped_refptr<SiteInstance> foo_site_instance(
        foo_contents->GetSiteInstance());
    EXPECT_NE(orig_site_instance, foo_site_instance);
  
    // Second, a target=_blank window.
    ShellAddedObserver new_shell_observer2;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(clickSameSiteTargetBlankLink());",
        &success));
    EXPECT_TRUE(success);
  
    // Wait for the navigation in the new window to finish, if it hasn't, then
    // send it to post_message.html on the original site.
    Shell* new_shell2 = new_shell_observer2.GetShell();
    WebContents* new_contents = new_shell2->web_contents();
    EXPECT_TRUE(WaitForLoadStop(new_contents));
    EXPECT_EQ("/title2.html", new_contents->GetLastCommittedURL().path());
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
        new_shell2, embedded_test_server()->GetURL("/post_message.html")));
    EXPECT_EQ(orig_site_instance.get(), new_contents->GetSiteInstance());
    RenderFrameHostManager* new_manager =
        static_cast<WebContentsImpl*>(new_contents)->GetRenderManagerForTesting();
  
    // We now have three windows.  The opener should have a RenderFrameProxyHost
    // for the new SiteInstance, but the _blank window should not.
    EXPECT_EQ(3u, Shell::windows().size());
    EXPECT_TRUE(opener_manager->GetRenderFrameProxyHost(foo_site_instance.get()));
    EXPECT_FALSE(new_manager->GetRenderFrameProxyHost(foo_site_instance.get()));
  
    // 2) Fail to post a message from the foo window to the opener if the target
    // origin is wrong.  We won't see an error, but we can check for the right
    // number of received messages below.
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        foo_contents,
        "window.domAutomationController.send(postToOpener('msg',"
        "    'http://google.com'));",
        &success));
    EXPECT_TRUE(success);
    ASSERT_FALSE(
        opener_manager->GetRenderFrameProxyHost(orig_site_instance.get()));
  
    // 3) Post a message from the foo window to the opener.  The opener will
    // reply, causing the foo window to update its own title.
    std::u16string expected_title = u"msg";
    TitleWatcher title_watcher(foo_contents, expected_title);
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        foo_contents,
        "window.domAutomationController.send(postToOpener('msg','*'));",
        &success));
    EXPECT_TRUE(success);
    ASSERT_FALSE(
        opener_manager->GetRenderFrameProxyHost(orig_site_instance.get()));
    ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  
    // We should have received only 1 message in the opener and "foo" tabs,
    // and updated the title.
    int opener_received_messages = 0;
    EXPECT_TRUE(ExecuteScriptAndExtractInt(
        opener_contents,
        "window.domAutomationController.send(window.receivedMessages);",
        &opener_received_messages));
    int foo_received_messages = 0;
    EXPECT_TRUE(ExecuteScriptAndExtractInt(
        foo_contents,
        "window.domAutomationController.send(window.receivedMessages);",
        &foo_received_messages));
    EXPECT_EQ(1, foo_received_messages);
    EXPECT_EQ(1, opener_received_messages);
    EXPECT_EQ(u"msg", foo_contents->GetTitle());
  
    // 4) Now post a message from the _blank window to the foo window.  The
    // foo window will update its title and will not reply.
    expected_title = u"msg2";
    TitleWatcher title_watcher2(foo_contents, expected_title);
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_contents, "window.domAutomationController.send(postToFoo('msg2'));",
        &success));
    EXPECT_TRUE(success);
    ASSERT_EQ(expected_title, title_watcher2.WaitAndGetTitle());
  
    // This postMessage should have created a RenderFrameProxyHost for the new
    // SiteInstance in the target=_blank window.
    EXPECT_TRUE(new_manager->GetRenderFrameProxyHost(foo_site_instance.get()));
  
    // TODO(nasko): Test subframe targeting of postMessage once
    // http://crbug.com/153701 is fixed.
  }
  
  // Test for crbug.com/278336. MessagePorts should work cross-process. Messages
  // which contain Transferables that need to be forwarded between processes via
  // RenderFrameProxy::willCheckAndDispatchMessageEvent should work.
  // Specifically:
  // 1) Create 2 windows (opener and "foo") and send "foo" cross-process.
  // 2) Post a message containing a message port from opener to "foo".
  // 3) Post a message from "foo" back to opener via the passed message port.
  // The test will be enabled when the feature implementation lands.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SupportCrossProcessPostMessageWithMessagePort) {
    StartEmbeddedServer();
    if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
      // Isolate "foo.com" so we are guaranteed it is placed in a different
      // process.
      IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                               {"foo.com"});
    }
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance and RVHM for later comparison.
    WebContents* opener_contents = shell()->web_contents();
    scoped_refptr<SiteInstance> orig_site_instance(
        opener_contents->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
    RenderFrameHostManager* opener_manager =
        static_cast<WebContentsImpl*>(opener_contents)
            ->GetRenderManagerForTesting();
  
    // 1) Open a named target=foo window. We will later post a message between the
    // opener and the new window.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        opener_contents,
        "window.domAutomationController.send(clickSameSiteTargetedLink());",
        &success));
    EXPECT_TRUE(success);
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Wait for the navigation in the new window to finish, if it hasn't, then
    // send it to post_message.html on a different site.
    WebContents* foo_contents = new_shell->web_contents();
    EXPECT_TRUE(WaitForLoadStop(foo_contents));
    EXPECT_EQ("/navigate_opener.html",
              foo_contents->GetLastCommittedURL().path());
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
        new_shell,
        embedded_test_server()->GetURL("foo.com", "/post_message.html")));
    scoped_refptr<SiteInstance> foo_site_instance(
        foo_contents->GetSiteInstance());
    EXPECT_NE(orig_site_instance, foo_site_instance);
  
    // We now have two windows. The opener should have a RenderFrameProxyHost
    // for the new SiteInstance.
    EXPECT_EQ(2u, Shell::windows().size());
    EXPECT_TRUE(opener_manager->GetRenderFrameProxyHost(foo_site_instance.get()));
  
    // 2) Post a message containing a MessagePort from opener to the the foo
    // window. The foo window will reply via the passed port, causing the opener
    // to update its own title.
    std::u16string expected_title = u"msg-back-via-port";
    TitleWatcher title_observer(opener_contents, expected_title);
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        opener_contents,
        "window.domAutomationController.send(postWithPortToFoo());", &success));
    EXPECT_TRUE(success);
    ASSERT_FALSE(
        opener_manager->GetRenderFrameProxyHost(orig_site_instance.get()));
    ASSERT_EQ(expected_title, title_observer.WaitAndGetTitle());
  
    // Check message counts.
    int opener_received_messages_via_port = 0;
    EXPECT_TRUE(ExecuteScriptAndExtractInt(
        opener_contents,
        "window.domAutomationController.send(window.receivedMessagesViaPort);",
        &opener_received_messages_via_port));
    int foo_received_messages = 0;
    EXPECT_TRUE(ExecuteScriptAndExtractInt(
        foo_contents,
        "window.domAutomationController.send(window.receivedMessages);",
        &foo_received_messages));
    int foo_received_messages_with_port = 0;
    EXPECT_TRUE(ExecuteScriptAndExtractInt(
        foo_contents,
        "window.domAutomationController.send(window.receivedMessagesWithPort);",
        &foo_received_messages_with_port));
    EXPECT_EQ(1, foo_received_messages);
    EXPECT_EQ(1, foo_received_messages_with_port);
    EXPECT_EQ(1, opener_received_messages_via_port);
    EXPECT_EQ(u"msg-with-port", foo_contents->GetTitle());
    EXPECT_EQ(u"msg-back-via-port", opener_contents->GetTitle());
  }
  
  // Test for crbug.com/116192.  Navigations to a window's opener should
  // still work after a process swap.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         AllowTargetedNavigationsInOpenerAfterSwap) {
    StartEmbeddedServer();
  
    if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
      // Isolate "foo.com" so we are guaranteed to get a non-default
      // SiteInstance for navigations to this origin.
      IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                               {"foo.com"});
    }
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original tab and SiteInstance for later comparison.
    WebContents* orig_contents = shell()->web_contents();
    scoped_refptr<SiteInstance> orig_site_instance(
        orig_contents->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a target=foo link.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        orig_contents,
        "window.domAutomationController.send(clickSameSiteTargetedLink());",
        &success));
    EXPECT_TRUE(success);
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Wait for the navigation in the new window to finish, if it hasn't.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
    EXPECT_EQ("/navigate_opener.html",
              new_shell->web_contents()->GetLastCommittedURL().path());
  
    // Should have the same SiteInstance.
    scoped_refptr<SiteInstance> blank_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_EQ(orig_site_instance, blank_site_instance);
  
    // Now navigate the original (opener) tab to a different site.
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(
        shell(), embedded_test_server()->GetURL("foo.com", "/title1.html")));
    scoped_refptr<SiteInstance> new_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, new_site_instance);
  
    // The opened tab should be able to navigate the opener back to its process.
    TestNavigationObserver navigation_observer(orig_contents);
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        new_shell, "window.domAutomationController.send(navigateOpener());",
        &success));
    EXPECT_TRUE(success);
    navigation_observer.Wait();
  
    // Should have swapped back into this process.
    scoped_refptr<SiteInstance> revisit_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_EQ(orig_site_instance, revisit_site_instance);
  }
  
  // Test that subframes do not crash when sending a postMessage to the top frame
  // from an unload handler while the top frame is being replaced as part of
  // navigating cross-process.  https://crbug.com/475651.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         PostMessageFromSubframeUnloadHandler) {
    StartEmbeddedServer();
  
    GURL frame_url(embedded_test_server()->GetURL("/post_message.html"));
    GURL main_url("data:text/html,<iframe name='foo' src='" + frame_url.spec() +
                  "'></iframe>");
    EXPECT_TRUE(NavigateToURL(shell(), main_url));
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_NE(nullptr, orig_site_instance.get());
  
    // It is safe to obtain the root frame tree node here, as it doesn't change.
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    ASSERT_EQ(1U, root->child_count());
    EXPECT_EQ(frame_url, root->child_at(0)->current_url());
  
    // Register an unload handler that sends a postMessage to the top frame.
    EXPECT_TRUE(ExecuteScript(root->child_at(0), "registerUnload();"));
  
    // Navigate the top frame cross-site.  This will cause the top frame to be
    // unloaded, and the original renderer process should then terminate since
    // it's not rendering any other frames.
    RenderProcessHostWatcher exit_observer(
        root->current_frame_host()->GetProcess(),
        RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("foo.com", "/title1.html")));
    scoped_refptr<SiteInstance> new_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, new_site_instance);
  
    // Ensure that the original renderer process exited cleanly without crashing.
    exit_observer.Wait();
    EXPECT_TRUE(exit_observer.did_exit_normally());
  }
  
  // Test that opening a new window in the same SiteInstance and then navigating
  // both windows to a different SiteInstance allows the first process to exit.
  // See http://crbug.com/126333.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ProcessExitWithSwappedOutViews) {
    StartEmbeddedServer();
    if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
      // Isolate "foo.com" so we are guaranteed to get a non-default
      // SiteInstance for navigations to this origin.
      IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                               {"foo.com"});
    }
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Test clicking a target=foo link.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(clickSameSiteTargetedLink());",
        &success));
    EXPECT_TRUE(success);
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Wait for the navigation in the new window to finish, if it hasn't.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
    EXPECT_EQ("/navigate_opener.html",
              new_shell->web_contents()->GetLastCommittedURL().path());
  
    // Should have the same SiteInstance.
    scoped_refptr<SiteInstance> opened_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_EQ(orig_site_instance, opened_site_instance);
  
    // Now navigate the opened window to a different site.
    GURL cross_site_url(
        embedded_test_server()->GetURL("foo.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
    scoped_refptr<SiteInstance> new_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, new_site_instance);
  
    // The original process should still be alive, since it is still used in the
    // first window.
    RenderProcessHost* orig_process = orig_site_instance->GetProcess();
    EXPECT_TRUE(orig_process->IsInitializedAndNotDead());
  
    // Navigate the first window to a different site as well.  The original
    // process should exit, since all of its active frames are gone.
    RenderProcessHostWatcher exit_observer(
        orig_process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(shell(), cross_site_url));
    exit_observer.Wait();
    scoped_refptr<SiteInstance> new_site_instance2(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_EQ(new_site_instance, new_site_instance2);
  }
  
  // Test for crbug.com/76666.  A cross-site navigation that fails with a 204
  // error should not make us ignore future renderer-initiated navigations.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, ClickLinkAfter204Error) {
    StartServer();
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance.get() != nullptr);
  
    // Load a cross-site page that fails with a 204 error.
    EXPECT_TRUE(
        NavigateToURLAndExpectNoCommit(shell(), GetCrossSiteURL("/nocontent")));
  
    // We should still be looking at the normal page.  Because we started from a
    // blank new tab, the typed URL will still be visible until the user clears it
    // manually.  The last committed URL will be the previous page.
    scoped_refptr<SiteInstance> post_nav_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_EQ(orig_site_instance, post_nav_site_instance);
    EXPECT_EQ("/nocontent", shell()->web_contents()->GetVisibleURL().path());
    EXPECT_FALSE(
        shell()->web_contents()->GetController().GetLastCommittedEntry());
  
    // Renderer-initiated navigations should work.
    std::u16string expected_title = u"Title Of Awesomeness";
    TitleWatcher title_watcher(shell()->web_contents(), expected_title);
    GURL url = embedded_test_server()->GetURL("/title2.html");
    EXPECT_TRUE(ExecuteScript(
        shell(), base::StringPrintf("location.href = '%s'", url.spec().c_str())));
    ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  
    // Opens in same tab.
    EXPECT_EQ(1u, Shell::windows().size());
    EXPECT_EQ("/title2.html",
              shell()->web_contents()->GetLastCommittedURL().path());
  
    // Should have the same SiteInstance.
    scoped_refptr<SiteInstance> new_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_EQ(orig_site_instance, new_site_instance);
  }
  
  // A collection of tests to prevent URL spoofs when showing pending URLs above
  // initial empty documents, ensuring that the URL reverts to about:blank if the
  // document is accessed. See https://crbug.com/9682.
  class RenderFrameHostManagerSpoofingTest : public RenderFrameHostManagerTest {
   public:
    void SetUpInProcessBrowserTestFixture() override {
      base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  
      base::CommandLine new_command_line(command_line->GetProgram());
      base::CommandLine::SwitchMap switches = command_line->GetSwitches();
  
      // Injecting the DOM automation controller causes false positives, since it
      // triggers the DidAccessInitialDocument() callback by mutating the global
      // object.
      switches.erase(switches::kDomAutomationController);
  
      for (const auto& it : switches)
        new_command_line.AppendSwitchNative(it.first, it.second);
  
      *command_line = new_command_line;
    }
  
   protected:
    // Custom ExecuteScript() helper that doesn't depend on DOM automation
    // controller. This is used to guarantee the script has completed execution,
    // but the spoofing tests synchronize execution using window title changes.
    void ExecuteScript(const ToRenderFrameHost& adapter, const char* script) {
      adapter.render_frame_host()->ExecuteJavaScriptForTests(
          base::UTF8ToUTF16(script), base::NullCallback());
    }
  };
  
  // Helper to wait until a WebContent's NavigationController has a visible entry.
  class VisibleEntryWaiter : public WebContentsObserver {
   public:
    explicit VisibleEntryWaiter(WebContents* web_contents)
        : WebContentsObserver(web_contents) {}
  
    void Wait() {
      if (web_contents()->GetController().GetVisibleEntry())
        return;
      run_loop_.Run();
    }
  
    // WebContentsObserver overrides:
    void DidStartNavigation(NavigationHandle* navigation_handle) override {
      run_loop_.Quit();
    }
  
   private:
    base::RunLoop run_loop_;
  };
  
  // Sanity test that a newly opened window shows the pending URL if the initial
  // empty document is not modified. This is intentionally structured as similarly
  // as possible to the subsequent ShowLoadingURLUntil*Spoof tests: it performs
  // the same operations as the subsequent tests except DOM modification. This
  // should help catch instances where the subsequent tests incorrectly pass due
  // to a side effect of the test infrastructure.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerSpoofingTest,
                         ShowLoadingURLIfNotModified) {
    ASSERT_TRUE(embedded_test_server()->Start());
  
    // Load a page that can open a URL that won't commit in a new window.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/click-nocontent-link.html")));
    WebContents* orig_contents = shell()->web_contents();
  
    // Click a /nocontent link that opens in a new window but never commits.
    ShellAddedObserver new_shell_observer;
    ExecuteScript(orig_contents, "clickNoContentTargetedLink();");
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
    WebContents* contents = new_shell->web_contents();
  
    // Make sure the new window has started the provisional load, so the
    // associated navigation controller will have a visible entry.
    {
      VisibleEntryWaiter waiter(contents);
      waiter.Wait();
    }
  
    // Ensure the destination URL is visible, because it is considered the
    // initial navigation.
    EXPECT_TRUE(contents->GetController().IsInitialNavigation());
    EXPECT_EQ("/nocontent",
              contents->GetController().GetVisibleEntry()->GetURL().path());
  
    // Now get another reference to the window object, but don't otherwise access
    // it. This is to ensure that DidAccessInitialDocument() notifications are not
    // incorrectly generated when nothing is modified.
    std::u16string expected_title = u"Modified Title";
    TitleWatcher title_watcher(orig_contents, expected_title);
    ExecuteScript(orig_contents, "getNewWindowReference();");
    ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  
    // The destination URL should still be visible, since nothing was modified.
    EXPECT_TRUE(contents->GetController().IsInitialNavigation());
    EXPECT_EQ("/nocontent",
              contents->GetController().GetVisibleEntry()->GetURL().path());
  }
  
  // Test for crbug.com/9682.  We should show the URL for a pending renderer-
  // initiated navigation in a new tab, until the content of the initial
  // about:blank page is modified by another window.  At that point, we should
  // revert to showing about:blank to prevent a URL spoof.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerSpoofingTest,
                         ShowLoadingURLUntilSpoof) {
    ASSERT_TRUE(embedded_test_server()->Start());
  
    // Load a page that can open a URL that won't commit in a new window.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/click-nocontent-link.html")));
    WebContents* orig_contents = shell()->web_contents();
  
    // Change the link to be cross-site.
    GURL target_url = embedded_test_server()->GetURL("foo.com", "/nocontent");
    ExecuteScript(
        orig_contents,
        base::StringPrintf(
            "document.getElementById('nocontent_targeted_link').href = '%s';",
            target_url.spec().c_str())
            .c_str());
  
    // Click a /nocontent link that opens in a new window but never commits.
    ShellAddedObserver new_shell_observer;
    ExecuteScript(orig_contents, "clickNoContentTargetedLink();");
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
    WebContents* contents = new_shell->web_contents();
  
    // Make sure the new window has started the navigation, so the associated
    // navigation controller will have a visible entry.
    {
      VisibleEntryWaiter waiter(contents);
      waiter.Wait();
    }
  
    // Ensure the destination URL is visible, because it is considered the
    // initial navigation.
    EXPECT_TRUE(contents->GetController().IsInitialNavigation());
    EXPECT_EQ(target_url, contents->GetController().GetVisibleEntry()->GetURL());
  
    // Now modify the contents of the new window from the opener.  This will also
    // modify the title of the document to give us something to listen for.
    std::u16string expected_title = u"Modified Title";
    TitleWatcher title_watcher(orig_contents, expected_title);
    ExecuteScript(orig_contents, "modifyNewWindow();");
    ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  
    // At this point, we should no longer be showing the destination URL.
    // The visible entry should be null, resulting in about:blank in the address
    // bar.
    EXPECT_FALSE(contents->GetController().GetVisibleEntry());
  }
  
  // Same as ShowLoadingURLUntilSpoof, but reloads the new popup before modifying
  // it, to test https://crbug.com/847718.  The reload should not cause the
  // visible entry to stick around after the modification, even though it is
  // triggered in the browser process.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerSpoofingTest,
                         ShowLoadingURLUntilSpoofAfterReload) {
    ASSERT_TRUE(embedded_test_server()->Start());
  
    // Load a page that can open a URL that won't commit in a new window.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/click-nocontent-link.html")));
    WebContents* orig_contents = shell()->web_contents();
  
    // Change the link to be cross-site.
    GURL target_url = embedded_test_server()->GetURL("foo.com", "/nocontent");
    ExecuteScript(
        orig_contents,
        base::StringPrintf(
            "document.getElementById('nocontent_targeted_link').href = '%s';",
            target_url.spec().c_str())
            .c_str());
  
    // Click a /nocontent link that opens in a new window but never commits.
    ShellAddedObserver new_shell_observer;
    ExecuteScript(orig_contents, "clickNoContentTargetedLink();");
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
    WebContents* contents = new_shell->web_contents();
  
    // Make sure the new window has started the navigation, so the associated
    // navigation controller will have a visible entry.
    {
      VisibleEntryWaiter waiter(contents);
      waiter.Wait();
    }
  
    // Ensure the destination URL is visible, because it is considered the
    // initial navigation.
    EXPECT_TRUE(contents->GetController().IsInitialNavigation());
    EXPECT_EQ(target_url, contents->GetController().GetVisibleEntry()->GetURL());
  
    // Reload the popup before modifying it.  See https://crbug.com/847718.
    contents->GetController().Reload(ReloadType::NORMAL, false);
  
    // Now modify the contents of the new window from the opener.  This will also
    // modify the title of the document to give us something to listen for.
    std::u16string expected_title = u"Modified Title";
    TitleWatcher title_watcher(orig_contents, expected_title);
    ExecuteScript(orig_contents, "modifyNewWindow();");
    ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  
    // At this point, we should no longer be showing the destination URL.
    // The visible entry should be null, resulting in about:blank in the address
    // bar.
    EXPECT_FALSE(contents->GetController().GetVisibleEntry());
  }
  
  // Similar but using document.open(): once a Document is opened, subsequent
  // document.write() calls can insert arbitrary content into the target Document.
  // Since this could result in URL spoofing, the pending URL should no longer be
  // shown in the omnibox.
  //
  // Note: document.write() implicitly invokes document.open() if the Document has
  // not already been opened, so there's no need to test document.write()
  // separately.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerSpoofingTest,
                         ShowLoadingURLUntilDocumentOpenSpoof) {
    ASSERT_TRUE(embedded_test_server()->Start());
  
    // Load a page that can open a URL that won't commit in a new window.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/click-nocontent-link.html")));
    WebContents* orig_contents = shell()->web_contents();
  
    // Click a /nocontent link that opens in a new window but never commits.
    ShellAddedObserver new_shell_observer;
    ExecuteScript(orig_contents, "clickNoContentTargetedLink();");
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
    WebContents* contents = new_shell->web_contents();
  
    // Make sure the new window has started the provisional load, so the
    // associated navigation controller will have a visible entry.
    {
      VisibleEntryWaiter waiter(contents);
      waiter.Wait();
    }
  
    // Ensure the destination URL is visible, because it is considered the
    // initial navigation.
    EXPECT_TRUE(contents->GetController().IsInitialNavigation());
    EXPECT_EQ("/nocontent",
              contents->GetController().GetVisibleEntry()->GetURL().path());
  
    // Now modify the contents of the new window from the opener.  This will also
    // modify the title of the document to give us something to listen for.
    std::u16string expected_title = u"Modified Title";
    TitleWatcher title_watcher(orig_contents, expected_title);
    ExecuteScript(orig_contents, "modifyNewWindowWithDocumentOpen();");
    ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  
    // At this point, we should no longer be showing the destination URL.
    // The visible entry should be null, resulting in about:blank in the address
    // bar.
    EXPECT_FALSE(contents->GetController().GetVisibleEntry());
  }
  
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         WasDiscardedWhenNavigationInterruptsReload) {
    EXPECT_TRUE(embedded_test_server()->Start());
    GURL discarded_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), discarded_url));
    // Discard the page.
    shell()->web_contents()->SetWasDiscarded(true);
    // Reload the discarded page, but pretend that it's slow to commit.
    TestNavigationManager first_reload(shell()->web_contents(), discarded_url);
    shell()->web_contents()->GetController().Reload(
        ReloadType::ORIGINAL_REQUEST_URL, false);
    EXPECT_TRUE(first_reload.WaitForRequestStart());
    // Before the response is received, simulate user navigating to another URL.
    GURL second_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
    TestNavigationManager second_navigation(shell()->web_contents(), second_url);
    shell()->LoadURL(second_url);
    second_navigation.WaitForNavigationFinished();
    const char kDiscardedStateJS[] =
        "window.domAutomationController.send(window.document.wasDiscarded);";
    bool discarded_result;
    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(shell(), kDiscardedStateJS,
                                                     &discarded_result));
    EXPECT_FALSE(discarded_result);
  }
  
  // Ensures that a pending navigation's URL  is no longer visible after the
  // speculative RFH is discarded due to a concurrent renderer-initiated
  // navigation.  See https://crbug.com/760342.
  // TODO(https://crbug.com/945194): Disabled due to flaky timeouts.
  IN_PROC_BROWSER_TEST_P(
      RenderFrameHostManagerTest,
      DISABLED_ResetVisibleURLOnCrossProcessNavigationInterrupted) {
    const std::string kVictimPath = "/victim.html";
    const std::string kAttackPath = "/attack.html";
    net::test_server::ControllableHttpResponse victim_response(
        embedded_test_server(), kVictimPath);
    net::test_server::ControllableHttpResponse attack_response(
        embedded_test_server(), kAttackPath);
    EXPECT_TRUE(embedded_test_server()->Start());
  
    const GURL kVictimURL = embedded_test_server()->GetURL("a.com", kVictimPath);
    const GURL kAttackURL = embedded_test_server()->GetURL("b.com", kAttackPath);
  
    // First navigate to the attacker page. This page will be cross-site compared
    // to the next navigations we will attempt.
    const GURL kAttackInitialURL =
        embedded_test_server()->GetURL("b.com", "/title1.html");
    EXPECT_TRUE(NavigateToURL(shell(), kAttackInitialURL));
    EXPECT_EQ(kAttackInitialURL, shell()->web_contents()->GetVisibleURL());
  
    // Now, start a browser-initiated cross-site navigation to a new page that
    // will hang at ready to commit stage.
    TestNavigationManager victim_navigation(shell()->web_contents(), kVictimURL);
    shell()->LoadURL(kVictimURL);
    EXPECT_TRUE(victim_navigation.WaitForRequestStart());
    victim_navigation.ResumeNavigation();
  
    victim_response.WaitForRequest();
    victim_response.Send(
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=utf-8\r\n"
        "\r\n");
    EXPECT_TRUE(victim_navigation.WaitForResponse());
    victim_navigation.ResumeNavigation();
  
    // The navigation is ready to commit: it has been handed to the speculative
    // RenderFrameHost for commit.
    RenderFrameHostImpl* speculative_rfh =
        static_cast<WebContentsImpl*>(shell()->web_contents())
            ->GetFrameTree()
            ->root()
            ->render_manager()
            ->speculative_frame_host();
    CHECK(speculative_rfh);
    EXPECT_TRUE(speculative_rfh->is_loading());
  
    // Since we have a browser-initiated pending navigation, the navigation URL is
    // showing in the address bar.
    EXPECT_EQ(kVictimURL, shell()->web_contents()->GetVisibleURL());
  
    // The attacker page requests a navigation to a new document while the
    // browser-initiated navigation hasn't committed yet.
    TestNavigationManager attack_navigation(shell()->web_contents(), kAttackURL);
    EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
        shell()->web_contents(),
        "location.href = \"" + kAttackURL.spec() + "\";"));
    EXPECT_TRUE(attack_navigation.WaitForRequestStart());
  
    // This deletes the speculative RenderFrameHost that was supposed to commit
    // the browser-initiated navigation.
    speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
                          ->GetFrameTree()
                          ->root()
                          ->render_manager()
                          ->speculative_frame_host();
    EXPECT_FALSE(speculative_rfh);
  
    // The URL of the browser-initiated navigation should no longer be shown in
    // the address bar since the RenderFrameHost that was supposed to commit it
    // has been discarded. Instead, we should be showing the URL of the current
    // page as we do not show the URL of pending navigations when they are
    // renderer-initiated.
    EXPECT_NE(kVictimURL, shell()->web_contents()->GetVisibleURL());
    EXPECT_EQ(kAttackInitialURL, shell()->web_contents()->GetVisibleURL());
  
    // The attacker navigation results in a 204.
    attack_navigation.ResumeNavigation();
    attack_response.WaitForRequest();
    attack_response.Send(
        "HTTP/1.1 204 OK\r\n"
        "Connection: close\r\n"
        "\r\n");
    attack_navigation.WaitForNavigationFinished();
    speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
                          ->GetFrameTree()
                          ->root()
                          ->render_manager()
                          ->speculative_frame_host();
    EXPECT_FALSE(speculative_rfh);
  
    // We are still showing the URL of the current page.
    EXPECT_EQ(kAttackInitialURL, shell()->web_contents()->GetVisibleURL());
  }
  
  // Ensures that deleting a speculative RenderFrameHost trying to commit a
  // navigation to the pending NavigationEntry will not crash if it happens
  // because a new navigation to the same pending NavigationEntry started. This is
  // a regression test for crbug.com/796135.
  IN_PROC_BROWSER_TEST_P(
      RenderFrameHostManagerTest,
      DeleteSpeculativeRFHPendingCommitOfPendingEntryOnInterrupted1) {
    const std::string kOriginalPath = "/original.html";
    const std::string kFirstRedirectPath = "/redirect1.html";
    const std::string kSecondRedirectPath = "/reidrect2.html";
    net::test_server::ControllableHttpResponse original_response1(
        embedded_test_server(), kOriginalPath);
    net::test_server::ControllableHttpResponse original_response2(
        embedded_test_server(), kOriginalPath);
    net::test_server::ControllableHttpResponse original_response3(
        embedded_test_server(), kOriginalPath);
    net::test_server::ControllableHttpResponse first_redirect_response(
        embedded_test_server(), kFirstRedirectPath);
    net::test_server::ControllableHttpResponse second_redirect_response(
        embedded_test_server(), kSecondRedirectPath);
    EXPECT_TRUE(embedded_test_server()->Start());
  
    const GURL kOriginalURL =
        embedded_test_server()->GetURL("a.com", kOriginalPath);
    const GURL kFirstRedirectURL =
        embedded_test_server()->GetURL("b.com", kFirstRedirectPath);
    const GURL kSecondRedirectURL =
        embedded_test_server()->GetURL("c.com", kSecondRedirectPath);
  
    // First navigate to the initial URL. This page will have a cross-site
    // redirect.
    shell()->LoadURL(kOriginalURL);
    original_response1.WaitForRequest();
    original_response1.Send(
        "HTTP/1.1 302 FOUND\r\n"
        "Location: " +
        kFirstRedirectURL.spec() +
        "\r\n"
        "\r\n");
    original_response1.Done();
    first_redirect_response.WaitForRequest();
    first_redirect_response.Send(
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=utf-8\r\n"
        "\r\n");
    first_redirect_response.Send(
        "<html>"
        "<body></body>"
        "</html>");
    first_redirect_response.Done();
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(kFirstRedirectURL, shell()->web_contents()->GetLastCommittedURL());
  
    // Now reload the original request, but redirect to yet another site.
    TestNavigationManager first_reload(shell()->web_contents(), kOriginalURL);
    shell()->web_contents()->GetController().Reload(
        ReloadType::ORIGINAL_REQUEST_URL, false);
    EXPECT_TRUE(first_reload.WaitForRequestStart());
    first_reload.ResumeNavigation();
  
    original_response2.WaitForRequest();
    original_response2.Send(
        "HTTP/1.1 302 FOUND\r\n"
        "Location: " +
        kSecondRedirectURL.spec() +
        "\r\n"
        "\r\n");
    original_response2.Done();
    second_redirect_response.WaitForRequest();
    second_redirect_response.Send(
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=utf-8\r\n"
        "\r\n");
    EXPECT_TRUE(first_reload.WaitForResponse());
    first_reload.ResumeNavigation();
  
    // The navigation is ready to commit: it has been handed to the speculative
    // RenderFrameHost for commit if Site Isolation is enabled, otherwise it
    // commits in the same RenderFrameHost.
    RenderFrameHostImpl* speculative_rfh =
        static_cast<WebContentsImpl*>(shell()->web_contents())
            ->GetFrameTree()
            ->root()
            ->render_manager()
            ->speculative_frame_host();
    if (AreAllSitesIsolatedForTesting()) {
      CHECK(speculative_rfh);
    } else {
      CHECK(!speculative_rfh);
    }
  
    // The user requests a new reload while the previous reload hasn't committed
    // yet. The navigation start deletes the speculative RenderFrameHost that was
    // supposed to commit the browser-initiated navigation, unless Site Isolation
    // is enabled. This should not crash.
    TestNavigationManager second_reload(shell()->web_contents(), kOriginalURL);
    shell()->web_contents()->GetController().Reload(
        ReloadType::ORIGINAL_REQUEST_URL, false);
    EXPECT_TRUE(second_reload.WaitForRequestStart());
    speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
                          ->GetFrameTree()
                          ->root()
                          ->render_manager()
                          ->speculative_frame_host();
    if (AreAllSitesIsolatedForTesting()) {
      EXPECT_TRUE(speculative_rfh);
    } else {
      EXPECT_FALSE(speculative_rfh);
    }
  
    // The second reload results in a 204.
    second_reload.ResumeNavigation();
    original_response3.WaitForRequest();
    original_response3.Send(
        "HTTP/1.1 204 OK\r\n"
        "Connection: close\r\n"
        "\r\n");
    second_reload.WaitForNavigationFinished();
    speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
                          ->GetFrameTree()
                          ->root()
                          ->render_manager()
                          ->speculative_frame_host();
    EXPECT_FALSE(speculative_rfh);
  }
  
  #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) || \
      defined(OS_MAC)
  #define MAYBE_DeleteSpeculativeRFHPendingCommitOfPendingEntryOnInterrupted2 \
    DISABLED_DeleteSpeculativeRFHPendingCommitOfPendingEntryOnInterrupted2
  #else
  #define MAYBE_DeleteSpeculativeRFHPendingCommitOfPendingEntryOnInterrupted2 \
    DeleteSpeculativeRFHPendingCommitOfPendingEntryOnInterrupted2
  #endif
  // Ensures that deleting a speculative RenderFrameHost trying to commit a
  // navigation to the pending NavigationEntry will not crash if it happens
  // because a new navigation to the same pending NavigationEntry started.  This
  // is a variant of the previous test, where we destroy the speculative
  // RenderFrameHost to create another speculative RenderFrameHost. This is a
  // regression test for crbug.com/796135.
  IN_PROC_BROWSER_TEST_P(
      RenderFrameHostManagerTest,
      MAYBE_DeleteSpeculativeRFHPendingCommitOfPendingEntryOnInterrupted2) {
    const std::string kOriginalPath = "/original.html";
    const std::string kRedirectPath = "/redirect.html";
    net::test_server::ControllableHttpResponse original_response1(
        embedded_test_server(), kOriginalPath);
    net::test_server::ControllableHttpResponse original_response2(
        embedded_test_server(), kOriginalPath);
    net::test_server::ControllableHttpResponse redirect_response(
        embedded_test_server(), kRedirectPath);
    EXPECT_TRUE(embedded_test_server()->Start());
  
    const GURL kOriginalURL =
        embedded_test_server()->GetURL("a.com", kOriginalPath);
    const GURL kRedirectURL =
        embedded_test_server()->GetURL("b.com", kRedirectPath);
    const GURL kCrossSiteURL =
        embedded_test_server()->GetURL("c.com", "/title1.html");
  
    IsolationContext isolation_context(
        shell()->web_contents()->GetBrowserContext());
    const auto kOriginalSiteInfo =
        SiteInfo::CreateForTesting(isolation_context, kOriginalURL);
    const auto kRedirectSiteInfo =
        SiteInfo::CreateForTesting(isolation_context, kRedirectURL);
  
    // First navigate to the initial URL.
    shell()->LoadURL(kOriginalURL);
    original_response1.WaitForRequest();
    original_response1.Send(
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=utf-8\r\n"
        "Cache-Control: no-cache, no-store, must-revalidate\r\n"
        "Pragma: no-cache\r\n"
        "\r\n");
    original_response1.Send(
        "<html>"
        "<body></body>"
        "</html>");
    original_response1.Done();
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(kOriginalURL, shell()->web_contents()->GetLastCommittedURL());
  
    // Navigate cross-site.
    EXPECT_TRUE(NavigateToURL(shell(), kCrossSiteURL));
  
    // Now go back to the original request, which will do a cross-site redirect.
    TestNavigationManager first_back(shell()->web_contents(), kOriginalURL);
    shell()->GoBackOrForward(-1);
    EXPECT_TRUE(first_back.WaitForRequestStart());
    first_back.ResumeNavigation();
  
    original_response2.WaitForRequest();
    original_response2.Send(
        "HTTP/1.1 302 FOUND\r\n"
        "Location: " +
        kRedirectURL.spec() +
        "\r\n"
        "\r\n");
    original_response2.Done();
    redirect_response.WaitForRequest();
    redirect_response.Send(
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=utf-8\r\n"
        "\r\n");
    EXPECT_TRUE(first_back.WaitForResponse());
    first_back.ResumeNavigation();
  
    // The navigation is ready to commit: it has been handed to the speculative
    // RenderFrameHost for commit.
    RenderFrameHostImpl* speculative_rfh =
        static_cast<WebContentsImpl*>(shell()->web_contents())
            ->GetFrameTree()
            ->root()
            ->render_manager()
            ->speculative_frame_host();
    CHECK(speculative_rfh);
    EXPECT_TRUE(speculative_rfh->is_loading());
    if (AreAllSitesIsolatedForTesting()) {
      EXPECT_EQ(kRedirectSiteInfo,
                speculative_rfh->GetSiteInstance()->GetSiteInfo());
    } else if (AreDefaultSiteInstancesEnabled()) {
      EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
    } else {
      EXPECT_EQ(kOriginalSiteInfo,
                speculative_rfh->GetSiteInstance()->GetSiteInfo());
    }
    int site_instance_id = speculative_rfh->GetSiteInstance()->GetId();
  
    // The user starts a navigation towards the redirected URL, for which we have
    // a speculative RenderFrameHost. This shouldn't delete the speculative
    // RenderFrameHost.
    TestNavigationManager navigation_to_redirect(shell()->web_contents(),
                                                 kRedirectURL);
    shell()->LoadURL(kRedirectURL);
    EXPECT_TRUE(navigation_to_redirect.WaitForRequestStart());
    speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
                          ->GetFrameTree()
                          ->root()
                          ->render_manager()
                          ->speculative_frame_host();
    CHECK(speculative_rfh);
    if (AreDefaultSiteInstancesEnabled()) {
      EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
    } else {
      EXPECT_EQ(kRedirectSiteInfo,
                speculative_rfh->GetSiteInstance()->GetSiteInfo());
      if (AreAllSitesIsolatedForTesting())
        EXPECT_EQ(site_instance_id, speculative_rfh->GetSiteInstance()->GetId());
    }
  
    // The user requests to go back again while the previous back hasn't committed
    // yet. This should delete the speculative RenderFrameHost trying to commit
    // the back, and re-create a new speculative RenderFrameHost. This shouldn't
    // crash.
    TestNavigationManager second_back(shell()->web_contents(), kOriginalURL);
    shell()->GoBackOrForward(-1);
    EXPECT_TRUE(second_back.WaitForRequestStart());
    speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
                          ->GetFrameTree()
                          ->root()
                          ->render_manager()
                          ->speculative_frame_host();
    CHECK(speculative_rfh);
    if (AreDefaultSiteInstancesEnabled()) {
      EXPECT_TRUE(speculative_rfh->GetSiteInstance()->IsDefaultSiteInstance());
    } else {
      EXPECT_EQ(kOriginalSiteInfo,
                speculative_rfh->GetSiteInstance()->GetSiteInfo());
    }
    if (AreAllSitesIsolatedForTesting())
      EXPECT_NE(site_instance_id, speculative_rfh->GetSiteInstance()->GetId());
  }
  
  // Test for crbug.com/9682.  We should not show the URL for a pending renderer-
  // initiated navigation in a new tab if it is not the initial navigation.  In
  // this case, the renderer will not notify us of a modification, so we cannot
  // show the pending URL without allowing a spoof.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         DontShowLoadingURLIfNotInitialNav) {
    ASSERT_TRUE(embedded_test_server()->Start());
  
    // Load a page that can open a URL that won't commit in a new window.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/click-nocontent-link.html")));
    WebContents* orig_contents = shell()->web_contents();
  
    // Click a /nocontent link that opens in a new window but never commits.
    // By using an onclick handler that first creates the window, the slow
    // navigation is not considered an initial navigation.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(
        ExecuteScriptAndExtractBool(orig_contents,
                                    "window.domAutomationController.send("
                                    "clickNoContentScriptedTargetedLink());",
                                    &success));
    EXPECT_TRUE(success);
  
    // Wait for the window to open.
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Ensure the destination URL is not visible, because it is not the initial
    // navigation.
    WebContents* contents = new_shell->web_contents();
    EXPECT_FALSE(contents->GetController().IsInitialNavigation());
    EXPECT_FALSE(contents->GetController().GetVisibleEntry());
  }
  
  // Crashes under ThreadSanitizer, http://crbug.com/356758.
  #if defined(THREAD_SANITIZER)
  #define MAYBE_BackForwardNotStale DISABLED_BackForwardNotStale
  #else
  #define MAYBE_BackForwardNotStale BackForwardNotStale
  #endif
  // Test for http://crbug.com/93427.  Ensure that cross-site navigations
  // do not cause back/forward navigations to be considered stale by the
  // renderer.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, MAYBE_BackForwardNotStale) {
    StartEmbeddedServer();
    EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
  
    // Visit a page on first site.
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
  
    // Visit three pages on second site.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("foo.com", "/title1.html")));
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("foo.com", "/title2.html")));
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("foo.com", "/title3.html")));
  
    // History is now [blank, A1, B1, B2, *B3].
    WebContents* contents = shell()->web_contents();
    EXPECT_EQ(5, contents->GetController().GetEntryCount());
  
    // Open another window in same process to keep this process alive.
    Shell* new_shell = CreateBrowser();
    EXPECT_TRUE(NavigateToURL(
        new_shell, embedded_test_server()->GetURL("foo.com", "/title1.html")));
  
    // Go back three times to first site.
    {
      TestNavigationObserver back_nav_load_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoBack();
      back_nav_load_observer.Wait();
    }
    {
      TestNavigationObserver back_nav_load_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoBack();
      back_nav_load_observer.Wait();
    }
    {
      TestNavigationObserver back_nav_load_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoBack();
      back_nav_load_observer.Wait();
    }
  
    // Now go forward twice to B2.  Shouldn't be left spinning.
    {
      TestNavigationObserver forward_nav_load_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoForward();
      forward_nav_load_observer.Wait();
    }
    {
      TestNavigationObserver forward_nav_load_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoForward();
      forward_nav_load_observer.Wait();
    }
  
    // Go back twice to first site.
    {
      TestNavigationObserver back_nav_load_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoBack();
      back_nav_load_observer.Wait();
    }
    {
      TestNavigationObserver back_nav_load_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoBack();
      back_nav_load_observer.Wait();
    }
  
    // Now go forward directly to B3.  Shouldn't be left spinning.
    {
      TestNavigationObserver forward_nav_load_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoToIndex(4);
      forward_nav_load_observer.Wait();
    }
  }
  
  // This class ensures that all the given RenderViewHosts have properly been
  // shutdown.
  class RenderViewHostDestructionObserver : public WebContentsObserver {
   public:
    explicit RenderViewHostDestructionObserver(WebContents* web_contents)
        : WebContentsObserver(web_contents) {}
    ~RenderViewHostDestructionObserver() override = default;
    void EnsureRVHGetsDestructed(RenderViewHost* rvh) {
      watched_render_view_hosts_.insert(rvh);
    }
    size_t GetNumberOfWatchedRenderViewHosts() const {
      return watched_render_view_hosts_.size();
    }
  
   private:
    // WebContentsObserver implementation:
    void RenderViewDeleted(RenderViewHost* rvh) override {
      watched_render_view_hosts_.erase(rvh);
    }
  
    std::set<RenderViewHost*> watched_render_view_hosts_;
  };
  
  // Crashes under ThreadSanitizer, http://crbug.com/356758.
  #if defined(THREAD_SANITIZER)
  #define MAYBE_LeakingRenderViewHosts DISABLED_LeakingRenderViewHosts
  #else
  #define MAYBE_LeakingRenderViewHosts LeakingRenderViewHosts
  #endif
  // Test for crbug.com/90867. Make sure we don't leak render view hosts since
  // they may cause crashes or memory corruptions when trying to call dead
  // delegate_. This test also verifies crbug.com/117420 and crbug.com/143255 to
  // ensure that a separate SiteInstance is created when navigating to view-source
  // URLs, regardless of current URL.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         MAYBE_LeakingRenderViewHosts) {
    StartEmbeddedServer();
  
    // Observe the created render_view_host's to make sure they will not leak.
    RenderViewHostDestructionObserver rvh_observers(shell()->web_contents());
  
    GURL navigated_url(embedded_test_server()->GetURL("/title2.html"));
    GURL view_source_url(kViewSourceScheme + std::string(":") +
                         navigated_url.spec());
  
    // Let's ensure that when we start with a blank window, navigating away to a
    // view-source URL, we create a new SiteInstance.
    RenderViewHost* blank_rvh =
        shell()->web_contents()->GetMainFrame()->GetRenderViewHost();
    SiteInstance* blank_site_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), GURL::EmptyGURL());
    EXPECT_EQ(blank_site_instance->GetSiteURL(), GURL::EmptyGURL());
    rvh_observers.EnsureRVHGetsDestructed(blank_rvh);
  
    // Now navigate to the view-source URL and ensure we got a different
    // SiteInstance and RenderViewHost.
    EXPECT_TRUE(NavigateToURL(shell(), view_source_url));
    EXPECT_NE(blank_rvh,
              shell()->web_contents()->GetMainFrame()->GetRenderViewHost());
    EXPECT_NE(blank_site_instance, shell()
                                       ->web_contents()
                                       ->GetMainFrame()
                                       ->GetSiteInstance());
    rvh_observers.EnsureRVHGetsDestructed(
        shell()->web_contents()->GetMainFrame()->GetRenderViewHost());
  
    // Load a random page and then navigate to view-source: of it.
    // This used to cause two RVH instances for the same SiteInstance, which
    // was a problem.  This is no longer the case.
    EXPECT_TRUE(NavigateToURL(shell(), navigated_url));
    SiteInstance* site_instance1 = shell()
                                       ->web_contents()
                                       ->GetMainFrame()
                                       ->GetSiteInstance();
    rvh_observers.EnsureRVHGetsDestructed(
        shell()->web_contents()->GetMainFrame()->GetRenderViewHost());
  
    EXPECT_TRUE(NavigateToURL(shell(), view_source_url));
    rvh_observers.EnsureRVHGetsDestructed(
        shell()->web_contents()->GetMainFrame()->GetRenderViewHost());
    SiteInstance* site_instance2 = shell()
                                       ->web_contents()
                                       ->GetMainFrame()
                                       ->GetSiteInstance();
  
    // Ensure that view-source navigations force a new SiteInstance.
    EXPECT_NE(site_instance1, site_instance2);
  
    // Now navigate to a different instance so that we swap out again.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("foo.com", "/title2.html")));
    rvh_observers.EnsureRVHGetsDestructed(
        shell()->web_contents()->GetMainFrame()->GetRenderViewHost());
  
    // This used to leak a render view host.
    shell()->Close();
  
    RunAllPendingInMessageLoop();  // Needed on ChromeOS.
  
    EXPECT_EQ(0U, rvh_observers.GetNumberOfWatchedRenderViewHosts());
  }
  
  // Test for crbug.com/143155.  Frame tree updates during unload should not
  // interrupt the intended navigation.
  // Specifically:
  // 1) Open 2 tabs in an HTTP SiteInstance, with a subframe in the opener.
  // 2) Send the second tab to a different foo.com SiteInstance.
  //    This created an opener proxy for the first tab in the foo process.
  // 3) Navigate the first tab to the foo.com SiteInstance, and have the first
  //    tab's unload handler remove its frame.
  // In older versions of Chrome, this caused an update to the frame tree that
  // resulted in showing an internal page rather than the real page.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         DontPreemptNavigationWithFrameTreeUpdate) {
    StartEmbeddedServer();
  
    if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
      // Isolate "foo.com" so we are guaranteed to get a non-default
      // SiteInstance for navigations to this origin.
      IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                               {"foo.com"});
    }
  
    // 1. Load a page that deletes its iframe during unload.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/remove_frame_on_unload.html")));
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
  
    // Open a same-site page in a new window.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(), "window.domAutomationController.send(openWindow());", &success));
    EXPECT_TRUE(success);
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Wait for the navigation in the new window to finish, if it hasn't.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
    EXPECT_EQ("/title1.html",
              new_shell->web_contents()->GetLastCommittedURL().path());
  
    // Should have the same SiteInstance.
    EXPECT_EQ(orig_site_instance.get(),
              new_shell->web_contents()->GetSiteInstance());
  
    // 2. Send the second tab to a different process.
    GURL cross_site_url(
        embedded_test_server()->GetURL("foo.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
    scoped_refptr<SiteInstance> new_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(orig_site_instance, new_site_instance);
  
    // 3. Send the first tab to the second tab's process.
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(shell(), cross_site_url));
  
    // Make sure it ends up at the right page.
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(cross_site_url, shell()->web_contents()->GetLastCommittedURL());
    EXPECT_EQ(new_site_instance, shell()->web_contents()->GetSiteInstance());
  }
  
  // Ensure that renderer-side debug URLs do not cause a process swap, since they
  // are meant to run in the current page.  We had a bug where we expected a
  // BrowsingInstance swap to occur on pages like view-source and extensions,
  // which broke chrome://crash and javascript: URLs.
  // See http://crbug.com/335503.
  // The test fails on Mac OSX with ASAN.
  // See http://crbug.com/699062.
  #if defined(OS_MAC) && defined(THREAD_SANITIZER)
  #define MAYBE_RendererDebugURLsDontSwap DISABLED_RendererDebugURLsDontSwap
  #else
  #define MAYBE_RendererDebugURLsDontSwap RendererDebugURLsDontSwap
  #endif
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         MAYBE_RendererDebugURLsDontSwap) {
    StartEmbeddedServer();
  
    GURL original_url(embedded_test_server()->GetURL("/title2.html"));
    GURL view_source_url(kViewSourceScheme + std::string(":") +
                         original_url.spec());
  
    EXPECT_TRUE(NavigateToURL(shell(), view_source_url));
  
    // Check that javascript: URLs work.
    std::u16string expected_title = u"msg";
    TitleWatcher title_watcher(shell()->web_contents(), expected_title);
    shell()->LoadURL(GURL("javascript:document.title='msg'"));
    ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    // Crash the renderer of the view-source page.
    RenderProcessHostWatcher crash_observer(
        shell()->web_contents(),
        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), GURL(kChromeUICrashURL)));
    crash_observer.Wait();
  
    // We should not change SiteInstance and BrowsingInstance on navigations to
    // RendererDebug URLs.
    auto* new_site_instance = shell()->web_contents()->GetSiteInstance();
    EXPECT_EQ(orig_site_instance, new_site_instance);
    EXPECT_TRUE(orig_site_instance->IsRelatedSiteInstance(new_site_instance));
  }
  
  // Ensure that renderer-side debug URLs don't take effect on crashed renderers.
  // Otherwise, we might try to load an unprivileged about:blank page into a
  // WebUI-enabled RenderProcessHost, failing a safety check in InitRenderView.
  // See http://crbug.com/334214.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         IgnoreRendererDebugURLsWhenCrashed) {
    // Visit a WebUI page with bindings.
    GURL webui_url = GURL(std::string(kChromeUIScheme) + "://" +
                          std::string(kChromeUIGpuHost));
    EXPECT_TRUE(NavigateToURL(shell(), webui_url));
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
  
    // Crash the renderer of the WebUI page.
    RenderProcessHostWatcher crash_observer(
        shell()->web_contents(),
        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), GURL(kChromeUICrashURL)));
    crash_observer.Wait();
  
    // Load the crash URL again but don't wait for any action.  If it is not
    // ignored this time, we will fail the WebUI CHECK in InitRenderView.
    shell()->LoadURL(GURL(kChromeUICrashURL));
  
    // Ensure that such URLs can still work as the initial navigation of a tab.
    // We postpone the initial navigation of the tab using an empty GURL, so that
    // we can add a watcher for crashes.
    Shell* shell2 =
        Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
                               GURL(), nullptr, gfx::Size());
    RenderProcessHostWatcher crash_observer2(
        shell2->web_contents(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell2, GURL(kChromeUIKillURL)));
    crash_observer2.Wait();
  }
  
  class RFHMProcessPerTabTest : public RenderFrameHostManagerTest {
   public:
    RFHMProcessPerTabTest() = default;
  
    void SetUpCommandLine(base::CommandLine* command_line) override {
      command_line->AppendSwitch(switches::kProcessPerTab);
    }
  };
  
  // Test that we still swap processes for BrowsingInstance changes even in
  // --process-per-tab mode.  See http://crbug.com/343017.
  // Disabled on Android: http://crbug.com/345873.
  // Crashes under ThreadSanitizer, http://crbug.com/356758.
  #if defined(OS_ANDROID) || defined(THREAD_SANITIZER)
  #define MAYBE_BackFromWebUI DISABLED_BackFromWebUI
  #else
  #define MAYBE_BackFromWebUI BackFromWebUI
  #endif
  IN_PROC_BROWSER_TEST_P(RFHMProcessPerTabTest, MAYBE_BackFromWebUI) {
    StartEmbeddedServer();
    GURL original_url(embedded_test_server()->GetURL("/title2.html"));
    EXPECT_TRUE(NavigateToURL(shell(), original_url));
  
    // Visit a WebUI page with bindings.
    GURL webui_url(GURL(std::string(kChromeUIScheme) + "://" +
                        std::string(kChromeUIGpuHost)));
    EXPECT_TRUE(NavigateToURL(shell(), webui_url));
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
  
    // Go back and ensure we have no WebUI bindings.
    TestNavigationObserver back_nav_load_observer(shell()->web_contents());
    shell()->web_contents()->GetController().GoBack();
    back_nav_load_observer.Wait();
    EXPECT_EQ(original_url, shell()->web_contents()->GetLastCommittedURL());
    EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
  }
  
  // crbug.com/372360
  // The test loads url1, opens a link pointing to url2 in a new tab, and
  // navigates the new tab to url1.
  // The following is needed for the bug to happen:
  //  - url1 must require webui bindings;
  //  - navigating to url2 in the site instance of url1 should not swap
  //   browsing instances, but should require a new site instance.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, WebUIGetsBindings) {
    GURL url1(std::string(kChromeUIScheme) + "://" +
              std::string(kChromeUIGpuHost));
    GURL url2(std::string(kChromeUIScheme) + "://" +
              std::string(kChromeUIHistogramHost));
  
    // Visit a WebUI page with bindings.
    EXPECT_TRUE(NavigateToURL(shell(), url1));
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
    SiteInstance* site_instance1 = shell()->web_contents()->GetSiteInstance();
    int process1_id = site_instance1->GetProcess()->GetID();
  
    // Open a new tab. Initially it gets a render view in the original tab's
    // current site instance.
    TestNavigationObserver nav_observer(nullptr);
    nav_observer.StartWatchingNewWebContents();
    ShellAddedObserver shao;
    OpenUrlViaClickTarget(shell(), url2);
    nav_observer.Wait();
    Shell* new_shell = shao.GetShell();
    WebContentsImpl* new_web_contents =
        static_cast<WebContentsImpl*>(new_shell->web_contents());
    SiteInstance* site_instance2 = new_web_contents->GetSiteInstance();
    int process2_id = site_instance2->GetProcess()->GetID();
  
    // The 2nd WebUI page should swap to a different process (and SiteInstance),
    // but should stay in the same BrowsingInstance as the 1st WebUI page.
    EXPECT_NE(process1_id, process2_id);
    EXPECT_NE(site_instance2, site_instance1);
    EXPECT_TRUE(site_instance2->IsRelatedSiteInstance(site_instance1));
  
    RenderFrameProxyHost* initial_rfph =
        new_web_contents->GetRenderManagerForTesting()->GetRenderFrameProxyHost(
            site_instance1);
    ASSERT_TRUE(initial_rfph);
  
    // Navigate to url1 and check bindings.
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, url1));
    // The navigation should have used the first SiteInstance, otherwise
    // |initial_rvh| did not have a chance to be used.
    EXPECT_EQ(new_web_contents->GetSiteInstance(), site_instance1);
    EXPECT_EQ(BINDINGS_POLICY_WEB_UI,
              new_web_contents->GetMainFrame()->GetEnabledBindings());
  }
  
  // crbug.com/424526
  // The test loads a WebUI page in process-per-tab mode, then navigates to a
  // blank page and then to a regular page. The bug reproduces if blank page is
  // visited in between WebUI and regular page.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ForceSwapAfterWebUIBindings) {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kProcessPerTab);
    StartEmbeddedServer();
  
    const GURL web_ui_url(std::string(kChromeUIScheme) + "://" +
                          std::string(kChromeUIGpuHost));
    EXPECT_TRUE(NavigateToURL(shell(), web_ui_url));
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
  
    // Capture the SiteInstance before navigating to about:blank to ensure
    // it doesn't change.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
  
    EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
    EXPECT_NE(orig_site_instance, shell()->web_contents()->GetSiteInstance());
  
    GURL regular_page_url(embedded_test_server()->GetURL("/title2.html"));
    EXPECT_TRUE(NavigateToURL(shell(), regular_page_url));
    EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID()));
  }
  
  // crbug.com/615274
  // This test ensures that after an RFH is unloaded, the associated WebUI
  // instance is no longer allowed to send JavaScript messages. This is necessary
  // because WebUI currently (and unusually) always sends JavaScript messages to
  // the current main frame, rather than the RFH that owns the WebUI.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         WebUIJavascriptDisallowedAfterUnload) {
    StartEmbeddedServer();
  
    const GURL web_ui_url(std::string(kChromeUIScheme) + "://" +
                          std::string(kChromeUIGpuHost));
    EXPECT_TRUE(NavigateToURL(shell(), web_ui_url));
  
    RenderFrameHostImpl* rfh =
        static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame();
  
    // Set up a slow unload handler to force the RFH to linger in the unloaded
    // but not-yet-deleted state.
    EXPECT_TRUE(
        ExecuteScript(rfh, "window.onunload=function(e){ while(1); };\n"));
  
    WebUIImpl* web_ui = rfh->web_ui();
  
    EXPECT_TRUE(web_ui->CanCallJavascript());
    auto handler_owner = std::make_unique<TestWebUIMessageHandler>();
    TestWebUIMessageHandler* handler = handler_owner.get();
  
    web_ui->AddMessageHandler(std::move(handler_owner));
    EXPECT_FALSE(handler->IsJavascriptAllowed());
  
    handler->AllowJavascript();
    EXPECT_TRUE(handler->IsJavascriptAllowed());
  
    rfh->DisableUnloadTimerForTesting();
    RenderFrameHostDestructionObserver rfh_observer(rfh);
  
    // Navigate, but wait for commit, not the actual load to finish.
    SiteInstanceImpl* web_ui_site_instance = rfh->GetSiteInstance();
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    TestFrameNavigationObserver commit_observer(root);
    shell()->LoadURL(GURL(url::kAboutBlankURL));
    commit_observer.WaitForCommit();
    EXPECT_NE(web_ui_site_instance, shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(
        root->render_manager()->GetRenderFrameProxyHost(web_ui_site_instance));
  
    // The previous RFH should still be pending deletion, as we wait for either
    // the mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame or a timeout.
    ASSERT_TRUE(rfh->IsRenderFrameLive());
    ASSERT_TRUE(rfh->IsPendingDeletion());
  
    // We specifically want verify behavior between unload and RFH destruction.
    ASSERT_FALSE(rfh_observer.deleted());
  
    EXPECT_FALSE(handler->IsJavascriptAllowed());
  }
  
  // Test for http://crbug.com/703303.  Ensures that the renderer process does not
  // try to select files whose paths cannot be converted to WebStrings.  This
  // check is done in the renderer because it is hard to predict which paths will
  // turn into empty WebStrings, and the behavior varies by platform.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, DontSelectInvalidFiles) {
    StartServer();
    base::RunLoop run_loop;
  
    // Use a file path with an invalid encoding, such that it can't be converted
    // to a WebString (on all platforms but Windows).
    base::FilePath file;
    EXPECT_TRUE(base::PathService::Get(base::DIR_TEMP, &file));
    file = file.Append(FILE_PATH_LITERAL("foo\337bar"));
  
    // Navigate and try to get page to reference this file in its PageState.
    GURL url1(embedded_test_server()->GetURL("/file_input.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url1));
    int process_id =
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
    std::unique_ptr<FileChooserDelegate> delegate(
        new FileChooserDelegate(file, run_loop.QuitClosure()));
    shell()->web_contents()->SetDelegate(delegate.get());
    EXPECT_TRUE(
        ExecuteScript(shell(), "document.getElementById('fileinput').click();"));
    run_loop.Run();
  
    // The browser process grants access to the file whether or not the renderer
    // process realizes that it can't use it.  This is ok, since the user actually
    // did select the file from the chooser.
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        process_id, file));
  
    // Disable the unload timer so we wait for the UpdateState message.
    static_cast<WebContentsImpl*>(shell()->web_contents())
        ->GetMainFrame()
        ->DisableUnloadTimerForTesting();
  
    // Navigate to a different process and wait for the old process to exit.
    RenderProcessHostWatcher exit_observer(
        shell()->web_contents()->GetMainFrame()->GetProcess(),
        RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
  
    // With BackForwardCache, old process won't get deleted on navigation as it is
    // still in use by the bfcached document, disable back-forward cache to ensure
    // that the process gets deleted.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
    exit_observer.Wait();
    EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID(), file));
  
    // The renderer process should not have been killed.  This is the important
    // part of the test.  If this fails, then we didn't get a PageState to check
    // below, so use an assert (since the test can't meaningfully proceed).
    ASSERT_TRUE(exit_observer.did_exit_normally());
  
    // Ensure that the file did not end up in the PageState of the previous entry,
    // except on Windows where the path is valid and WebString can handle it.
    NavigationEntry* prev_entry =
        shell()->web_contents()->GetController().GetEntryAtIndex(0);
    EXPECT_EQ(url1, prev_entry->GetURL());
    const std::vector<base::FilePath>& files =
        prev_entry->GetPageState().GetReferencedFiles();
  #if defined(OS_WIN)
    EXPECT_EQ(1U, files.size());
  #else
    EXPECT_EQ(0U, files.size());
  #endif
  }
  
  // Test for http://crbug.com/262948.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         RestoreFileAccessForHistoryNavigation) {
    StartServer();
    base::RunLoop run_loop;
    base::FilePath file;
    EXPECT_TRUE(base::PathService::Get(base::DIR_TEMP, &file));
    file = file.AppendASCII("bar");
  
    WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // Navigate to url and get it to reference a file in its PageState.
    GURL url1(embedded_test_server()->GetURL("/file_input.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url1));
    int process_id = wc->GetMainFrame()->GetProcess()->GetID();
    std::unique_ptr<FileChooserDelegate> delegate(
        new FileChooserDelegate(file, run_loop.QuitClosure()));
    wc->SetDelegate(delegate.get());
    EXPECT_TRUE(
        ExecuteScript(shell(), "document.getElementById('fileinput').click();"));
    run_loop.Run();
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        process_id, file));
  
    // Disable the unload timer so we wait for the UpdateState message.
    wc->GetMainFrame()->DisableUnloadTimerForTesting();
  
    // Navigate to a different process without access to the file, and wait for
    // the old process to exit.
    RenderProcessHostWatcher exit_observer(
        wc->GetMainFrame()->GetProcess(),
        RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
  
    // With BackForwardCache, old process won't get deleted on navigation as it is
    // still in use by the bfcached document, disable back-forward cache to ensure
    // that the process gets deleted.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
    exit_observer.Wait();
    EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        wc->GetMainFrame()->GetProcess()->GetID(), file));
  
    // Ensure that the file ended up in the PageState of the previous entry.
    NavigationEntry* prev_entry = wc->GetController().GetEntryAtIndex(0);
    EXPECT_EQ(url1, prev_entry->GetURL());
    const std::vector<base::FilePath>& files =
        prev_entry->GetPageState().GetReferencedFiles();
    ASSERT_EQ(1U, files.size());
    EXPECT_EQ(file, files.at(0));
  
    // Go back, ending up in a different RenderProcessHost than before.
    TestNavigationObserver back_nav_load_observer(wc);
    wc->GetController().GoBack();
    back_nav_load_observer.Wait();
    EXPECT_NE(process_id, wc->GetMainFrame()->GetProcess()->GetID());
  
    // Ensure that the file access still exists in the new process ID.
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        wc->GetMainFrame()->GetProcess()->GetID(), file));
  
    // Navigate to a same site page to trigger a PageState update and ensure the
    // renderer is not killed.
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
  }
  
  // Same as RenderFrameHostManagerTest.RestoreFileAccessForHistoryNavigation, but
  // replace the cross-origin navigation by a crash, followed by a reload.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         RestoreFileAccessForHistoryNavigationAfterCrash) {
    StartServer();
    base::RunLoop run_loop;
    base::FilePath file;
    EXPECT_TRUE(base::PathService::Get(base::DIR_TEMP, &file));
    file = file.AppendASCII("bar");
  
    WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // Navigate to url and get it to reference a file in its PageState.
    GURL url1(embedded_test_server()->GetURL("/file_input.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url1));
    int process_id = wc->GetMainFrame()->GetProcess()->GetID();
    std::unique_ptr<FileChooserDelegate> delegate(
        new FileChooserDelegate(file, run_loop.QuitClosure()));
    wc->SetDelegate(delegate.get());
    EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        process_id, file));
    EXPECT_TRUE(
        ExecuteScript(shell(), "document.getElementById('fileinput').click();"));
    run_loop.Run();
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        process_id, file));
  
    // The PageState hasn't been updated yet. It requires a navigation.
    {
      NavigationEntry* prev_entry = wc->GetController().GetEntryAtIndex(0);
      EXPECT_EQ(url1, prev_entry->GetURL());
      const std::vector<base::FilePath>& files =
          prev_entry->GetPageState().GetReferencedFiles();
      ASSERT_EQ(0U, files.size());
    }
  
    // Same-document navigation
    EXPECT_TRUE(ExecJs(shell(), "history.pushState({},'title','#foo')"));
  
    // The PageState has been updated, it now contains the file.
    {
      NavigationEntry* prev_entry = wc->GetController().GetEntryAtIndex(0);
      EXPECT_EQ(url1, prev_entry->GetURL());
      const std::vector<base::FilePath>& files =
          prev_entry->GetPageState().GetReferencedFiles();
      ASSERT_EQ(1U, files.size());
      EXPECT_EQ(file, files.at(0));
    }
  
    // Crash.
    {
      RenderProcessHost* process = wc->GetMainFrame()->GetProcess();
      RenderProcessHostWatcher crash_observer(
          process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
      process->Shutdown(0);
      crash_observer.Wait();
    }
  
    // The renderer process is still allowed to read the file, even if it is
    // crashed.
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        wc->GetMainFrame()->GetProcess()->GetID(), file));
  
    // Reload
    wc->GetController().Reload(ReloadType::NORMAL, false);
    EXPECT_TRUE(WaitForLoadStop(wc));
  
    // After recovering from the crash, the renderer process is allowed to read
    // the file.
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        wc->GetMainFrame()->GetProcess()->GetID(), file));
  
    // Same-document history back navigation.
    {
      TestNavigationObserver back_nav_load_observer(wc);
      wc->GetController().GoBack();
      back_nav_load_observer.Wait();
    }
  
    // Ensure that the file access still exists in the new process ID.
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        wc->GetMainFrame()->GetProcess()->GetID(), file));
  
    // Navigate to a same site page to trigger a PageState update and ensure the
    // renderer is not killed.
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
  }
  
  // Test for http://crbug.com/441966.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         RestoreSubframeFileAccessForHistoryNavigation) {
    StartServer();
    base::RunLoop run_loop;
    base::FilePath file;
    EXPECT_TRUE(base::PathService::Get(base::DIR_TEMP, &file));
    file = file.AppendASCII("bar");
  
    // Navigate to url and get it to reference a file in its PageState.
    GURL url1(embedded_test_server()->GetURL("/file_input_subframe.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url1));
    WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
    FrameTreeNode* root = wc->GetFrameTree()->root();
    int process_id =
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
    std::unique_ptr<FileChooserDelegate> delegate(
        new FileChooserDelegate(file, run_loop.QuitClosure()));
    shell()->web_contents()->SetDelegate(delegate.get());
    EXPECT_TRUE(ExecuteScript(root->child_at(0),
                              "document.getElementById('fileinput').click();"));
    run_loop.Run();
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        process_id, file));
  
    // Disable the unload timer so we wait for the UpdateState message.
    root->current_frame_host()->DisableUnloadTimerForTesting();
  
    // Do an in-page navigation in the child to make sure we hear a PageState with
    // the chosen file before the subframe's FrameTreeNode is deleted.  In
    // practice, we'll get the PageState 1 second after the file is chosen.
    // TODO(creis): Remove this in-page navigation once we keep track of
    // FrameTreeNodes that are pending deletion.  See https://crbug.com/609963.
    {
      TestNavigationObserver nav_observer(shell()->web_contents());
      std::string script = "location.href='#foo';";
      EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
      nav_observer.Wait();
    }
  
    // Navigate to a different process without access to the file, and wait for
    // the old process to exit.
    RenderProcessHostWatcher exit_observer(
        shell()->web_contents()->GetMainFrame()->GetProcess(),
        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  
    // With BackForwardCache, old process won't get deleted on navigation as it is
    // still in use by the bfcached document, disable back-forward cache to ensure
    // that the process gets deleted.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
    exit_observer.Wait();
    EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID(), file));
  
    // Ensure that the file ended up in the PageState of the previous entry.
    NavigationEntry* prev_entry =
        shell()->web_contents()->GetController().GetEntryAtIndex(0);
    EXPECT_EQ(url1, prev_entry->GetURL());
    const std::vector<base::FilePath>& files =
        prev_entry->GetPageState().GetReferencedFiles();
    ASSERT_EQ(1U, files.size());
    EXPECT_EQ(file, files.at(0));
  
    // Go back, ending up in a different RenderProcessHost than before.
    TestNavigationObserver back_nav_load_observer(shell()->web_contents());
    shell()->web_contents()->GetController().GoToIndex(0);
    back_nav_load_observer.Wait();
    EXPECT_NE(process_id,
              shell()->web_contents()->GetMainFrame()->GetProcess()->GetID());
  
    // Ensure that the file access still exists in the new process ID.
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID(), file));
  
    // Do another in-page navigation in the child to make sure we hear a PageState
    // with the chosen file.
    // TODO(creis): Remove this in-page navigation once we keep track of
    // FrameTreeNodes that are pending deletion.  See https://crbug.com/609963.
    {
      TestNavigationObserver nav_observer(shell()->web_contents());
      std::string script = "location.href='#foo';";
      EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
      nav_observer.Wait();
    }
  
    // Also try cloning the tab by creating a new NavigationEntry with the same
    // PageState.  This exercises a different path, by combining the frame
    // specific PageStates into a full-tree PageState and converting back.  There
    // was a bug where this caused us to lose the list of referenced files.  See
    // https://crbug.com/620261.
    std::unique_ptr<NavigationEntryImpl> cloned_entry =
        NavigationEntryImpl::FromNavigationEntry(
            NavigationController::CreateNavigationEntry(
                url1, Referrer(), base::nullopt, ui::PAGE_TRANSITION_RELOAD,
                false, std::string(),
                shell()->web_contents()->GetBrowserContext(),
                nullptr /* blob_url_loader_factory */));
    prev_entry = shell()->web_contents()->GetController().GetEntryAtIndex(0);
    cloned_entry->SetPageState(prev_entry->GetPageState());
    const std::vector<base::FilePath>& cloned_files =
        cloned_entry->GetPageState().GetReferencedFiles();
    ASSERT_EQ(1U, cloned_files.size());
    EXPECT_EQ(file, cloned_files.at(0));
  
    std::vector<std::unique_ptr<NavigationEntry>> entries;
    entries.push_back(std::move(cloned_entry));
    Shell* new_shell =
        Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
                               GURL::EmptyGURL(), nullptr, gfx::Size());
    FrameTreeNode* new_root =
        static_cast<WebContentsImpl*>(new_shell->web_contents())
            ->GetFrameTree()
            ->root();
    NavigationControllerImpl& new_controller =
        static_cast<NavigationControllerImpl&>(
            new_shell->web_contents()->GetController());
    new_controller.Restore(entries.size() - 1, RestoreType::kRestored, &entries);
    ASSERT_EQ(0u, entries.size());
    {
      TestNavigationObserver restore_observer(new_shell->web_contents());
      new_controller.LoadIfNecessary();
      restore_observer.Wait();
    }
    ASSERT_EQ(1U, new_root->child_count());
    EXPECT_EQ(url1, new_root->current_url());
  
    // Ensure that the file access exists in the new process ID.
    EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
        new_root->current_frame_host()->GetProcess()->GetID(), file));
  
    // Also, extract the file from the renderer process to ensure that the
    // response made it over successfully and the proper filename is set.
    std::string file_name;
    EXPECT_TRUE(ExecuteScriptAndExtractString(
        new_root->child_at(0),
        "window.domAutomationController.send("
        "document.getElementById('fileinput').files[0].name);",
        &file_name));
    EXPECT_EQ("bar", file_name);
  
    // Navigate to a same site page to trigger a PageState update and ensure the
    // renderer is not killed.
    EXPECT_TRUE(
        NavigateToURL(new_shell, embedded_test_server()->GetURL("/title2.html")));
  }
  
  // Ensures that no RenderFrameHost/RenderViewHost objects are leaked when
  // doing a simple cross-process navigation.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         CleanupOnCrossProcessNavigation) {
    StartEmbeddedServer();
  
    // Do an initial navigation and capture objects we expect to be cleaned up
    // on cross-process navigation.
    GURL start_url = embedded_test_server()->GetURL("/title1.html");
    EXPECT_TRUE(NavigateToURL(shell(), start_url));
  
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    int32_t orig_site_instance_id =
        root->current_frame_host()->GetSiteInstance()->GetId();
    int initial_process_id =
        root->current_frame_host()->GetSiteInstance()->GetProcess()->GetID();
    int initial_rfh_id = root->current_frame_host()->GetRoutingID();
    int initial_rvh_id =
        root->current_frame_host()->render_view_host()->GetRoutingID();
  
    // Navigate cross-process and ensure that cleanup is performed as expected.
    GURL cross_site_url =
        embedded_test_server()->GetURL("foo.com", "/title2.html");
    RenderFrameHostDestructionObserver rfh_observer(root->current_frame_host());
  
    // The old RenderFrameHost might have entered the BackForwardCache. Disable
    // back-forward cache to ensure that the RenderFrameHost gets deleted.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
    rfh_observer.Wait();
  
    EXPECT_NE(orig_site_instance_id,
              root->current_frame_host()->GetSiteInstance()->GetId());
    EXPECT_FALSE(RenderFrameHost::FromID(initial_process_id, initial_rfh_id));
    EXPECT_FALSE(RenderViewHost::FromID(initial_process_id, initial_rvh_id));
  }
  
  // Ensure that the opener chain proxies and RVHs are properly reinitialized if
  // a tab crashes and reloads.  See https://crbug.com/505090.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ReinitializeOpenerChainAfterCrashAndReload) {
    StartEmbeddedServer();
  
    if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
      // Isolate "foo.com" so we are guaranteed to get a non-default
      // SiteInstance for navigations to this origin.
      IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                               {"foo.com"});
    }
  
    GURL main_url = embedded_test_server()->GetURL("/title1.html");
    EXPECT_TRUE(NavigateToURL(shell(), main_url));
  
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
  
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance);
  
    // Open a popup and navigate it cross-site.
    Shell* new_shell = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo");
    EXPECT_TRUE(new_shell);
    FrameTreeNode* popup_root =
        static_cast<WebContentsImpl*>(new_shell->web_contents())
            ->GetFrameTree()
            ->root();
  
    GURL cross_site_url =
        embedded_test_server()->GetURL("foo.com", "/title2.html");
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(new_shell, cross_site_url));
  
    scoped_refptr<SiteInstance> foo_site_instance(
        new_shell->web_contents()->GetSiteInstance());
    EXPECT_NE(foo_site_instance, orig_site_instance);
  
    // Kill the popup's process.
    RenderProcessHost* popup_process =
        popup_root->current_frame_host()->GetProcess();
    RenderProcessHostWatcher crash_observer(
        popup_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    popup_process->Shutdown(0);
    crash_observer.Wait();
    EXPECT_FALSE(popup_root->current_frame_host()->IsRenderFrameLive());
    EXPECT_FALSE(
        popup_root->current_frame_host()->render_view_host()->IsRenderViewLive());
  
    // The proxy and RVH for the opener page in the foo.com SiteInstance should
    // not be live.
    RenderFrameHostManager* opener_manager = root->render_manager();
    RenderFrameProxyHost* opener_rfph =
        opener_manager->GetRenderFrameProxyHost(foo_site_instance.get());
    EXPECT_TRUE(opener_rfph);
    EXPECT_FALSE(opener_rfph->is_render_frame_proxy_live());
    RenderViewHostImpl* opener_rvh = opener_rfph->GetRenderViewHost();
    EXPECT_TRUE(opener_rvh);
    EXPECT_FALSE(opener_rvh->IsRenderViewLive());
  
    // Re-navigate the popup to the same URL and check that this recreates the
    // opener's RVH and proxy in the foo.com SiteInstance.
    EXPECT_TRUE(NavigateToURL(new_shell, cross_site_url));
    EXPECT_TRUE(opener_rvh->IsRenderViewLive());
    EXPECT_TRUE(opener_rfph->is_render_frame_proxy_live());
  }
  
  // Test that when a frame's opener is updated via window.open, the browser
  // process and the frame's proxies in other processes find out about the new
  // opener.  Open two popups in different processes, set one popup's opener to
  // the other popup, and ensure that the opener is updated in all processes.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, UpdateOpener) {
    StartEmbeddedServer();
    if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
      // Isolate "foo.com" so we are guaranteed it is placed in a different
      // process.
      IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                               {"foo.com"});
    }
  
    GURL main_url = embedded_test_server()->GetURL("/post_message.html");
    EXPECT_TRUE(NavigateToURL(shell(), main_url));
  
    // It is safe to obtain the root frame tree node here, as it doesn't change.
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
  
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_TRUE(orig_site_instance);
  
    // Open a cross-site popup named "foo" and a same-site popup named "bar".
    Shell* foo_shell = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo");
    EXPECT_TRUE(foo_shell);
    GURL foo_url(embedded_test_server()->GetURL("foo.com", "/post_message.html"));
    EXPECT_TRUE(NavigateToURLInSameBrowsingInstance(foo_shell, foo_url));
  
    GURL bar_url(embedded_test_server()->GetURL(
        "/frame_tree/page_with_post_message_frames.html"));
    Shell* bar_shell = OpenPopup(shell(), bar_url, "bar");
    EXPECT_TRUE(bar_shell);
  
    EXPECT_NE(shell()->web_contents()->GetSiteInstance()->GetProcess(),
              foo_shell->web_contents()->GetSiteInstance()->GetProcess());
    EXPECT_EQ(shell()->web_contents()->GetSiteInstance()->GetProcess(),
              bar_shell->web_contents()->GetSiteInstance()->GetProcess());
  
    // Initially, both popups' openers should point to main window.
    FrameTreeNode* foo_root =
        static_cast<WebContentsImpl*>(foo_shell->web_contents())
            ->GetFrameTree()
            ->root();
    FrameTreeNode* bar_root =
        static_cast<WebContentsImpl*>(bar_shell->web_contents())
            ->GetFrameTree()
            ->root();
    EXPECT_EQ(root, foo_root->opener());
    EXPECT_EQ(root, foo_root->original_opener());
    EXPECT_EQ(root, bar_root->opener());
    EXPECT_EQ(root, bar_root->original_opener());
  
    // From the bar process, use window.open to update foo's opener to point to
    // bar. This is allowed since bar is same-origin with foo's opener.  Use
    // window.open with an empty URL, which should return a reference to the
    // target frame without navigating it.
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        bar_shell,
        "window.domAutomationController.send(!!window.open('','foo'));",
        &success));
    EXPECT_TRUE(success);
    EXPECT_FALSE(foo_shell->web_contents()->IsLoading());
    EXPECT_EQ(foo_url, foo_root->current_url());
  
    // Check that updated opener propagated to the browser process.
    EXPECT_EQ(bar_root, foo_root->opener());
    EXPECT_EQ(root, foo_root->original_opener());
  
    // Check that foo's opener was updated in foo's process. Send a postMessage
    // to the opener and check that the right window (bar_shell) receives it.
    std::u16string expected_title = u"opener-msg";
    TitleWatcher title_watcher(bar_shell->web_contents(), expected_title);
    success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        foo_shell,
        "window.domAutomationController.send(postToOpener('opener-msg', '*'));",
        &success));
    EXPECT_TRUE(success);
    EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
  
    // Check that a non-null assignment to the opener doesn't change the opener
    // in the browser process.
    EXPECT_TRUE(ExecuteScript(foo_shell, "window.opener = window;"));
    EXPECT_EQ(bar_root, foo_root->opener());
    EXPECT_EQ(root, foo_root->original_opener());
  }
  
  // Tests that when a popup is opened, which is then navigated cross-process and
  // back, it can be still accessed through the original window reference in
  // JavaScript. See https://crbug.com/537657
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         PopupKeepsWindowReferenceCrossProcesAndBack) {
    StartEmbeddedServer();
  
    // Load a page with links that open in a new window.
    NavigateToPageWithLinks(shell());
  
    // Click a target=foo link to open a popup.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(clickSameSiteTargetedLink());",
        &success));
    EXPECT_TRUE(success);
    Shell* new_shell = new_shell_observer.GetShell();
    EXPECT_TRUE(new_shell->web_contents()->HasOpener());
  
    // Wait for the navigation in the popup to finish, if it hasn't.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
    EXPECT_EQ("/navigate_opener.html",
              new_shell->web_contents()->GetLastCommittedURL().path());
  
    // Capture the window reference, so we can check that accessing its location
    // works after navigating cross-process and back.
    GURL expected_url = new_shell->web_contents()->GetLastCommittedURL();
    EXPECT_TRUE(ExecuteScript(shell(), "saveWindowReference();"));
  
    // Now navigate the popup to a different site and then go back.
    EXPECT_TRUE(NavigateToURL(
        new_shell, embedded_test_server()->GetURL("foo.com", "/title1.html")));
    TestNavigationObserver back_nav_load_observer(new_shell->web_contents());
    new_shell->web_contents()->GetController().GoBack();
    back_nav_load_observer.Wait();
  
    // Check that the location.href window attribute is accessible and is correct.
    std::string result;
    EXPECT_TRUE(ExecuteScriptAndExtractString(
        shell(),
        "window.domAutomationController.send(getLastOpenedWindowLocation());",
        &result));
    EXPECT_EQ(expected_url.spec(), result);
  }
  
  // Tests that going back to the same SiteInstance as a pending RenderFrameHost
  // doesn't create a duplicate RenderFrameProxyHost. For example:
  // 1. Navigate to a page on the opener site - a.com
  // 2. Navigate to a page on site b.com
  // 3. Start a navigation to another page on a.com, but commit is delayed.
  // 4. Go back.
  // See https://crbug.com/541619.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         PopupPendingAndBackToSameSiteInstance) {
    StartEmbeddedServer();
    GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), main_url));
  
    // Open a popup to navigate.
    Shell* new_shell = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo");
    EXPECT_EQ(shell()->web_contents()->GetSiteInstance(),
              new_shell->web_contents()->GetSiteInstance());
  
    // Navigate the popup to a different site.
    EXPECT_TRUE(NavigateToURL(
        new_shell, embedded_test_server()->GetURL("b.com", "/title2.html")));
  
    // Navigate again to the original site, but to a page that will take a while
    // to commit.
    GURL same_site_url(embedded_test_server()->GetURL("a.com", "/title3.html"));
    TestNavigationManager stalled_navigation(new_shell->web_contents(),
                                             same_site_url);
    new_shell->LoadURL(same_site_url);
    EXPECT_TRUE(stalled_navigation.WaitForRequestStart());
  
    // Going back in history should work and the test should not crash.
    TestNavigationObserver back_nav_load_observer(new_shell->web_contents());
    new_shell->web_contents()->GetController().GoBack();
    back_nav_load_observer.Wait();
  }
  
  // Tests that navigating cross-process and reusing an existing RenderViewHost
  // (whose process has been killed/crashed) recreates properly the RenderView and
  // RenderFrameProxy on the renderer side.
  // See https://crbug.com/544271
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         RenderViewInitAfterProcessKill) {
    StartEmbeddedServer();
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
  
    // Open a popup to navigate.
    Shell* new_shell = OpenPopup(
        shell(), embedded_test_server()->GetURL("a.com", "/title2.html"), "foo");
    FrameTreeNode* popup_root =
        static_cast<WebContentsImpl*>(new_shell->web_contents())
            ->GetFrameTree()
            ->root();
    EXPECT_EQ(shell()->web_contents()->GetSiteInstance(),
              new_shell->web_contents()->GetSiteInstance());
  
    // Navigate the popup to a different site.
    EXPECT_TRUE(NavigateToURL(
        new_shell, embedded_test_server()->GetURL("b.com", "/title2.html")));
    EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
              new_shell->web_contents()->GetSiteInstance());
  
    // Kill the process hosting the popup.
    RenderProcessHost* process = popup_root->current_frame_host()->GetProcess();
    RenderProcessHostWatcher crash_observer(
        process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    process->Shutdown(0);
    crash_observer.Wait();
    EXPECT_FALSE(popup_root->current_frame_host()->IsRenderFrameLive());
    EXPECT_FALSE(
        popup_root->current_frame_host()->render_view_host()->IsRenderViewLive());
  
    // Navigate the main tab to the site of the popup. This will cause the
    // RenderView for b.com in the main tab to be recreated. If the issue
    // is not fixed, this will result in process crash and failing test.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("b.com", "/title3.html")));
  }
  
  // Ensure that we don't crash the renderer in CreateRenderView if a proxy goes
  // away between unload and the next navigation.  See https://crbug.com/581912.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         CreateRenderViewAfterProcessKillAndClosedProxy) {
    StartEmbeddedServer();
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
  
    // Give an initial page an unload handler that never completes.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
    EXPECT_TRUE(
        ExecuteScript(root, "window.onunload=function(e){ while(1); };\n"));
  
    // Open a popup in the same process.
    Shell* new_shell = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo");
    FrameTreeNode* popup_root =
        static_cast<WebContentsImpl*>(new_shell->web_contents())
            ->GetFrameTree()
            ->root();
    EXPECT_EQ(shell()->web_contents()->GetSiteInstance(),
              new_shell->web_contents()->GetSiteInstance());
  
    // Navigate the first tab to a different site, and only wait for commit, not
    // load stop.
    RenderFrameHostImpl* rfh_a = root->current_frame_host();
    rfh_a->DisableUnloadTimerForTesting();
    SiteInstanceImpl* site_instance_a = rfh_a->GetSiteInstance();
    TestFrameNavigationObserver commit_observer(root);
    shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title2.html"));
    commit_observer.WaitForCommit();
    EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
              new_shell->web_contents()->GetSiteInstance());
    EXPECT_TRUE(root->render_manager()->GetRenderFrameProxyHost(site_instance_a));
  
    // The previous RFH should still be pending deletion, as we wait for either
    // the mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame or a timeout.
    ASSERT_TRUE(rfh_a->IsRenderFrameLive());
    ASSERT_TRUE(rfh_a->IsPendingDeletion());
  
    // The corresponding RVH should still be referenced by the proxy and the old
    // frame.
    RenderViewHostImpl* rvh_a = rfh_a->render_view_host();
    EXPECT_FALSE(rvh_a->HasOneRef());
    EXPECT_TRUE(rvh_a->HasAtLeastOneRef());
  
    // Kill the old process.
    RenderProcessHost* process = rfh_a->GetProcess();
    RenderProcessHostWatcher crash_observer(
        process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    process->Shutdown(0);
    crash_observer.Wait();
    EXPECT_FALSE(popup_root->current_frame_host()->IsRenderFrameLive());
    // |rfh_a| is now deleted, thanks to the bug fix.
  
    // With |rfh_a| gone, the RVH should only be referenced by the (dead) proxy.
    EXPECT_TRUE(rvh_a->HasOneRef());
    EXPECT_TRUE(root->render_manager()->GetRenderFrameProxyHost(site_instance_a));
    EXPECT_FALSE(root->render_manager()
                     ->GetRenderFrameProxyHost(site_instance_a)
                     ->is_render_frame_proxy_live());
  
    // Close the popup so there is no proxy for a.com in the original tab.
    new_shell->Close();
    EXPECT_FALSE(
        root->render_manager()->GetRenderFrameProxyHost(site_instance_a));
  
    // This should delete the RVH as well.
    EXPECT_FALSE(root->frame_tree()->GetRenderViewHost(site_instance_a));
  
    // Go back in the main frame from b.com to a.com. In https://crbug.com/581912,
    // the browser process would crash here because there was no main frame
    // routing ID or proxy in RVHI::CreateRenderView.
    {
      TestNavigationObserver back_nav_load_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoBack();
      back_nav_load_observer.Wait();
    }
  }
  
  // Ensure that we don't crash in RenderViewImpl::Init if a proxy is created
  // after unload and before navigation.  See https://crbug.com/544755.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         RenderViewInitAfterNewProxyAndProcessKill) {
    StartEmbeddedServer();
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
  
    // Give an initial page an unload handler that never completes.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
    EXPECT_TRUE(
        ExecuteScript(root, "window.onunload=function(e){ while(1); };\n"));
  
    // With BackForwardCache, swapped out RenderFrameHost won't have a
    // replacement proxy as the document is stored in cache.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    // Navigate the tab to a different site, and only wait for commit, not load
    // stop.
    RenderFrameHostImpl* rfh_a = root->current_frame_host();
    rfh_a->DisableUnloadTimerForTesting();
    SiteInstanceImpl* site_instance_a = rfh_a->GetSiteInstance();
    TestFrameNavigationObserver commit_observer(root);
    shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title2.html"));
    commit_observer.WaitForCommit();
    EXPECT_NE(site_instance_a, shell()->web_contents()->GetSiteInstance());
  
    // The previous RFH should still be pending deletion, as we wait for either
    // the unload ACK or a timeout.
    ASSERT_TRUE(rfh_a->IsRenderFrameLive());
    ASSERT_TRUE(rfh_a->IsPendingDeletion());
  
    // When the previous RFH was unloaded, it should have still gotten a
    // replacement proxy even though it's the last active frame in the process.
    EXPECT_TRUE(root->render_manager()->GetRenderFrameProxyHost(site_instance_a));
  
    // Open a popup in the new B process.
    Shell* new_shell = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo");
    EXPECT_EQ(shell()->web_contents()->GetSiteInstance(),
              new_shell->web_contents()->GetSiteInstance());
  
    // Navigate the popup to the original site, but don't wait for commit (which
    // won't happen).  This should reuse the proxy in the original tab, which at
    // this point exists alongside the RFH pending deletion.
    new_shell->LoadURL(embedded_test_server()->GetURL("a.com", "/title2.html"));
    EXPECT_TRUE(root->render_manager()->GetRenderFrameProxyHost(site_instance_a));
  
    // Kill the old process.
    RenderProcessHost* process = rfh_a->GetProcess();
    RenderProcessHostWatcher crash_observer(
        process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    process->Shutdown(0);
    crash_observer.Wait();
    // |rfh_a| is now deleted, thanks to the bug fix.
  
    // Go back in the main frame from b.com to a.com.
    {
      TestNavigationObserver back_nav_load_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoBack();
      back_nav_load_observer.Wait();
    }
  
    // In https://crbug.com/581912, the renderer process would crash here because
    // there was a frame, view, and proxy (and is_swapped_out was true).
    EXPECT_EQ(site_instance_a, root->current_frame_host()->GetSiteInstance());
    EXPECT_TRUE(root->current_frame_host()->IsRenderFrameLive());
    EXPECT_TRUE(new_shell->web_contents()->GetMainFrame()->IsRenderFrameLive());
  }
  
  // Ensure that we use the same pending RenderFrameHost if a second navigation to
  // its site occurs before it commits.  Otherwise the renderer process will have
  // two competing pending RenderFrames that both try to swap with the same
  // RenderFrameProxy.  See https://crbug.com/545900.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ConsecutiveNavigationsToSite) {
    StartEmbeddedServer();
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
  
    // Open a popup and navigate it to b.com to keep the b.com process alive.
    Shell* new_shell = OpenPopup(shell(), GURL(url::kAboutBlankURL), "popup");
    EXPECT_TRUE(NavigateToURL(
        new_shell, embedded_test_server()->GetURL("b.com", "/title3.html")));
  
    // Start a cross-site navigation to the same site but don't commit.
    GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
    TestNavigationManager stalled_navigation(shell()->web_contents(),
                                             cross_site_url);
    shell()->LoadURL(cross_site_url);
    EXPECT_TRUE(stalled_navigation.WaitForResponse());
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    RenderFrameHostImpl* next_rfh =
        web_contents->GetRenderManagerForTesting()->speculative_frame_host();
    ASSERT_TRUE(next_rfh);
  
    // Navigate to the same new site and verify that we commit in the same RFH.
    GURL cross_site_url2(embedded_test_server()->GetURL("b.com", "/title2.html"));
    TestNavigationObserver navigation_observer(web_contents, 1);
    shell()->LoadURL(cross_site_url2);
    EXPECT_EQ(
        next_rfh,
        web_contents->GetRenderManagerForTesting()->speculative_frame_host());
    navigation_observer.Wait();
    EXPECT_EQ(cross_site_url2, web_contents->GetLastCommittedURL());
    EXPECT_EQ(next_rfh, web_contents->GetMainFrame());
    EXPECT_FALSE(
        web_contents->GetRenderManagerForTesting()->speculative_frame_host());
  }
  
  // Check that if a sandboxed subframe opens a cross-process popup such that the
  // popup's opener won't be set, the popup still inherits the subframe's sandbox
  // flags.  This matters for rel=noopener and rel=noreferrer links, as well as
  // for some situations in non-site-per-process mode where the popup would
  // normally maintain the opener, but loses it due to being placed in a new
  // process and not creating subframe proxies.  The latter might happen when
  // opening the default search provider site.  See https://crbug.com/576204.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         CrossProcessPopupInheritsSandboxFlagsWithNoOpener) {
    StartEmbeddedServer();
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
  
    GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), main_url));
  
    // Add a sandboxed about:blank iframe.
    {
      std::string script =
          "var frame = document.createElement('iframe');\n"
          "frame.sandbox = 'allow-scripts allow-popups';\n"
          "document.body.appendChild(frame);\n";
      EXPECT_TRUE(ExecuteScript(shell(), script));
    }
  
    // Navigate iframe to a page with target=_blank links, and rewrite the links
    // to point to valid cross-site URLs.
    GURL frame_url(
        embedded_test_server()->GetURL("a.com", "/click-noreferrer-links.html"));
    EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), frame_url));
    std::string script = "setOriginForLinks('http://b.com:" +
                         embedded_test_server()->base_url().port() + "/');";
    EXPECT_TRUE(ExecuteScript(root->child_at(0), script));
  
    // Helper to click on the 'rel=noreferrer target=_blank' and 'rel=noopener
    // target=_blank' links.  Checks that these links open a popup that ends up
    // in a new SiteInstance even without site-per-process and then verifies that
    // the popup is still sandboxed.
    auto click_link_and_verify_popup = [this,
                                        root](std::string link_opening_script) {
      ShellAddedObserver new_shell_observer;
      bool success = false;
      EXPECT_TRUE(ExecuteScriptAndExtractBool(
          root->child_at(0),
          "window.domAutomationController.send(" + link_opening_script + ")",
          &success));
      EXPECT_TRUE(success);
  
      Shell* new_shell = new_shell_observer.GetShell();
      EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
      EXPECT_NE(new_shell->web_contents()->GetSiteInstance(),
                shell()->web_contents()->GetSiteInstance());
  
      // Check that the popup is sandboxed by checking its self.origin, which
      // should be unique.
      std::string origin;
      EXPECT_TRUE(ExecuteScriptAndExtractString(
          new_shell, "domAutomationController.send(self.origin)", &origin));
      EXPECT_EQ("null", origin);
    };
  
    click_link_and_verify_popup("clickNoOpenerTargetBlankLink()");
    click_link_and_verify_popup("clickNoRefTargetBlankLink()");
  }
  
  // When two frames are same-origin but cross-process, they should behave as if
  // they are not same-origin and should not crash.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SameOriginFramesInDifferentProcesses) {
    StartEmbeddedServer();
  
    // Load a page with links that open in a new window.
    EXPECT_TRUE(NavigateToURL(
        shell(),
        embedded_test_server()->GetURL("a.com", "/click-noreferrer-links.html")));
  
    // Get the original SiteInstance for later comparison.
    scoped_refptr<SiteInstance> orig_site_instance(
        shell()->web_contents()->GetSiteInstance());
    EXPECT_NE(nullptr, orig_site_instance.get());
  
    // Test clicking a target=foo link.
    ShellAddedObserver new_shell_observer;
    bool success = false;
    EXPECT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send(clickSameSiteTargetedLink());"
        "saveWindowReference();",
        &success));
    EXPECT_TRUE(success);
    Shell* new_shell = new_shell_observer.GetShell();
  
    // Wait for the navigation in the new tab to finish, if it hasn't.
    EXPECT_TRUE(WaitForLoadStop(new_shell->web_contents()));
    EXPECT_EQ("/navigate_opener.html",
              new_shell->web_contents()->GetLastCommittedURL().path());
  
    // Do a cross-site navigation that winds up same-site. The same-site
    // navigation to a.com will commit in a different process than the original
    // a.com window.
    EXPECT_TRUE(NavigateToURL(
        new_shell,
        embedded_test_server()->GetURL("b.com", "/cross-site/a.com/title1.html"),
        /* expected_commit_url */
        embedded_test_server()->GetURL("a.com", "/title1.html")));
    // The SiteInstance for the navigation is determined after the redirect.
    // So both windows will actually be in the same process.
    EXPECT_EQ(shell()->web_contents()->GetSiteInstance(),
              new_shell->web_contents()->GetSiteInstance());
  
    std::string result;
    EXPECT_TRUE(ExecuteScriptAndExtractString(
        shell(),
        "window.domAutomationController.send((function() {\n"
        "  try {\n"
        "    return getLastOpenedWindowLocation();\n"
        "  } catch (e) {\n"
        "    return e.toString();\n"
        "  }\n"
        "})())",
        &result));
    EXPECT_THAT(result, ::testing::MatchesRegex("http://a.com:\\d+/title1.html"));
  }
  
  // Test coverage for attempts to open subframe links in new windows, to prevent
  // incorrect invariant checks.  See https://crbug.com/605055.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, CtrlClickSubframeLink) {
    StartEmbeddedServer();
  
    // Load a page with a subframe link.
    EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL(
                                           "/ctrl-click-subframe-link.html")));
  
    // Simulate a ctrl click on the link.  This won't actually create a new Shell
    // because Shell::OpenURLFromTab only supports CURRENT_TAB, but it's enough to
    // trigger the crash from https://crbug.com/605055.
    EXPECT_TRUE(ExecuteScript(
        shell(), "window.domAutomationController.send(ctrlClickLink());"));
  }
  
  // Ensure that we don't update the wrong NavigationEntry's title after an
  // ignored commit during a cross-process navigation.
  // See https://crbug.com/577449.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         UnloadPushStateOnCrossProcessNavigation) {
    // TODO(sreejakshetty): Replace 'unload' with 'pagehide' and reenable this
    // test for BackForwardCache.
    DisableBackForwardCache(content::BackForwardCache::TEST_USES_UNLOAD_EVENT);
  
    StartEmbeddedServer();
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
  
    // Give an initial page an unload handler that does a pushState, which will be
    // ignored by the browser process.  It then does a title update which is
    // meant for a NavigationEntry that will never be created.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("a.com", "/title2.html")));
    EXPECT_TRUE(ExecuteScript(root,
                              "window.onunload=function(e){"
                              "history.pushState({}, 'foo', 'foo');"
                              "document.title='foo'; };\n"));
    std::u16string title = web_contents->GetTitle();
    NavigationEntryImpl* entry = web_contents->GetController().GetEntryAtIndex(0);
  
    // Navigate the first tab to a different site and wait for the old process to
    // complete its unload handler and exit.
    RenderFrameHostImpl* rfh_a = root->current_frame_host();
    rfh_a->DisableUnloadTimerForTesting();
    RenderProcessHostWatcher exit_observer(
        rfh_a->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    TestNavigationObserver commit_observer(web_contents);
    shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html"));
    commit_observer.Wait();
    exit_observer.Wait();
  
    // Ensure the entry's title hasn't changed after the ignored commit.
    EXPECT_EQ(title, entry->GetTitle());
  }
  
  // Ensure that document hosted on file: URL can successfully execute pushState
  // with arbitrary origin, when universal access setting is enabled.
  // TODO(nasko): The test is disabled on Mac, since universal access from file
  // scheme behaves differently.  See also https://crbug.com/981018.
  #if defined(OS_MAC)
  #define MAYBE_EnsureUniversalAccessFromFileSchemeSucceeds \
    DISABLED_EnsureUniversalAccessFromFileSchemeSucceeds
  #else
  #define MAYBE_EnsureUniversalAccessFromFileSchemeSucceeds \
    EnsureUniversalAccessFromFileSchemeSucceeds
  #endif
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         MAYBE_EnsureUniversalAccessFromFileSchemeSucceeds) {
    StartEmbeddedServer();
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
  
    auto prefs = web_contents->GetOrCreateWebPreferences();
    prefs.allow_universal_access_from_file_urls = true;
    web_contents->SetWebPreferences(prefs);
  
    GURL file_url = GetTestUrl("", "title1.html");
    ASSERT_TRUE(NavigateToURL(shell(), file_url));
    EXPECT_EQ(1, web_contents->GetController().GetEntryCount());
    EXPECT_TRUE(ExecuteScript(
        root, "window.history.pushState({}, '', 'https://chromium.org');"));
    EXPECT_EQ(2, web_contents->GetController().GetEntryCount());
    EXPECT_TRUE(web_contents->GetMainFrame()->IsRenderFrameLive());
  }
  
  // Ensure that navigating back from a sad tab to an existing process works
  // correctly. See https://crbug.com/591984.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         NavigateBackToExistingProcessFromSadTab) {
    StartEmbeddedServer();
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
  
    // Open a popup and navigate it to b.com.
    Shell* popup = OpenPopup(
        shell(), embedded_test_server()->GetURL("a.com", "/title2.html"), "foo");
    EXPECT_TRUE(NavigateToURL(
        popup, embedded_test_server()->GetURL("b.com", "/title3.html")));
  
    // Kill the b.com process.
    RenderProcessHost* b_process =
        popup->web_contents()->GetMainFrame()->GetProcess();
    RenderProcessHostWatcher crash_observer(
        b_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    b_process->Shutdown(0);
    crash_observer.Wait();
  
    // The popup should now be showing the sad tab.  Main tab should not be.
    EXPECT_NE(base::TERMINATION_STATUS_STILL_RUNNING,
              popup->web_contents()->GetCrashedStatus());
    EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING,
              shell()->web_contents()->GetCrashedStatus());
  
    // Go back in the popup from b.com to a.com/title2.html.
    TestNavigationObserver back_observer(popup->web_contents());
    popup->web_contents()->GetController().GoBack();
    back_observer.Wait();
  
    // In the bug, after the back navigation the popup was still showing
    // the sad tab.  Ensure this is not the case.
    EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING,
              popup->web_contents()->GetCrashedStatus());
    EXPECT_TRUE(popup->web_contents()->GetMainFrame()->IsRenderFrameLive());
    EXPECT_EQ(popup->web_contents()->GetMainFrame()->GetSiteInstance(),
              shell()->web_contents()->GetMainFrame()->GetSiteInstance());
  }
  
  // Verify that GetLastCommittedOrigin() is correct for the full lifetime of a
  // RenderFrameHost, including when it's pending, current, and pending deletion.
  // This is checked both for main frames and subframes.
  // See https://crbug.com/590035.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, LastCommittedOrigin) {
    StartEmbeddedServer();
  
    // Disable the back-forward cache so that documents are always deleted when
    // navigating.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url_a));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
    RenderFrameHostImpl* rfh_a = root->current_frame_host();
    rfh_a->DisableUnloadTimerForTesting();
  
    EXPECT_EQ(url::Origin::Create(url_a), rfh_a->GetLastCommittedOrigin());
    EXPECT_EQ(rfh_a, web_contents->GetMainFrame());
  
    // Start a navigation to a b.com URL, and don't wait for commit.
    GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html"));
    TestFrameNavigationObserver commit_observer(root);
    RenderFrameDeletedObserver deleted_observer(rfh_a);
    shell()->LoadURL(url_b);
  
    // The speculative RFH shouln't have a last committed origin (the default
    // value is a unique origin). The current RFH shouldn't change its last
    // committed origin before commit.
    RenderFrameHostImpl* rfh_b = root->render_manager()->speculative_frame_host();
    EXPECT_EQ("null", rfh_b->GetLastCommittedOrigin().Serialize());
    EXPECT_EQ(url::Origin::Create(url_a), rfh_a->GetLastCommittedOrigin());
  
    // Verify that the last committed origin is set for the b.com RHF once it
    // commits.
    commit_observer.WaitForCommit();
    EXPECT_EQ(url::Origin::Create(url_b), rfh_b->GetLastCommittedOrigin());
    EXPECT_EQ(rfh_b, web_contents->GetMainFrame());
  
    // The old RFH should now be pending deletion.  Verify it still has correct
    // last committed origin.
    EXPECT_EQ(url::Origin::Create(url_a), rfh_a->GetLastCommittedOrigin());
    EXPECT_TRUE(rfh_a->IsPendingDeletion());
  
    // Wait for |rfh_a| to be deleted and double-check |rfh_b|'s origin.
    deleted_observer.WaitUntilDeleted();
    EXPECT_EQ(url::Origin::Create(url_b), rfh_b->GetLastCommittedOrigin());
  
    // Navigate to a same-origin page with an about:blank iframe.  The iframe
    // should also have a b.com origin.
    GURL url_b_with_frame(embedded_test_server()->GetURL(
        "b.com", "/navigation_controller/page_with_iframe.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url_b_with_frame));
    if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
      // If same-site ProactivelySwapBrowsingInstance or main-frame RenderDocument
      // is enabled, the navigation will result in a new RFH.
      EXPECT_NE(rfh_b, web_contents->GetMainFrame());
      rfh_b = web_contents->GetMainFrame();
    } else {
      EXPECT_EQ(rfh_b, web_contents->GetMainFrame());
    }
    EXPECT_EQ(url::Origin::Create(url_b), rfh_b->GetLastCommittedOrigin());
    FrameTreeNode* child = root->child_at(0);
    RenderFrameHostImpl* child_rfh_b = root->child_at(0)->current_frame_host();
    child_rfh_b->DisableUnloadTimerForTesting();
    EXPECT_EQ(url::Origin::Create(url_b), child_rfh_b->GetLastCommittedOrigin());
  
    // Navigate subframe to c.com.  Wait for commit but not full load, and then
    // verify the subframe's origin.
    GURL url_c(embedded_test_server()->GetURL("c.com", "/title3.html"));
    {
      TestFrameNavigationObserver commit_observer(root->child_at(0));
      EXPECT_TRUE(
          ExecuteScript(child, "location.href = '" + url_c.spec() + "';"));
      commit_observer.WaitForCommit();
    }
    EXPECT_EQ(url::Origin::Create(url_c),
              child->current_frame_host()->GetLastCommittedOrigin());
  
    // With OOPIFs, this navigation used a cross-process transfer.  Ensure that
    // the iframe's old RFH still has correct origin, even though it's pending
    // deletion.
    if (AreAllSitesIsolatedForTesting()) {
      EXPECT_TRUE(child_rfh_b->IsPendingDeletion());
      EXPECT_NE(child_rfh_b, child->current_frame_host());
      EXPECT_EQ(url::Origin::Create(url_b),
                child_rfh_b->GetLastCommittedOrigin());
    }
  }
  
  // Ensure that loading a page with cross-site coreferencing iframes does not
  // cause an infinite number of nested iframes to be created.
  // See https://crbug.com/650332.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, CoReferencingFrames) {
    // Load a page with a cross-site coreferencing iframe. "Coreferencing" here
    // refers to two separate pages that contain subframes with URLs to each
    // other.
    StartEmbeddedServer();
    GURL url_1(
        embedded_test_server()->GetURL("a.com", "/coreferencingframe_1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
  
    // The FrameTree contains two successful instances of each site plus an
    // unsuccessfully-navigated third instance of B with a blank URL.  When not in
    // strict SiteInstance mode, the FrameTreeVisualizer depicts all nodes as
    // referencing Site A because iframes are identified with their root site.
    if (AreStrictSiteInstancesEnabled()) {
      EXPECT_EQ(
          " Site A ------------ proxies for B\n"
          "   +--Site B ------- proxies for A\n"
          "        +--Site A -- proxies for B\n"
          "             +--Site B -- proxies for A\n"
          "                  +--Site B -- proxies for A\n"
          "Where A = http://a.com/\n"
          "      B = http://b.com/",
          DepictFrameTree(*root));
    } else {
      const GURL kExpectedSiteURL = AreDefaultSiteInstancesEnabled()
                                        ? SiteInstanceImpl::GetDefaultSiteURL()
                                        : GURL("http://a.com/");
      EXPECT_EQ(std::string(" Site A\n"
                            "   +--Site A\n"
                            "        +--Site A\n"
                            "             +--Site A\n"
                            "                  +--Site A\n"
                            "Where A = ") +
                    kExpectedSiteURL.spec(),
                DepictFrameTree(*root));
    }
    FrameTreeNode* bottom_child =
        root->child_at(0)->child_at(0)->child_at(0)->child_at(0);
    EXPECT_TRUE(bottom_child->current_url().is_empty());
    EXPECT_FALSE(bottom_child->has_committed_real_load());
  }
  
  // Ensures that nested subframes with the same URL but different fragments can
  // only be nested once.  See https://crbug.com/650332.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SelfReferencingFragmentFrames) {
    StartEmbeddedServer();
    GURL url(
        embedded_test_server()->GetURL("a.com", "/page_with_iframe.html#123"));
    EXPECT_TRUE(NavigateToURL(shell(), url));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
    FrameTreeNode* child = root->child_at(0);
  
    // ExecuteScript is used here and once more below because it is important to
    // use renderer-initiated navigations since browser-initiated navigations are
    // bypassed in the self-referencing navigation check.
    TestFrameNavigationObserver observer1(child);
    EXPECT_TRUE(
        ExecuteScript(child, "location.href = '" + url.spec() + "456" + "';"));
    observer1.Wait();
  
    FrameTreeNode* grandchild = child->child_at(0);
    GURL expected_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_EQ(expected_url, grandchild->current_url());
  
    // This navigation should be blocked.
    GURL blocked_url(embedded_test_server()->GetURL(
        "a.com", "/page_with_iframe.html#123456789"));
    TestNavigationManager manager(web_contents, blocked_url);
    EXPECT_TRUE(ExecuteScript(grandchild,
                              "location.href = '" + blocked_url.spec() + "';"));
    // Wait for WillStartRequest and verify that the request is aborted before
    // starting it.
    EXPECT_FALSE(manager.WaitForRequestStart());
    EXPECT_TRUE(WaitForLoadStop(web_contents));
  
    // The FrameTree contains two successful instances of the url plus an
    // unsuccessfully-navigated third instance with a blank URL.
    const GURL kExpectedSiteURL = AreDefaultSiteInstancesEnabled()
                                      ? SiteInstanceImpl::GetDefaultSiteURL()
                                      : GURL("http://a.com/");
    EXPECT_EQ(std::string(" Site A\n"
                          "   +--Site A\n"
                          "        +--Site A\n"
                          "Where A = ") +
                  kExpectedSiteURL.spec(),
              DepictFrameTree(*root));
  
    // The URL of the grandchild has not changed.
    EXPECT_EQ(expected_url, grandchild->current_url());
  }
  
  // Ensure that loading a page with a meta refresh iframe does not cause an
  // infinite number of nested iframes to be created.  This test loads a page with
  // an about:blank iframe where the page injects html containing a meta refresh
  // into the iframe.  This test then checks that this does not cause infinite
  // nested iframes to be created.  See https://crbug.com/527367.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SelfReferencingMetaRefreshFrames) {
    // Load a page with a blank iframe.
    StartEmbeddedServer();
    GURL url(embedded_test_server()->GetURL(
        "a.com", "/page_with_meta_refresh_frame.html"));
    NavigateToURLBlockUntilNavigationsComplete(shell(), url, 3);
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
  
    // The third navigation should fail and be cancelled, leaving a FrameTree with
    // a height of 2.
    const GURL kExpectedSiteURL = AreDefaultSiteInstancesEnabled()
                                      ? SiteInstanceImpl::GetDefaultSiteURL()
                                      : GURL("http://a.com/");
    EXPECT_EQ(std::string(" Site A\n"
                          "   +--Site A\n"
                          "        +--Site A\n"
                          "Where A = ") +
                  kExpectedSiteURL.spec(),
              DepictFrameTree(*root));
  
    EXPECT_EQ(GURL(url::kAboutBlankURL),
              root->child_at(0)->child_at(0)->current_url());
  
    EXPECT_FALSE(root->child_at(0)->child_at(0)->has_committed_real_load());
  }
  
  // Ensure that navigating a subframe to the same URL as its parent twice in a
  // row is not blocked by the self-reference check.
  // See https://crbug.com/650332.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SelfReferencingSameURLRenavigation) {
    StartEmbeddedServer();
    GURL first_url(
        embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
    GURL second_url(first_url.spec() + "#123");
    EXPECT_TRUE(NavigateToURL(shell(), first_url));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
    FrameTreeNode* child = root->child_at(0);
  
    TestFrameNavigationObserver observer1(child);
    EXPECT_TRUE(
        ExecuteScript(child, "location.href = '" + second_url.spec() + "';"));
    observer1.Wait();
  
    EXPECT_EQ(child->current_url(), second_url);
  
    TestFrameNavigationObserver observer2(child);
    // This navigation shouldn't be blocked. Blocking should only occur when more
    // than one ancestor has the same URL (excluding fragments), and the
    // navigating frame's current URL shouldn't count toward that.
    EXPECT_TRUE(
        ExecuteScript(child, "location.href = '" + first_url.spec() + "';"));
    observer2.Wait();
  
    EXPECT_EQ(child->current_url(), first_url);
  }
  
  // Ensures that POST requests bypass self-referential URL checks. See
  // https://crbug.com/710008.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         SelfReferencingFramesWithPOST) {
    StartEmbeddedServer();
    GURL url(embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
    FrameTreeNode* child = root->child_at(0);
  
    GURL child_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_EQ(url, root->current_url());
    EXPECT_EQ(child_url, child->current_url());
  
    // Navigate the child frame to the same URL as parent via POST.
    std::string script =
        "var f = document.createElement('form');\n"
        "f.method = 'POST';\n"
        "f.action = '/page_with_iframe.html';\n"
        "document.body.appendChild(f);\n"
        "f.submit();";
    {
      TestFrameNavigationObserver observer(child);
      EXPECT_TRUE(ExecuteScript(child, script));
      observer.Wait();
    }
  
    FrameTreeNode* grandchild = child->child_at(0);
    EXPECT_EQ(url, child->current_url());
    EXPECT_EQ(child_url, grandchild->current_url());
  
    // Now navigate the grandchild to the same URL as its two ancestors. This
    // should be allowed since it uses POST; it was blocked prior to
    // fixing https://crbug.com/710008.
    {
      TestFrameNavigationObserver observer(grandchild);
      EXPECT_TRUE(ExecuteScript(grandchild, script));
      observer.Wait();
    }
  
    EXPECT_EQ(url, grandchild->current_url());
    ASSERT_EQ(1U, grandchild->child_count());
    EXPECT_EQ(child_url, grandchild->child_at(0)->current_url());
  }
  
  // Ensures that we don't reset a speculative RFH if a JavaScript URL is loaded
  // while there's an ongoing cross-process navigation. See
  // https://crbug.com/793432.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         JavaScriptLoadDoesntResetSpeculativeRFH) {
    EXPECT_TRUE(embedded_test_server()->Start());
  
    GURL site1 = embedded_test_server()->GetURL("a.com", "/title1.html");
    GURL site2 = embedded_test_server()->GetURL("b.com", "/title2.html");
  
    EXPECT_TRUE(NavigateToURL(shell(), site1));
  
    TestNavigationManager cross_site_navigation(shell()->web_contents(), site2);
    shell()->LoadURL(site2);
    EXPECT_TRUE(cross_site_navigation.WaitForRequestStart());
  
    RenderFrameHostImpl* speculative_rfh =
        static_cast<WebContentsImpl*>(shell()->web_contents())
            ->GetFrameTree()
            ->root()
            ->render_manager()
            ->speculative_frame_host();
    CHECK(speculative_rfh);
    shell()->web_contents()->GetController().LoadURL(
        GURL("javascript:(0)"), Referrer(), ui::PAGE_TRANSITION_TYPED,
        std::string());
  
    cross_site_navigation.ResumeNavigation();
    // No crash means everything worked!
  }
  
  // Test that unrelated browsing contexts cannot find each other's windows,
  // even when they end up using the same renderer process (e.g. because of
  // hitting a process limit).  See also https://crbug.com/718489.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ProcessReuseVsBrowsingInstance) {
    // Set max renderers to 1 to force reusing a renderer process between two
    // unrelated tabs.
    RenderProcessHost::SetMaxRendererProcessCount(1);
  
    // Navigate 2 tabs to a web page (regular web pages can share renderers
    // among themselves without any restrictions, unlike extensions, apps, etc.).
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url1(embedded_test_server()->GetURL("/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url1));
    RenderFrameHost* tab1 = shell()->web_contents()->GetMainFrame();
    EXPECT_EQ(url1, tab1->GetLastCommittedURL());
    GURL url2(embedded_test_server()->GetURL("/title2.html"));
    Shell* shell2 = Shell::CreateNewWindow(
        shell()->web_contents()->GetBrowserContext(), url2, nullptr, gfx::Size());
    EXPECT_TRUE(NavigateToURL(shell2, url2));
    RenderFrameHost* tab2 = shell2->web_contents()->GetMainFrame();
    EXPECT_EQ(url2, tab2->GetLastCommittedURL());
  
    // Sanity-check test setup: 2 frames share a renderer process, but are not in
    // a related browsing instance.
    if (!AreAllSitesIsolatedForTesting())
      EXPECT_EQ(tab1->GetProcess(), tab2->GetProcess());
    EXPECT_FALSE(
        tab1->GetSiteInstance()->IsRelatedSiteInstance(tab2->GetSiteInstance()));
  
    // Name the 2 frames.
    EXPECT_TRUE(ExecuteScript(tab1, "window.name = 'tab1';"));
    EXPECT_TRUE(ExecuteScript(tab2, "window.name = 'tab2';"));
  
    // Verify that |tab1| cannot find named frames belonging to |tab2| (i.e. that
    // window.open will end up creating a new tab rather than returning the old
    // |tab2| tab).
    WebContentsAddedObserver new_contents_observer;
    std::string location_of_opened_window;
    EXPECT_TRUE(ExecuteScriptAndExtractString(
        tab1,
        "var w = window.open('', 'tab2');\n"
        "window.domAutomationController.send(w.location.href);",
        &location_of_opened_window));
    EXPECT_EQ(url::kAboutBlankURL, location_of_opened_window);
    EXPECT_TRUE(new_contents_observer.GetWebContents());
  }
  
  // Verify that cross-site main frame navigations will swap BrowsingInstances
  // for certain browser-initiated navigations, such as user typing the URL into
  // the address bar.  This helps avoid unneeded process sharing and should
  // happen even if the current frame has an opener.  See
  // https://crbug.com/803367.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         BrowserInitiatedNavigationsSwapBrowsingInstance) {
    ASSERT_TRUE(embedded_test_server()->Start());
  
    // Start with a page on a.com.
    GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), a_url));
    scoped_refptr<SiteInstance> a_site_instance(
        shell()->web_contents()->GetSiteInstance());
  
    // Open a popup for b.com. This should stay in the current BrowsingInstance.
    GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
    Shell* popup = OpenPopup(shell(), b_url, "foo");
    EXPECT_TRUE(WaitForLoadStop(popup->web_contents()));
    scoped_refptr<SiteInstance> b_site_instance(
        popup->web_contents()->GetSiteInstance());
    EXPECT_TRUE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
  
    // 1. Same-site browser-initiated navigations shouldn't swap BrowsingInstances
    //    or SiteInstances.
    EXPECT_TRUE(NavigateToURL(
        popup, embedded_test_server()->GetURL("b.com", "/title2.html")));
    EXPECT_EQ(b_site_instance, popup->web_contents()->GetSiteInstance());
  
    // 2. A cross-site browser-initiated navigation should swap BrowsingInstances,
    //    despite having an opener in the same site as the destination URL.
    EXPECT_TRUE(NavigateToURL(
        popup, embedded_test_server()->GetURL("a.com", "/title3.html")));
    EXPECT_NE(b_site_instance, popup->web_contents()->GetSiteInstance());
    EXPECT_NE(a_site_instance, popup->web_contents()->GetSiteInstance());
    EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(
        popup->web_contents()->GetSiteInstance()));
    EXPECT_FALSE(b_site_instance->IsRelatedSiteInstance(
        popup->web_contents()->GetSiteInstance()));
  
    auto transitions_forcing_browsing_instance_swap = {
        ui::PAGE_TRANSITION_AUTO_BOOKMARK, /* user clicking on a bookmark */
        ui::PAGE_TRANSITION_GENERATED,     /* search query */
        ui::PAGE_TRANSITION_KEYWORD, /* search within a site from address bar */
        ui::PAGE_TRANSITION_TYPED,   /* user typing URL into address bar */
    };
    auto transitions_not_forcing_browsing_instance_swap = {
        ui::PAGE_TRANSITION_LINK, /* user clicked on a link in the document */
        ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
        ui::PAGE_TRANSITION_FORM_SUBMIT,
        ui::PAGE_TRANSITION_KEYWORD_GENERATED,
        ui::PAGE_TRANSITION_RELOAD,
    };
    int current_site = 0;
    scoped_refptr<SiteInstance> curr_instance(
        popup->web_contents()->GetSiteInstance());
  
    // 3. Perform several cross-site browser-initiated navigations in the popup
    //    that do not force a BrowsingInstance swap.
    //
    // When ProactivelySwapBrowsingInstance is disabled, the BrowsingInstance
    // isn't swapped, even if it could be.
    //
    // When ProactivelySwapBrowsingInstance is enabled, the BrowsingInstance is
    // now swapped. It can be swapped, because (2) caused the popup to live in a
    // different BrowsingInstance. The window and the popup are no longer related.
    for (auto transition : transitions_not_forcing_browsing_instance_swap) {
      GURL cross_site_url(embedded_test_server()->GetURL(
          base::StringPrintf("site%d.com", current_site++), "/title1.html"));
      SCOPED_TRACE(base::StringPrintf(
          "... wrong BrowsingInstance for '%s' transition to %s",
          ui::PageTransitionGetCoreTransitionString(transition),
          cross_site_url.spec().c_str()));
  
      TestNavigationObserver observer(popup->web_contents());
      NavigationController::LoadURLParams params(cross_site_url);
      params.transition_type = transition;
      popup->web_contents()->GetController().LoadURLWithParams(params);
      observer.Wait();
  
      if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) {
        EXPECT_FALSE(curr_instance->IsRelatedSiteInstance(
            popup->web_contents()->GetSiteInstance()));
      } else {
        EXPECT_TRUE(curr_instance->IsRelatedSiteInstance(
            popup->web_contents()->GetSiteInstance()));
      }
    }
  
    // 4. Perform several cross-site browser-initiated navigations in the popup,
    //    all using page transitions that force BrowsingInstance swaps:
    for (auto transition : transitions_forcing_browsing_instance_swap) {
      GURL cross_site_url(embedded_test_server()->GetURL(
          base::StringPrintf("site%d.com", current_site++), "/title1.html"));
      scoped_refptr<SiteInstance> prev_instance(
          popup->web_contents()->GetSiteInstance());
      SCOPED_TRACE(base::StringPrintf(
          "... expected BrowsingInstance swap for '%s' transition to %s",
          ui::PageTransitionGetCoreTransitionString(transition),
          cross_site_url.spec().c_str()));
  
      TestNavigationObserver observer(popup->web_contents());
      NavigationController::LoadURLParams params(cross_site_url);
      params.transition_type = transition;
      popup->web_contents()->GetController().LoadURLWithParams(params);
      observer.Wait();
  
      // This should swap BrowsingInstances.
      scoped_refptr<SiteInstance> curr_instance(
          popup->web_contents()->GetSiteInstance());
      EXPECT_NE(a_site_instance, curr_instance);
      EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(curr_instance.get()));
      EXPECT_NE(prev_instance, curr_instance);
      EXPECT_FALSE(prev_instance->IsRelatedSiteInstance(curr_instance.get()));
    }
  }
  
  // Ensure that these two browser-initiated navigations:
  //   foo.com -> about:blank -> foo.com
  // stay in the same SiteInstance.  This isn't technically required for
  // correctness, but some tests (e.g., testEnsureHotFromScratch from
  // telemetry_unittests) currently depend on this behavior.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         NavigateToAndFromAboutBlank) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), foo_url));
    scoped_refptr<SiteInstance> site_instance(
        shell()->web_contents()->GetSiteInstance());
  
    // Navigate to about:blank from address bar.  This stays in the foo.com
    // SiteInstance.
    EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
    EXPECT_EQ(site_instance, shell()->web_contents()->GetSiteInstance());
  
    // Perform a browser-initiated navigation to foo.com.  This should also stay
    // in the original foo.com SiteInstance and BrowsingInstance.
    EXPECT_TRUE(NavigateToURL(shell(), foo_url));
    EXPECT_EQ(site_instance, shell()->web_contents()->GetSiteInstance());
  }
  
  // Check that with the following sequence of navigations:
  //   foo.com -(1)-> bar.com -(2)-> about:blank -(3)-> foo.com
  // where (1) is renderer-initiated and (2)+(3) are browser-initiated, the last
  // navigation goes back to the first SiteInstance without --site-per-process,
  // and to a new SiteInstance and BrowsingInstance with --site-per-process.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         NavigateToFooThenBarThenAboutBlankThenFoo) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), foo_url));
    scoped_refptr<SiteInstance> site_instance(
        shell()->web_contents()->GetSiteInstance());
  
    // Do a renderer-initiated navigation to bar.com, then navigate to
    // about:blank from address bar, then back to foo.com.
    GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html"));
  
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), bar_url));
    EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
    EXPECT_TRUE(NavigateToURL(shell(), foo_url));
  
    // This should again go back to the original foo.com SiteInstance without
    // --site-per-process, as in that case both the bar.com and
    // about:blank navigation will stay in the foo.com SiteInstance, and the
    // final navigation to foo.com will be considered same-site with the current
    // SiteInstance.
    //
    // With --site-per-process, bar.com should get its own SiteInstance, the
    // about:blank navigation will stay in it, and thus the final foo.com
    // navigation should be considered cross-site from the current SiteInstance.
    // Since this is a browser-initiated, cross-site navigation, it will swap
    // BrowsingInstances, and create a new foo.com SiteInstance, distinct from
    // the initial one.
    if (ExpectSameSiteInstance()) {
      EXPECT_EQ(site_instance, shell()->web_contents()->GetSiteInstance());
    } else {
      EXPECT_NE(site_instance, shell()->web_contents()->GetSiteInstance());
      EXPECT_FALSE(site_instance->IsRelatedSiteInstance(
          shell()->web_contents()->GetSiteInstance()));
      EXPECT_EQ(site_instance->GetSiteURL(),
                shell()->web_contents()->GetSiteInstance()->GetSiteURL());
    }
  }
  
  // Test to verify that navigations in the main frame, which result in an error
  // page, properly commit the error page in its own dedicated process.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationInMainFrame) {
    // This test is only valid if error page isolation is enabled.
    if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
      return;
  
    StartEmbeddedServer();
    GURL url(embedded_test_server()->GetURL("/title1.html"));
    GURL error_url(embedded_test_server()->GetURL("/empty.html"));
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(error_url);
    // Start with a successful navigation to a document.
    EXPECT_TRUE(NavigateToURL(shell(), url));
    scoped_refptr<SiteInstance> success_site_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
  
    // Browser-initiated navigation to an error page should result in changing the
    // SiteInstance and process.
    {
      NavigationHandleObserver observer(shell()->web_contents(), error_url);
      EXPECT_FALSE(NavigateToURL(shell(), error_url));
      EXPECT_TRUE(observer.is_error());
      EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.net_error_code());
  
      scoped_refptr<SiteInstance> error_site_instance =
          shell()->web_contents()->GetMainFrame()->GetSiteInstance();
      EXPECT_NE(success_site_instance, error_site_instance);
      if (CanSameSiteMainFrameNavigationsChangeSiteInstances()) {
        // When ProactivelySwapBrowsingInstance is enabled on same-site
        // navigations, the navigation above will result in a new
        // BrowsingInstance.
        EXPECT_FALSE(success_site_instance->IsRelatedSiteInstance(
            error_site_instance.get()));
      } else {
        EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
            error_site_instance.get()));
      }
      EXPECT_NE(success_site_instance->GetProcess()->GetID(),
                error_site_instance->GetProcess()->GetID());
      EXPECT_TRUE(HasErrorPageSiteInfo(error_site_instance.get()));
  
      // Verify that the error page process is locked to origin
      EXPECT_TRUE(HasErrorPageProcessLock(error_site_instance.get()));
      EXPECT_TRUE(
          IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
    }
  
    // Navigate successfully again to a document, then perform a
    // renderer-initiated navigation and verify it behaves the same way.
    EXPECT_TRUE(NavigateToURL(shell(), url));
    success_site_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
    EXPECT_FALSE(HasErrorPageProcessLock(success_site_instance.get()));
  
    {
      NavigationHandleObserver observer(shell()->web_contents(), error_url);
      TestFrameNavigationObserver frame_observer(shell()->web_contents());
      EXPECT_TRUE(ExecuteScript(shell()->web_contents(),
                                "location.href = '" + error_url.spec() + "';"));
      frame_observer.Wait();
      EXPECT_TRUE(observer.is_error());
      EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.net_error_code());
  
      scoped_refptr<SiteInstance> error_site_instance =
          shell()->web_contents()->GetMainFrame()->GetSiteInstance();
      EXPECT_NE(success_site_instance, error_site_instance);
      if (CanSameSiteMainFrameNavigationsChangeSiteInstances()) {
        // When ProactivelySwapBrowsingInstance is enabled on same-site
        // navigations, the navigation above will result in a new
        // BrowsingInstance.
        EXPECT_FALSE(success_site_instance->IsRelatedSiteInstance(
            error_site_instance.get()));
      } else {
        EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
            error_site_instance.get()));
      }
      EXPECT_NE(success_site_instance->GetProcess()->GetID(),
                error_site_instance->GetProcess()->GetID());
      EXPECT_TRUE(HasErrorPageSiteInfo(error_site_instance.get()));
  
      // Verify that the error page process is locked to origin
      EXPECT_TRUE(HasErrorPageProcessLock(error_site_instance.get()));
    }
  }
  
  // Test to verify that navigations in subframes, which result in an error
  // page, commit the error page in the same process and not in the dedicated
  // error page process.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationInChildFrame) {
    StartEmbeddedServer();
    GURL url(embedded_test_server()->GetURL("/page_with_iframe.html"));
    GURL error_url(embedded_test_server()->GetURL("/empty.html"));
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(error_url);
  
    // Start with a successful navigation to a document.
    EXPECT_TRUE(NavigateToURL(shell(), url));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
    FrameTreeNode* child = root->child_at(0);
  
    scoped_refptr<SiteInstance> success_site_instance =
        child->current_frame_host()->GetSiteInstance();
  
    NavigationHandleObserver observer(web_contents, error_url);
    TestFrameNavigationObserver frame_observer(child);
    EXPECT_TRUE(
        ExecuteScript(child, "location.href = '" + error_url.spec() + "';"));
    frame_observer.Wait();
  
    EXPECT_TRUE(observer.is_error());
    EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.net_error_code());
  
    scoped_refptr<SiteInstance> error_site_instance =
        child->current_frame_host()->GetSiteInstance();
    EXPECT_TRUE(IsExpectedSubframeErrorTransition(success_site_instance.get(),
                                                  error_site_instance.get()));
    EXPECT_TRUE(IsOriginOpaqueAndCompatibleWithURL(child, error_url));
  }
  
  // Test to verify that navigations in new window, which result in an error
  // page, commit the error page in the dedicated error page process and not in
  // the one for the destination site.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationInNewWindow) {
    // This test is only valid if error page isolation is enabled.
    if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
      return;
  
    StartEmbeddedServer();
    GURL error_url(embedded_test_server()->GetURL("/empty.html"));
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(error_url);
  
    // Start with a successful navigation to a document.
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
  
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    scoped_refptr<SiteInstance> main_site_instance =
        root->current_frame_host()->GetSiteInstance();
  
    Shell* new_shell = OpenPopup(shell(), error_url, "foo");
    EXPECT_TRUE(new_shell);
  
    scoped_refptr<SiteInstance> error_site_instance =
        new_shell->web_contents()->GetMainFrame()->GetSiteInstance();
    EXPECT_NE(main_site_instance, error_site_instance);
    EXPECT_TRUE(HasErrorPageSiteInfo(error_site_instance.get()));
  
    // Verify that the error page process is locked to origin
    EXPECT_TRUE(HasErrorPageProcessLock(error_site_instance.get()));
    EXPECT_TRUE(
        IsMainFrameOriginOpaqueAndCompatibleWithURL(new_shell, error_url));
  }
  
  // Test to verify that windows that are not part of the same
  // BrowsingInstance end up using the same error page process, even though
  // their SiteInstances are not related.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationInUnrelatedWindows) {
    // This test is only valid if error page isolation is enabled.
    if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
      return;
  
    StartEmbeddedServer();
    GURL error_url(embedded_test_server()->GetURL("/empty.html"));
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(error_url);
  
    // Navigate the main window to an error page and verify.
    {
      NavigationHandleObserver observer(shell()->web_contents(), error_url);
      EXPECT_FALSE(NavigateToURL(shell(), error_url));
      scoped_refptr<SiteInstance> error_site_instance =
          shell()->web_contents()->GetMainFrame()->GetSiteInstance();
      EXPECT_TRUE(observer.is_error());
      EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.net_error_code());
      EXPECT_TRUE(HasErrorPageSiteInfo(error_site_instance.get()));
      EXPECT_TRUE(
          IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
    }
  
    // Creat a new, unrelated, window, navigate it to an error page and
    // verify.
    Shell* new_shell = CreateBrowser();
    {
      NavigationHandleObserver observer(new_shell->web_contents(), error_url);
      EXPECT_FALSE(NavigateToURL(new_shell, error_url));
      scoped_refptr<SiteInstance> error_site_instance =
          new_shell->web_contents()->GetMainFrame()->GetSiteInstance();
      EXPECT_TRUE(observer.is_error());
      EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.net_error_code());
      EXPECT_TRUE(HasErrorPageSiteInfo(error_site_instance.get()));
      EXPECT_TRUE(
          IsMainFrameOriginOpaqueAndCompatibleWithURL(new_shell, error_url));
    }
  
    // Verify the two SiteInstanes are not related, but they end up using the
    // same underlying RenderProcessHost.
    EXPECT_FALSE(
        shell()->web_contents()->GetSiteInstance()->IsRelatedSiteInstance(
            new_shell->web_contents()->GetSiteInstance()));
    EXPECT_EQ(shell()->web_contents()->GetSiteInstance()->GetProcess(),
              new_shell->web_contents()->GetSiteInstance()->GetProcess());
  
    // Verify that the process is locked to origin
    EXPECT_TRUE(
        HasErrorPageProcessLock(shell()->web_contents()->GetSiteInstance()));
  }
  
  // Test to verify that reloading an error page once the error condition has
  // cleared up is successful and does not create a new navigation entry.
  // See https://crbug.com/840485.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, ErrorPageNavigationReload) {
    // This test is only valid if error page isolation is enabled.
    if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
      return;
  
    StartEmbeddedServer();
    GURL start_url(embedded_test_server()->GetURL("/title1.html"));
    GURL error_url(embedded_test_server()->GetURL("/empty.html"));
    GURL end_url(embedded_test_server()->GetURL("/title2.html"));
    NavigationControllerImpl& nav_controller =
        static_cast<NavigationControllerImpl&>(
            shell()->web_contents()->GetController());
  
    // Build session history with three entries, where the middle one will be
    // tested for successful and failed reloads. This allows checking whether
    // reload accidentally clears the forward session history if it is
    // incorrectly classified.
    EXPECT_TRUE(NavigateToURL(shell(), start_url));
    EXPECT_TRUE(NavigateToURL(shell(), error_url));
    EXPECT_TRUE(NavigateToURL(shell(), end_url));
    {
      TestNavigationObserver back_observer(shell()->web_contents());
      shell()->web_contents()->GetController().GoBack();
      back_observer.Wait();
      EXPECT_TRUE(back_observer.last_navigation_succeeded());
    }
    EXPECT_EQ(3, nav_controller.GetEntryCount());
    EXPECT_EQ(1, nav_controller.GetLastCommittedEntryIndex());
    EXPECT_EQ(error_url, shell()->web_contents()->GetLastCommittedURL());
  
    scoped_refptr<SiteInstance> success_site_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
    url::Origin expected_origin =
        shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin();
  
    EXPECT_EQ(url::Origin::Create(error_url), expected_origin);
  
    // Install an interceptor which will cause network failure for |error_url|,
    // reload the existing entry and verify.
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(error_url);
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
      reload_observer.Wait();
      EXPECT_FALSE(reload_observer.last_navigation_succeeded());
      // TODO(nasko): Investigate making a failing reload of a successful
      // navigation be classified as NEW_ENTRY instead, since with error page
      // isolation it involves a SiteInstance swap.
      EXPECT_EQ(NavigationType::NAVIGATION_TYPE_EXISTING_ENTRY,
                reload_observer.last_navigation_type());
    }
    EXPECT_EQ(3, nav_controller.GetEntryCount());
    EXPECT_EQ(1, nav_controller.GetLastCommittedEntryIndex());
    int process_id =
        shell()->web_contents()->GetMainFrame()->GetProcess()->GetID();
    EXPECT_TRUE(HasErrorPageProcessLock(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
  
    // Reload while it will still fail to ensure it stays in the same process.
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
      reload_observer.Wait();
      EXPECT_FALSE(reload_observer.last_navigation_succeeded());
      EXPECT_EQ(NavigationType::NAVIGATION_TYPE_EXISTING_ENTRY,
                reload_observer.last_navigation_type());
    }
    EXPECT_EQ(process_id,
              shell()->web_contents()->GetMainFrame()->GetProcess()->GetID());
    EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
  
    // Reload the error page after clearing the error condition, such that the
    // navigation is successful and verify that no new entry was added to
    // session history and forward history is not pruned.
    url_interceptor.reset();
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
      reload_observer.Wait();
      EXPECT_TRUE(reload_observer.last_navigation_succeeded());
      // The successful reload should be classified as a NEW_ENTRY navigation
      // with replacement, since it needs to stay at the same entry in session
      // history, but needs a new entry because of the change in SiteInstance.
      EXPECT_EQ(NavigationType::NAVIGATION_TYPE_NEW_ENTRY,
                reload_observer.last_navigation_type());
    }
    EXPECT_EQ(3, nav_controller.GetEntryCount());
    EXPECT_EQ(1, nav_controller.GetLastCommittedEntryIndex());
    EXPECT_FALSE(HasErrorPageSiteInfo(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_FALSE(HasErrorPageProcessLock(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_EQ(expected_origin,
              shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin());
  
    // Test the same scenario as above, but this time initiated by the
    // renderer process.
    url_interceptor = SetupRequestFailForURL(error_url);
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
      reload_observer.Wait();
      EXPECT_FALSE(reload_observer.last_navigation_succeeded());
      // TODO(nasko): Investigate making a failing reload of a successful
      // navigation be classified as NEW_ENTRY instead, since with error page
      // isolation it involves a SiteInstance swap.
      EXPECT_EQ(NavigationType::NAVIGATION_TYPE_EXISTING_ENTRY,
                reload_observer.last_navigation_type());
    }
    EXPECT_EQ(3, nav_controller.GetEntryCount());
    EXPECT_EQ(1, nav_controller.GetLastCommittedEntryIndex());
    EXPECT_TRUE(HasErrorPageSiteInfo(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_TRUE(HasErrorPageProcessLock(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
  
    url_interceptor.reset();
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      EXPECT_TRUE(ExecuteScript(shell(), "location.reload();"));
      reload_observer.Wait();
      EXPECT_TRUE(reload_observer.last_navigation_succeeded());
      // TODO(nasko): Investigate making renderer initiated reloads that change
      // SiteInstance be classified as NEW_ENTRY as well.
      EXPECT_EQ(NavigationType::NAVIGATION_TYPE_EXISTING_ENTRY,
                reload_observer.last_navigation_type());
    }
    EXPECT_EQ(3, nav_controller.GetEntryCount());
    EXPECT_EQ(1, nav_controller.GetLastCommittedEntryIndex());
    EXPECT_FALSE(HasErrorPageSiteInfo(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_FALSE(HasErrorPageProcessLock(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_EQ(expected_origin,
              shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin());
  }
  
  // Version of ErrorPageNavigationReload test that targets a subframe (because
  // subframes are currently [~2019Q1] not subject to error page isolation).
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationReload_InSubframe_NetworkError) {
    StartEmbeddedServer();
  
    // Isolating a.com helps more robustly exercise platforms without strict
    // site isolation - we want to ensure that enforcing |initiator_origin| in
    // BeginNavigation is compatible with process locks, even when only one of
    // the frames requires isolation.
    IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                             {"b.com"});
  
    // Start on a page with a main frame and a subframe.
    GURL page_url(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(b)"));
    GURL test_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), page_url));
    NavigationControllerImpl& nav_controller =
        static_cast<NavigationControllerImpl&>(
            shell()->web_contents()->GetController());
    // We start at 3, because IsolateOriginsForTesting is sneeking in extra two
    // navigations.
    EXPECT_EQ(3, nav_controller.GetEntryCount());
  
    // Grab child's site URL.
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    FrameTreeNode* child = root->child_at(0);
    GURL child_site_url =
        child->current_frame_host()->GetSiteInstance()->GetSiteURL();
  
    // Navigate the subframe to a URL that is cross-site from the main frame.
    {
      TestNavigationObserver nav_observer(shell()->web_contents());
      ASSERT_TRUE(ExecJs(child, JsReplace("window.location = $1", test_url)));
      nav_observer.Wait();
  
      EXPECT_TRUE(nav_observer.last_navigation_succeeded());
      EXPECT_EQ(4, nav_controller.GetEntryCount());
      EXPECT_EQ(test_url, child->current_frame_host()->GetLastCommittedURL());
      EXPECT_EQ(url::Origin::Create(test_url),
                child->current_frame_host()->GetLastCommittedOrigin());
      EXPECT_EQ(child_site_url,
                child->current_frame_host()->GetSiteInstance()->GetSiteURL());
    }
  
    // Reload the subframe while the network is down.
    {
      scoped_refptr<SiteInstance> success_site_instance =
          child->current_frame_host()->GetSiteInstance();
      std::unique_ptr<URLLoaderInterceptor> url_interceptor =
          SetupRequestFailForURL(test_url);
      TestNavigationObserver nav_observer(shell()->web_contents());
      ASSERT_TRUE(ExecJs(child, "window.location.reload()"));
      nav_observer.Wait();
  
      EXPECT_FALSE(nav_observer.last_navigation_succeeded());
      EXPECT_EQ(4, nav_controller.GetEntryCount());
      EXPECT_EQ(test_url, child->current_frame_host()->GetLastCommittedURL());
  
      // Error pages should commit in an opaque origin.
      EXPECT_TRUE(IsOriginOpaqueAndCompatibleWithURL(child, test_url));
  
      EXPECT_TRUE(IsExpectedSubframeErrorTransition(
          success_site_instance.get(),
          child->current_frame_host()->GetSiteInstance()));
    }
  
    // Reload the subframe after the network is restored.
    {
      TestNavigationObserver nav_observer(shell()->web_contents());
      // Note that some error pages actually do embed a button that will do an
      // equivalent of the renderer-initiated reload that is triggered below.
      ASSERT_TRUE(ExecJs(child, "window.location.reload()"));
      nav_observer.Wait();
  
      EXPECT_TRUE(nav_observer.last_navigation_succeeded());
      EXPECT_EQ(4, nav_controller.GetEntryCount());
      EXPECT_EQ(test_url, child->current_frame_host()->GetLastCommittedURL());
      EXPECT_EQ(url::Origin::Create(test_url),
                child->current_frame_host()->GetLastCommittedOrigin());
      EXPECT_EQ(child_site_url,
                child->current_frame_host()->GetSiteInstance()->GetSiteURL());
    }
  }
  
  // Version of ErrorPageNavigationReload test that targets a subframe (because
  // subframes are currently [~2019Q1] not subject to error page isolation).
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationReload_InSubframe_BlockedByClient) {
    StartEmbeddedServer();
  
    // Isolating a.com and b.com helps more robustly exercise platforms without
    // strict site isolation - we want to ensure that enforcing |initiator_origin|
    // in BeginNavigation is compatible with process locks, even when only some of
    // the frames requires isolation.
    IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                             {"a.com", "b.com"});
  
    // Start on a page with a same-site main frame and a subframe.
    GURL page_url(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(b,b)"));
    GURL test_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), page_url));
    NavigationControllerImpl& nav_controller =
        static_cast<NavigationControllerImpl&>(
            shell()->web_contents()->GetController());
    // We start at 3, because IsolateOriginsForTesting calls are sneeking in extra
    // two navigations.
    EXPECT_EQ(3, nav_controller.GetEntryCount());
  
    // Grab child's site URL.
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    FrameTreeNode* child1 = root->child_at(0);
    FrameTreeNode* child2 = root->child_at(1);
    GURL a_site_url = root->current_frame_host()->GetSiteInstance()->GetSiteURL();
    EXPECT_EQ("a.com", a_site_url.host());
    GURL b_site_url =
        child2->current_frame_host()->GetSiteInstance()->GetSiteURL();
    EXPECT_EQ("b.com", b_site_url.host());
  
    // Navigate the subframe to a cross-site URL, while blocking the request with
    // ERR_BLOCKED_BY_CLIENT.
    {
      // Name the first child, so it can be found and navigated by the other child
      // in the next test step below.
      ASSERT_TRUE(ExecJs(child1, "window.name = 'child1';"));
  
      // Have |child2| initiate navigation of |child1| - the navigation should
      // result in ERR_BLOCKED_BY_CLIENT (because of the throttle created below).
      content::TestNavigationThrottleInserter throttle_inserter(
          shell()->web_contents(),
          base::BindRepeating(&RequestBlockingNavigationThrottle::Create));
      const char kScriptTemplate[] = R"(
          var child1 = window.open('', 'child1');
          child1.location = $1;
      )";
      scoped_refptr<SiteInstance> initial_site_instance =
          child2->current_frame_host()->GetSiteInstance();
      TestNavigationObserver nav_observer(shell()->web_contents());
      ASSERT_TRUE(ExecJs(child2, JsReplace(kScriptTemplate, test_url)));
      nav_observer.Wait();
      EXPECT_FALSE(nav_observer.last_navigation_succeeded());
      EXPECT_EQ(4, nav_controller.GetEntryCount());
      EXPECT_EQ(test_url, child1->current_frame_host()->GetLastCommittedURL());
  
      // Error pages should commit in an opaque origin.
      EXPECT_TRUE(IsOriginOpaqueAndCompatibleWithURL(child1, test_url));
  
      // net::ERR_BLOCKED_BY_CLIENT errors in subframes should commit in the
      // the correct process based on whether isolation is enabled or not.
      EXPECT_TRUE(IsExpectedSubframeErrorTransition(
          initial_site_instance.get(),
          child1->current_frame_host()->GetSiteInstance()));
    }
  
    // Reload the subframe when no longer blocking the navigation.
    {
      TestNavigationObserver nav_observer(shell()->web_contents());
      // Note that some error pages actually do embed a button that will do an
      // equivalent of the renderer-initiated reload that is triggered below.
      ASSERT_TRUE(ExecJs(child1, "window.location.reload()"));
      nav_observer.Wait();
  
      EXPECT_TRUE(nav_observer.last_navigation_succeeded());
      EXPECT_EQ(4, nav_controller.GetEntryCount());
      EXPECT_EQ(test_url, child1->current_frame_host()->GetLastCommittedURL());
      EXPECT_EQ(url::Origin::Create(test_url),
                child1->current_frame_host()->GetLastCommittedOrigin());
  
      SiteInstanceImpl* child1_site_instance =
          child1->current_frame_host()->GetSiteInstance();
  
      GURL c_site_url = child1_site_instance->GetSiteURL();
      if (AreDefaultSiteInstancesEnabled()) {
        EXPECT_TRUE(child1_site_instance->IsDefaultSiteInstance());
      } else {
        EXPECT_EQ("c.com", c_site_url.host());
        EXPECT_EQ(test_url.host(), c_site_url.host());
      }
      EXPECT_NE(a_site_url, c_site_url);
      EXPECT_NE(b_site_url, c_site_url);
    }
  }
  
  // Make sure that reload works properly if it redirects to a different site than
  // the initial navigation.  The initial purpose of this test was to make sure
  // the corresponding unit test matches the actual product code behavior
  // (e.g. see NavigationControllerTest.Reload_GeneratesNewPage).
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ReloadRedirectsToDifferentCrossSitePage) {
    // Set-up http server handlers for |start_url|.
    //
    // |response1| is only required to make sure that |response2| doesn't kick-in
    // and intercept the http request until step 4.  Note that step 3 won't hit
    // the http server and therefore doesn't require handling here.
    auto response1 = std::make_unique<net::test_server::ControllableHttpResponse>(
        embedded_test_server(), "/title1.html");
    auto response2 = std::make_unique<net::test_server::ControllableHttpResponse>(
        embedded_test_server(), "/title1.html");
  
    // Start the server after all the required handlers have been already
    // registered.
    StartEmbeddedServer();
    NavigationControllerImpl& nav_controller =
        static_cast<NavigationControllerImpl&>(
            shell()->web_contents()->GetController());
  
    // URLs used in the test:
    // - Step1: Navigate to |start_url|
    // - Step2: Navigate to |second_url|
    // - Step3: Go back (to |start_url|)
    // - Step4: Reload (redirects to a different destination - |redirect_url|).
    GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
    GURL second_url(embedded_test_server()->GetURL("foo.com", "/title3.html"));
    GURL redirect_url(embedded_test_server()->GetURL("bar.com", "/title2.html"));
  
    // Test Step 1: Simple navigation that won't redirect and will just
    // successfully commit http://foo.com/title1.html.
    {
      // Navigate...
      TestNavigationObserver nav_observer(shell()->web_contents());
      shell()->LoadURL(start_url);
      response1->WaitForRequest();
      response1->Send(net::HTTP_OK, "text/html; charset=utf-8", "<p>Blah</p>");
      response1->Done();
      nav_observer.Wait();
  
      // Verify...
      EXPECT_TRUE(nav_observer.last_navigation_succeeded());
      EXPECT_EQ(1, nav_controller.GetEntryCount());
      EXPECT_EQ(0, nav_controller.GetLastCommittedEntryIndex());
      EXPECT_EQ(start_url, nav_controller.GetLastCommittedEntry()->GetURL());
    }
  
    // Test Step 2: Simple navigation that won't redirect and will just
    // successfully commit http://foo.com/title3.html.
    {
      // Navigate...
      EXPECT_TRUE(NavigateToURL(shell(), second_url));
  
      // Verify...
      EXPECT_EQ(2, nav_controller.GetEntryCount());
      EXPECT_EQ(1, nav_controller.GetLastCommittedEntryIndex());
      EXPECT_EQ(second_url, nav_controller.GetLastCommittedEntry()->GetURL());
    }
  
    // Test Step 3: Go back (to |start_url|).
    {
      // Navigate...
      TestNavigationObserver nav_observer(shell()->web_contents());
      nav_controller.GoBack();
      nav_observer.Wait();
  
      // Verify...
      EXPECT_TRUE(nav_observer.last_navigation_succeeded());
      EXPECT_EQ(2, nav_controller.GetEntryCount());
      EXPECT_EQ(0, nav_controller.GetLastCommittedEntryIndex());
      EXPECT_EQ(start_url, nav_controller.GetLastCommittedEntry()->GetURL());
    }
  
    // Test Step 4: Reload that will redirect to http://bar.com/title2.html (which
    // is a different, cross-site location compared to what the initial navigation
    // committed).
    {
      // Navigate...
      TestNavigationObserver reload_observer(shell()->web_contents());
      shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
      response2->WaitForRequest();
      response2->Send("HTTP/1.1 302 Moved Temporarily\n");
      response2->Send("Location: " + redirect_url.spec());
      response2->Done();
      reload_observer.Wait();
  
      // Verify that reload 1) replaced the current entry (rather than creating a
      // new entry) and 2) the tail of the history (e.g. the |second_url|
      // navigation) was preserved rather than truncated.
      EXPECT_TRUE(reload_observer.last_navigation_succeeded());
      EXPECT_EQ(2, nav_controller.GetEntryCount());
      EXPECT_EQ(0, nav_controller.GetLastCommittedEntryIndex());
      EXPECT_EQ(redirect_url, nav_controller.GetLastCommittedEntry()->GetURL());
      EXPECT_EQ(second_url, nav_controller.GetEntryAtIndex(1)->GetURL());
      if (AreAllSitesIsolatedForTesting()) {
        // The successful reload should be classified as a NEW_ENTRY navigation
        // with replacement, since it needs to stay at the same entry in session
        // history, but needs a new entry because of the change in SiteInstance.
        // (the same as expectations in the ErrorPageNavigationReload test above).
        EXPECT_EQ(NavigationType::NAVIGATION_TYPE_NEW_ENTRY,
                  reload_observer.last_navigation_type());
      } else {
        EXPECT_EQ(NavigationType::NAVIGATION_TYPE_EXISTING_ENTRY,
                  reload_observer.last_navigation_type());
      }
    }
  }
  
  // Test to verify that navigating away from an error page results in correct
  // change in SiteInstance.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationAfterError) {
    // This test is only valid if error page isolation is enabled.
    if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
      return;
  
    StartEmbeddedServer();
    GURL url(embedded_test_server()->GetURL("/title1.html"));
    GURL error_url(embedded_test_server()->GetURL("/empty.html"));
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(error_url);
    NavigationControllerImpl& nav_controller =
        static_cast<NavigationControllerImpl&>(
            shell()->web_contents()->GetController());
  
    // Start with a successful navigation to a document.
    EXPECT_TRUE(NavigateToURL(shell(), url));
    scoped_refptr<SiteInstance> success_site_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
    EXPECT_EQ(1, nav_controller.GetEntryCount());
  
    // Navigate to an url resulting in an error page.
    EXPECT_FALSE(NavigateToURL(shell(), error_url));
    EXPECT_TRUE(HasErrorPageSiteInfo(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_TRUE(HasErrorPageProcessLock(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_EQ(2, nav_controller.GetEntryCount());
    EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
  
    // Navigate again to the initial successful document, expecting a new
    // navigation and new SiteInstance. A new SiteInstance is expected here
    // because we are doing a cross-site navigation from an error page
    // to a site for |url|. This triggers the creation of a new BrowsingInstance
    // and therefore a new SiteInstance.
    EXPECT_TRUE(NavigateToURL(shell(), url));
    EXPECT_FALSE(HasErrorPageSiteInfo(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    if (AreDefaultSiteInstancesEnabled()) {
      // Verify that we get the default SiteInstance because the original URL does
      // not require a dedicated process.
      EXPECT_TRUE(static_cast<SiteInstanceImpl*>(
                      shell()->web_contents()->GetMainFrame()->GetSiteInstance())
                      ->IsDefaultSiteInstance());
    }
    EXPECT_EQ(
        success_site_instance->GetSiteURL(),
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()->GetSiteURL());
    EXPECT_NE(success_site_instance,
              shell()->web_contents()->GetMainFrame()->GetSiteInstance());
  
    EXPECT_EQ(3, nav_controller.GetEntryCount());
  
    // Repeat again using a renderer-initiated navigation for the successful one.
    EXPECT_FALSE(NavigateToURL(shell(), error_url));
    EXPECT_TRUE(HasErrorPageSiteInfo(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_TRUE(HasErrorPageProcessLock(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    EXPECT_EQ(4, nav_controller.GetEntryCount());
    {
      TestNavigationObserver observer(shell()->web_contents());
      EXPECT_TRUE(
          ExecuteScript(shell(), "location.href = '" + url.spec() + "';"));
      observer.Wait();
      EXPECT_TRUE(observer.last_navigation_succeeded());
    }
    EXPECT_EQ(5, nav_controller.GetEntryCount());
    EXPECT_FALSE(HasErrorPageSiteInfo(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
  }
  
  // Test to verify that when an error page is hit and its process is terminated,
  // a successful reload correctly commits in a different process.
  // See https://crbug.com/866549.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationReloadWithTerminatedProcess) {
    // This test is only valid if error page isolation is enabled.
    if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
      return;
  
    StartEmbeddedServer();
    GURL url(embedded_test_server()->GetURL("/title1.html"));
    GURL error_url(embedded_test_server()->GetURL("/empty.html"));
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(error_url);
    WebContents* web_contents = shell()->web_contents();
    NavigationControllerImpl& nav_controller =
        static_cast<NavigationControllerImpl&>(web_contents->GetController());
  
    // Start with a successful navigation to a document.
    EXPECT_TRUE(NavigateToURL(shell(), url));
    scoped_refptr<SiteInstance> success_site_instance =
        web_contents->GetMainFrame()->GetSiteInstance();
    EXPECT_EQ(1, nav_controller.GetEntryCount());
  
    // Navigate to an url resulting in an error page.
    EXPECT_FALSE(NavigateToURL(shell(), error_url));
    EXPECT_TRUE(
        HasErrorPageSiteInfo(web_contents->GetMainFrame()->GetSiteInstance()));
    EXPECT_TRUE(
        HasErrorPageProcessLock(web_contents->GetMainFrame()->GetSiteInstance()));
    EXPECT_EQ(2, nav_controller.GetEntryCount());
    EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
  
    // Terminate the renderer process.
    {
      RenderProcessHostWatcher termination_observer(
          web_contents->GetMainFrame()->GetProcess(),
          RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
      web_contents->GetMainFrame()->GetProcess()->Shutdown(0);
      termination_observer.Wait();
    }
  
    // Clear the interceptor so the navigation will succeed on reload.
    url_interceptor.reset();
  
    // Reload the URL and execute a Javascript statement to verify that the
    // renderer process is still around and responsive.
    TestNavigationObserver reload_observer(shell()->web_contents());
    nav_controller.Reload(ReloadType::NORMAL, false);
    reload_observer.Wait();
    EXPECT_TRUE(reload_observer.last_navigation_succeeded());
  
    std::string result;
    EXPECT_TRUE(ExecuteScriptAndExtractString(
        shell(), "window.domAutomationController.send(location.href);", &result));
    EXPECT_EQ(error_url.spec(), result);
  }
  
  // Test to verify that navigation to existing history entry, which results in
  // an error page, is correctly placed in the error page SiteInstance.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationHistoryNavigationFailure) {
    // This test is only valid if error page isolation is enabled.
    if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
      return;
  
    StartEmbeddedServer();
  
    // Perform successful navigations to two URLs to establish session history.
    GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url1));
  
    GURL url2(embedded_test_server()->GetURL("b.com", "/title2.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url2));
  
    WebContents* web_contents = shell()->web_contents();
    NavigationControllerImpl& nav_controller =
        static_cast<NavigationControllerImpl&>(web_contents->GetController());
  
    // There should be two NavigationEntries.
    EXPECT_EQ(2, nav_controller.GetEntryCount());
  
    // Ensure that previous document won't be restored from the BackForwardCache,
    // to force a network fetch, which would result in a network error.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    // Create an interceptor to cause navigations to url1 to fail and go back
    // in session history.
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(url1);
    TestNavigationObserver back_observer(web_contents);
    nav_controller.GoBack();
    back_observer.Wait();
    EXPECT_FALSE(back_observer.last_navigation_succeeded());
    EXPECT_EQ(2, nav_controller.GetEntryCount());
    EXPECT_EQ(0, nav_controller.GetLastCommittedEntryIndex());
  
    EXPECT_TRUE(
        HasErrorPageSiteInfo(web_contents->GetMainFrame()->GetSiteInstance()));
    EXPECT_TRUE(
        HasErrorPageProcessLock(web_contents->GetMainFrame()->GetSiteInstance()));
    EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), url1));
  }
  
  // Test to verify that a successful navigation to existing history entry,
  // which initially resulted in an error page, is correctly placed in a
  // SiteInstance different than the error page one.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationHistoryNavigationSuccess) {
    // This test is only valid if error page isolation is enabled.
    if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
      return;
  
    StartEmbeddedServer();
    WebContents* web_contents = shell()->web_contents();
  
    // Start with a successful navigation.
    GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url1));
  
    // Navigate to URL that results in an error page and verify its SiteInstance.
    GURL url2(embedded_test_server()->GetURL("b.com", "/title2.html"));
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(url2);
  
    EXPECT_FALSE(NavigateToURL(shell(), url2));
    EXPECT_TRUE(
        HasErrorPageSiteInfo(web_contents->GetMainFrame()->GetSiteInstance()));
    EXPECT_TRUE(
        HasErrorPageProcessLock(web_contents->GetMainFrame()->GetSiteInstance()));
    EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), url2));
  
    // There should be two NavigationEntries.
    NavigationControllerImpl& nav_controller =
        static_cast<NavigationControllerImpl&>(web_contents->GetController());
    EXPECT_EQ(2, nav_controller.GetEntryCount());
  
    // Navigate once more to create another session history entry.
    GURL url3(embedded_test_server()->GetURL("c.com", "/title3.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url3));
    EXPECT_EQ(3, nav_controller.GetEntryCount());
  
    // Navigate back, this time remove the interceptor so the navigation will
    // succeed.
    url_interceptor.reset();
    TestNavigationObserver back_observer(web_contents);
    nav_controller.GoBack();
    back_observer.Wait();
    EXPECT_TRUE(back_observer.last_navigation_succeeded());
    EXPECT_EQ(3, nav_controller.GetEntryCount());
    EXPECT_EQ(1, nav_controller.GetLastCommittedEntryIndex());
  
    EXPECT_FALSE(
        HasErrorPageSiteInfo(web_contents->GetMainFrame()->GetSiteInstance()));
    EXPECT_FALSE(
        HasErrorPageProcessLock(web_contents->GetMainFrame()->GetSiteInstance()));
  }
  
  // Test to verify that navigations to WebUI URL which results in an error
  // commits properly in the error page process and does not give it WebUI
  // bindings.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationToWebUIResourceWithError) {
    // This test is only valid if error page isolation is enabled.
    if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
      return;
  
    GURL webui_url = GetWebUIURL(kChromeUIGpuHost);
    GURL error_url(webui_url.Resolve("/foo"));
  
    // Navigate to the main WebUI URL and ensure it is successful.
    EXPECT_TRUE(NavigateToURL(shell(), webui_url));
  
    // Ensure that the subsequent navigation is blocked, resulting in an
    // error.
    TestNavigationThrottleInserter throttle_inserter(
        shell()->web_contents(),
        base::BindRepeating(&RequestBlockingNavigationThrottle::Create));
  
    // Navigate to a WebUI URL and verify the resulting error page process does
    // not get WebUI bindings.
    NavigationHandleObserver observer(shell()->web_contents(), error_url);
    EXPECT_FALSE(NavigateToURL(shell(), error_url));
    scoped_refptr<SiteInstance> error_site_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
    EXPECT_TRUE(observer.is_error());
    EXPECT_TRUE(HasErrorPageSiteInfo(error_site_instance.get()));
    EXPECT_TRUE(HasErrorPageProcessLock(error_site_instance.get()));
    EXPECT_FALSE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(
        error_site_instance->GetProcess()->GetID()));
    EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
  }
  
  // If a WebUI page leads to an error page and is then reloaded successfully from
  // its NavigationEntry, ensure that WebUI bindings are granted and that we don't
  // crash. See https://crbug.com/1046159.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ReloadWebUIErrorPageToValidWebUI) {
    // This test is only valid if error page isolation is enabled.
    if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(true))
      return;
  
    GURL webui_url = GetWebUIURL(kChromeUIGpuHost);
  
    // Temporarily insert throttles that will block all navigations, leading to
    // error pages instead.
    {
      TestNavigationThrottleInserter throttle_inserter(
          shell()->web_contents(),
          base::BindRepeating(&RequestBlockingNavigationThrottle::Create));
  
      // Navigate to a WebUI URL and verify the resulting error page process does
      // not get WebUI bindings.
      NavigationHandleObserver observer(shell()->web_contents(), webui_url);
      EXPECT_FALSE(NavigateToURL(shell(), webui_url));
      EXPECT_TRUE(observer.is_error());
      scoped_refptr<SiteInstance> error_site_instance =
          shell()->web_contents()->GetMainFrame()->GetSiteInstance();
      EXPECT_TRUE(HasErrorPageSiteInfo(error_site_instance.get()));
      EXPECT_FALSE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(
          error_site_instance->GetProcess()->GetID()));
    }
  
    // Once the throttles are no longer inserted into each navigation, reloading
    // the NavigationEntry should succeed and grant WebUI bindings.
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
      reload_observer.Wait();
      EXPECT_TRUE(reload_observer.last_navigation_succeeded());
    }
    scoped_refptr<SiteInstance> webui_site_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
    EXPECT_EQ(webui_url, webui_site_instance->GetSiteURL());
    EXPECT_TRUE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(
        webui_site_instance->GetProcess()->GetID()));
  
    // A second reload should work without crashing the browser process.
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
      reload_observer.Wait();
      EXPECT_TRUE(reload_observer.last_navigation_succeeded());
    }
  }
  // A custom ContentBrowserClient that simulates GetEffectiveURL() translation
  // for all URLs that are in the same page (including URL with refs).
  class PageEffectiveURLContentBrowserClient : public ContentBrowserClient {
   public:
    PageEffectiveURLContentBrowserClient(const GURL& url_to_modify,
                                         const GURL& url_to_return)
        : url_to_modify_(url_to_modify), url_to_return_(url_to_return) {}
    ~PageEffectiveURLContentBrowserClient() override = default;
  
   private:
    GURL GetEffectiveURL(BrowserContext* browser_context,
                         const GURL& url) override {
      if (url.EqualsIgnoringRef(url_to_modify_))
        return url_to_return_;
      return url;
    }
  
    GURL url_to_modify_;
    GURL url_to_return_;
  
    DISALLOW_COPY_AND_ASSIGN(PageEffectiveURLContentBrowserClient);
  };
  
  // Ensure that same-document navigations for URLs with effective URLs don't
  // incorrectly swap BrowsingInstance. See crbug.com/1073540.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         NavigationToSameDocumentWithEffectiveURL) {
    StartEmbeddedServer();
    const GURL page_url(embedded_test_server()->GetURL("/title1.html"));
    const GURL anchor_in_page_url(
        embedded_test_server()->GetURL("/title1.html#bar"));
    const GURL effective_url("http://foo.com");
    auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
    // The effective URL for |page_url| and |anchor_in_page_url| will be
    // |effective_url|.
    PageEffectiveURLContentBrowserClient modified_client(page_url, effective_url);
    ContentBrowserClient* regular_client =
        SetBrowserClientForTesting(&modified_client);
  
    // Make a navigation to |page_url|.
    EXPECT_TRUE(NavigateToURL(shell(), page_url));
    EXPECT_EQ(web_contents->GetLastCommittedURL(), page_url);
    scoped_refptr<SiteInstance> orig_site_instance =
        web_contents->GetMainFrame()->GetSiteInstance();
  
    // Navigate to #bar in the same document.
    EXPECT_TRUE(NavigateToURL(shell(), anchor_in_page_url));
    EXPECT_EQ(web_contents->GetLastCommittedURL(), anchor_in_page_url);
    // We should reuse the same SiteInstance.
    EXPECT_EQ(orig_site_instance,
              web_contents->GetMainFrame()->GetSiteInstance());
  
    // Set the browser client back to the regular client.
    SetBrowserClientForTesting(regular_client);
  }
  
  // A test ContentBrowserClient implementation which enforces
  // BrowsingInstance swap on every navigation. It is used to verify that
  // reloading of an error page to an URL that requires BrowsingInstance swap
  // works correctly.
  class BrowsingInstanceSwapContentBrowserClient
      : public TestContentBrowserClient {
   public:
    BrowsingInstanceSwapContentBrowserClient() = default;
  
    bool ShouldIsolateErrorPage(bool in_main_frame) override {
      return in_main_frame;
    }
  
    bool ShouldSwapBrowsingInstancesForNavigation(
        content::SiteInstance* site_instance,
        const GURL& current_effective_url,
        const GURL& destination_effective_url) override {
      return true;
    }
  
   private:
    DISALLOW_COPY_AND_ASSIGN(BrowsingInstanceSwapContentBrowserClient);
  };
  
  // Test to verify that reloading of an error page which resulted from a
  // navigation to an URL which requires a BrowsingInstance swap, correcly
  // reloads in the same SiteInstance for the error page.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ErrorPageNavigationReloadBrowsingInstanceSwap) {
    StartEmbeddedServer();
    GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL error_url(embedded_test_server()->GetURL("b.com", "/empty.html"));
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(error_url);
    NavigationControllerImpl& nav_controller =
        static_cast<NavigationControllerImpl&>(
            shell()->web_contents()->GetController());
  
    // Start with a successful navigation to a document and verify there is
    // only one entry in session history.
    EXPECT_TRUE(NavigateToURL(shell(), url));
    scoped_refptr<SiteInstance> success_site_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
    EXPECT_EQ(1, nav_controller.GetEntryCount());
  
    // Navigate to an url resulting in an error page and ensure a new entry
    // was added to session history.
    EXPECT_FALSE(NavigateToURL(shell(), error_url));
    EXPECT_EQ(2, nav_controller.GetEntryCount());
  
    scoped_refptr<SiteInstance> initial_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
    EXPECT_TRUE(HasErrorPageSiteInfo(initial_instance.get()));
    EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
    if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) {
      EXPECT_FALSE(
          success_site_instance->IsRelatedSiteInstance(initial_instance.get()));
    } else {
      EXPECT_TRUE(
          success_site_instance->IsRelatedSiteInstance(initial_instance.get()));
    }
  
    // Install a client forcing every navigation to swap BrowsingInstances.
    // TODO(https://crbug.com/1045524): Ensure for now that we are not swapping
    // pages on reloads for now even when content client tells us to.
    // Move client initialisation to a later point in the time after the
    // problem is fixed.
    BrowsingInstanceSwapContentBrowserClient content_browser_client;
    ContentBrowserClient* old_client =
        SetBrowserClientForTesting(&content_browser_client);
  
    // Reload of the error page that still results in an error should stay in
    // the same SiteInstance. Ensure this works for both browser-initiated
    // reloads and renderer-initiated ones.
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
      reload_observer.Wait();
      EXPECT_FALSE(reload_observer.last_navigation_succeeded());
      EXPECT_EQ(2, nav_controller.GetEntryCount());
      EXPECT_EQ(initial_instance,
                shell()->web_contents()->GetMainFrame()->GetSiteInstance());
      EXPECT_TRUE(
          IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
    }
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      EXPECT_TRUE(ExecuteScript(shell(), "location.reload();"));
      reload_observer.Wait();
      EXPECT_FALSE(reload_observer.last_navigation_succeeded());
      EXPECT_EQ(2, nav_controller.GetEntryCount());
      EXPECT_EQ(initial_instance,
                shell()->web_contents()->GetMainFrame()->GetSiteInstance());
      EXPECT_TRUE(
          IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
    }
  
    // Allow the navigation to succeed and ensure it swapped to a non-related
    // SiteInstance.
    url_interceptor.reset();
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      EXPECT_TRUE(ExecuteScript(shell(), "location.reload();"));
      reload_observer.Wait();
      EXPECT_TRUE(reload_observer.last_navigation_succeeded());
      EXPECT_EQ(2, nav_controller.GetEntryCount());
      EXPECT_FALSE(initial_instance->IsRelatedSiteInstance(
          shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
      EXPECT_FALSE(success_site_instance->IsRelatedSiteInstance(
          shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    }
  
    SetBrowserClientForTesting(old_client);
  }
  
  class ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest
      : public RenderFrameHostManagerTest {
   public:
    ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest() {
      std::map<std::string, std::string> parameters;
      parameters[kProactivelySwapBrowsingInstanceLevelParameterName] =
          "CrossSiteSwapProcess";
      feature_list_.InitAndEnableFeatureWithParameters(
          features::kProactivelySwapBrowsingInstance, parameters);
    }
  
    ~ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest() override =
        default;
  
   private:
    base::test::ScopedFeatureList feature_list_;
  };
  
  // Test to ensure that the error page navigation does not change
  // BrowsingInstances when window.open is present.
  IN_PROC_BROWSER_TEST_P(
      ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest,
      ErrorPageNavigationWithWindowOpenDoesNotChangeBrowsingInstance) {
    StartEmbeddedServer();
    GURL url(embedded_test_server()->GetURL("/title1.html"));
    GURL error_url(embedded_test_server()->GetURL("/empty.html"));
    std::unique_ptr<URLLoaderInterceptor> url_interceptor =
        SetupRequestFailForURL(error_url);
    NavigationControllerImpl& nav_controller =
        static_cast<NavigationControllerImpl&>(
            shell()->web_contents()->GetController());
  
    // Start with a successful navigation to a document and verify there is
    // only one entry in session history.
    EXPECT_TRUE(NavigateToURL(shell(), url));
    scoped_refptr<SiteInstance> success_site_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
    EXPECT_EQ(1, nav_controller.GetEntryCount());
  
    // Open a new window to ensure that we can't swap BrowsingInstances
    // as we have to preserve the scripting relationship.
    EXPECT_TRUE(OpenPopup(shell(), GURL(url::kAboutBlankURL), ""));
  
    // Navigate to an url resulting in an error page and ensure a new entry
    // was added to session history.
    EXPECT_FALSE(NavigateToURL(shell(), error_url));
    EXPECT_EQ(2, nav_controller.GetEntryCount());
  
    scoped_refptr<SiteInstance> initial_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
    EXPECT_TRUE(HasErrorPageSiteInfo(initial_instance.get()));
    EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
    EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
  
    // Reload of the error page that still results in an error should stay in
    // the related SiteInstance. Ensure this works for both browser-initiated
    // reloads and renderer-initiated ones.
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
      reload_observer.Wait();
      EXPECT_FALSE(reload_observer.last_navigation_succeeded());
      EXPECT_EQ(2, nav_controller.GetEntryCount());
      EXPECT_TRUE(
          IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
      EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
          shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    }
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      EXPECT_TRUE(ExecuteScript(shell(), "location.reload();"));
      reload_observer.Wait();
      EXPECT_FALSE(reload_observer.last_navigation_succeeded());
      EXPECT_EQ(2, nav_controller.GetEntryCount());
      EXPECT_TRUE(
          IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
      EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
          shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    }
  
    // Allow the navigation to succeed and ensure the new SiteInstance
    // stays related.
    url_interceptor.reset();
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      EXPECT_TRUE(ExecuteScript(shell(), "location.reload();"));
      reload_observer.Wait();
      EXPECT_TRUE(reload_observer.last_navigation_succeeded());
      EXPECT_EQ(2, nav_controller.GetEntryCount());
      EXPECT_TRUE(success_site_instance->IsRelatedSiteInstance(
          shell()->web_contents()->GetMainFrame()->GetSiteInstance()));
    }
  }
  
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest,
                         ReloadShouldNotChangeBrowsingInstance) {
    StartEmbeddedServer();
    GURL url(embedded_test_server()->GetURL("/title1.html"));
  
    // 1) Navigate to the page.
    EXPECT_TRUE(NavigateToURL(shell(), url));
    scoped_refptr<SiteInstance> site_instance =
        shell()->web_contents()->GetMainFrame()->GetSiteInstance();
  
    // 2) Reload page.
    shell()->web_contents()->GetMainFrame()->Reload();
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  
    // Ensure that we do not change BrowsingInstances for reload.
    // We should keep this even when we start swapping BrowsingInstances
    // for same-site navigations.
    EXPECT_EQ(site_instance,
              shell()->web_contents()->GetMainFrame()->GetSiteInstance());
  }
  
  class ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest
      : public RenderFrameHostManagerTest {
   public:
    ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest() {
      std::map<std::string, std::string> parameters;
      parameters[kProactivelySwapBrowsingInstanceLevelParameterName] =
          "CrossSiteReuseProcess";
      feature_list_.InitAndEnableFeatureWithParameters(
          features::kProactivelySwapBrowsingInstance, parameters);
    }
  
    ~ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest() override =
        default;
  
    void SetUpCommandLine(base::CommandLine* command_line) override {
      RenderFrameHostManagerTest::SetUpCommandLine(command_line);
      command_line->AppendSwitch(switches::kDisableSiteIsolation);
      if (AreAllSitesIsolatedForTesting()) {
        LOG(WARNING)
            << "This test should be run without strict site isolation. "
            << "It's going to fail when  --site-per-process is specified.";
      }
    }
  
   private:
    base::test::ScopedFeatureList feature_list_;
  };
  
  // ProactivelySwapBrowsingInstance makes us swap BrowsingInstances for
  // renderer-initiated navigations, which we normally would've kept in the same
  // BrowsingInstance as before - which means we can keep the old process because
  // we would've continued using that process before anyways.
  IN_PROC_BROWSER_TEST_P(
      ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
      RendererInitiatedCrossSiteNavigationReusesProcess) {
    if (AreAllSitesIsolatedForTesting())
      return;
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // Navigate to A.
    EXPECT_TRUE(NavigateToURL(shell(), a_url));
    scoped_refptr<SiteInstanceImpl> a_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // Navigate to B. The navigation is document/renderer initiated.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
    scoped_refptr<SiteInstanceImpl> b_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that A and B are in different BrowsingInstances but have the same
    // renderer process. When default SiteInstances are enabled, A and B are
    // both default SiteInstances of different BrowsingInstances.
    EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
    EXPECT_EQ(a_site_instance->GetProcess(), b_site_instance->GetProcess());
    EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
              a_site_instance->IsDefaultSiteInstance());
    EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
              b_site_instance->IsDefaultSiteInstance());
  }
  
  // Different from renderer-initiated cross-site navigations, browser-initiated
  // cross-site navigations do swap BrowsingInstances and processes without
  // ProactivelySwapBrowsingInstance. Because of that, we shouldn't reuse the
  // process for the new BrowsingInstance.
  IN_PROC_BROWSER_TEST_P(
      ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
      BrowserInitiatedCrossSiteNavigationDoesNotReuseProcess) {
    if (AreAllSitesIsolatedForTesting())
      return;
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // Navigate to A.
    EXPECT_TRUE(NavigateToURL(shell(), a_url));
    scoped_refptr<SiteInstanceImpl> a_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // Navigate to B. The navigation is browser initiated.
    EXPECT_TRUE(NavigateToURL(shell(), b_url));
    scoped_refptr<SiteInstanceImpl> b_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that A and B are in different BrowsingInstances and renderer
    // processes. When default SiteInstances are enabled, A and B are
    // both default SiteInstances of different BrowsingInstances.
    EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
    EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess());
    EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
              a_site_instance->IsDefaultSiteInstance());
    EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
              b_site_instance->IsDefaultSiteInstance());
  }
  
  // A test ContentBrowserClient implementation that enforce process-per-site mode
  // if |should_use_process_per_site_| is true. It is used to verify that we don't
  // reuse the current page's renderer process when navigating to sites that uses
  // process-per-site.
  class ProcessPerSiteContentBrowserClient : public TestContentBrowserClient {
   public:
    ProcessPerSiteContentBrowserClient() = default;
  
    void SetShouldUseProcessPerSite(bool should_use_process_per_site) {
      should_use_process_per_site_ = should_use_process_per_site;
    }
  
    bool ShouldUseProcessPerSite(BrowserContext* browser_context,
                                 const GURL& site_url) override {
      return should_use_process_per_site_;
    }
  
   private:
    bool should_use_process_per_site_ = false;
    DISALLOW_COPY_AND_ASSIGN(ProcessPerSiteContentBrowserClient);
  };
  
  // We should not reuse the current process on renderer-initiated navigations to
  // sites that needs to use process-per-site, and should create a new process for
  // the site if there isn't already a process for that site.
  IN_PROC_BROWSER_TEST_P(
      ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
      RendererInitiatedCrossSiteNavigationToProcessPerSiteURLCreatesNewProcess) {
    if (AreAllSitesIsolatedForTesting())
      return;
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
    GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    ProcessPerSiteContentBrowserClient content_browser_client;
    ContentBrowserClient* old_client =
        SetBrowserClientForTesting(&content_browser_client);
    // Navigate to A.
    EXPECT_TRUE(NavigateToURL(shell(), a_url));
    scoped_refptr<SiteInstanceImpl> a_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    RenderProcessHost* original_process = a_site_instance->GetProcess();
  
    // Navigate to B. The navigation is document/renderer initiated.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
    scoped_refptr<SiteInstanceImpl> b_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that A and B are in different BrowsingInstances but have the same
    // renderer process.
    EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
    EXPECT_EQ(b_site_instance->GetProcess(), original_process);
    EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
              a_site_instance->IsDefaultSiteInstance());
    EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
              b_site_instance->IsDefaultSiteInstance());
  
    // Make sure we will use process-per-site for C.
    // Note this is enforcing process-per-site for all sites, which is why we turn
    // it off right after the navigation to C. We might reconsider after
    // crbug.com/1062211 is fixed.
    content_browser_client.SetShouldUseProcessPerSite(true);
  
    // Navigate to C. The navigation is document/renderer initiated.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), c_url));
    scoped_refptr<SiteInstanceImpl> c_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that B and C are in different BrowsingInstances and renderer
    // processes.
    EXPECT_FALSE(b_site_instance->IsRelatedSiteInstance(c_site_instance.get()));
    EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
              c_site_instance->IsDefaultSiteInstance());
    EXPECT_NE(c_site_instance->GetProcess(), original_process);
    // C is using the process for C's site.
    EXPECT_EQ(c_site_instance->GetProcess(),
              RenderProcessHostImpl::GetSoleProcessHostForSite(
                  c_site_instance->GetIsolationContext(),
                  c_site_instance->GetSiteInfo()));
  
    // Make sure we will not use process-per-site for B.
    content_browser_client.SetShouldUseProcessPerSite(false);
  
    // Navigate to B again. The navigation is document/renderer initiated.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
    scoped_refptr<SiteInstanceImpl> b2_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    EXPECT_FALSE(b2_site_instance->IsRelatedSiteInstance(c_site_instance.get()));
    EXPECT_FALSE(b2_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
    EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
              b2_site_instance->IsDefaultSiteInstance());
    EXPECT_NE(b2_site_instance->GetProcess(), original_process);
    // B will reuse C's process here, even though C is process-per-site, because
    // neither of them require a dedicated process.
    EXPECT_EQ(b2_site_instance->GetProcess(), c_site_instance->GetProcess());
  
    SetBrowserClientForTesting(old_client);
  }
  
  // We should not reuse the current process on renderer-initiated navigations to
  // sites that needs to use process-per-site, and should use the sole process for
  // that site if it already exists.
  IN_PROC_BROWSER_TEST_P(
      ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
      RendererInitiatedCrossSiteNavigationToProcessPerSiteURLUsesProcessForSite) {
    if (AreAllSitesIsolatedForTesting())
      return;
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    ProcessPerSiteContentBrowserClient content_browser_client;
    ContentBrowserClient* old_client =
        SetBrowserClientForTesting(&content_browser_client);
  
    // Navigate to A.
    EXPECT_TRUE(NavigateToURL(shell(), a_url));
    scoped_refptr<SiteInstanceImpl> a_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    RenderProcessHost* original_process = a_site_instance->GetProcess();
  
    // Create a new process and set it as the sole process host for B.
    scoped_refptr<SiteInstanceImpl> placeholder_b_site_instance =
        SiteInstanceImpl::CreateForTesting(web_contents->GetBrowserContext(),
                                           b_url);
    RenderProcessHost* process_for_b =
        RenderProcessHostImpl::CreateRenderProcessHost(
            web_contents->GetBrowserContext(), placeholder_b_site_instance.get());
    RenderProcessHostImpl::RegisterSoleProcessHostForSite(
        process_for_b, placeholder_b_site_instance.get());
    EXPECT_EQ(process_for_b,
              RenderProcessHostImpl::GetSoleProcessHostForSite(
                  placeholder_b_site_instance->GetIsolationContext(),
                  placeholder_b_site_instance->GetSiteInfo()));
    // Make sure we will use process-per-site for B.
    content_browser_client.SetShouldUseProcessPerSite(true);
  
    // Navigate to B. The navigation is document/renderer initiated.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
    scoped_refptr<SiteInstanceImpl> b_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that A and B are in different BrowsingInstances but B should use the
    // sole process assigned to site B.
    EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
    EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
              a_site_instance->IsDefaultSiteInstance());
    EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
              b_site_instance->IsDefaultSiteInstance());
    EXPECT_NE(b_site_instance->GetProcess(), original_process);
    EXPECT_EQ(b_site_instance->GetProcess(), process_for_b);
    EXPECT_EQ(b_site_instance->GetProcess(),
              RenderProcessHostImpl::GetSoleProcessHostForSite(
                  b_site_instance->GetIsolationContext(),
                  b_site_instance->GetSiteInfo()));
  
    SetBrowserClientForTesting(old_client);
  }
  
  // We should not reuse the current process on renderer-initiated navigations to
  // sites that require a dedicated process.
  IN_PROC_BROWSER_TEST_P(
      ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
      NavigationToSiteThatRequiresDedicatedProcess) {
    if (AreAllSitesIsolatedForTesting())
      return;
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    // The client will make sure b.com require a dedicated process.
    EffectiveURLContentBrowserClient modified_client(
        b_url /* url_to_modify */, b_url, true /* requires_dedicated_process */);
    ContentBrowserClient* old_client =
        SetBrowserClientForTesting(&modified_client);
    // 1) Navigate to A.
    EXPECT_TRUE(NavigateToURL(shell(), a_url));
    scoped_refptr<SiteInstanceImpl> a_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    EXPECT_FALSE(a_site_instance->RequiresDedicatedProcess());
  
    // 2) Navigate cross-site to B. The navigation is document/renderer initiated.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
    scoped_refptr<SiteInstanceImpl> b_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    EXPECT_TRUE(b_site_instance->RequiresDedicatedProcess());
  
    // Check that A and B are in different BrowsingInstances and processes.
    EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
    EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess());
    SetBrowserClientForTesting(old_client);
  }
  
  // We should not reuse the current process on renderer-initiated navigations to
  // sites that require a dedicated process.
  IN_PROC_BROWSER_TEST_P(
      ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
      NavigationFromSiteThatRequiresDedicatedProcess) {
    if (AreAllSitesIsolatedForTesting())
      return;
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    // The client will make sure a.com require a dedicated process.
    EffectiveURLContentBrowserClient modified_client(
        a_url /* url_to_modify */, a_url, true /* requires_dedicated_process */);
    ContentBrowserClient* old_client =
        SetBrowserClientForTesting(&modified_client);
    // 1) Navigate to A.
    EXPECT_TRUE(NavigateToURL(shell(), a_url));
    scoped_refptr<SiteInstanceImpl> a_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    EXPECT_TRUE(a_site_instance->RequiresDedicatedProcess());
  
    // 2) Navigate cross-site to B. The navigation is document/renderer initiated.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
    scoped_refptr<SiteInstanceImpl> b_site_instance =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    EXPECT_FALSE(b_site_instance->RequiresDedicatedProcess());
  
    // Check that A and B are in different BrowsingInstances and processes.
    EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
    EXPECT_NE(a_site_instance->GetProcess(), b_site_instance->GetProcess());
    SetBrowserClientForTesting(old_client);
  }
  
  class ProactivelySwapBrowsingInstancesSameSiteTest
      : public RenderFrameHostManagerTest {
   public:
    ProactivelySwapBrowsingInstancesSameSiteTest() {
      std::map<std::string, std::string> parameters;
      parameters[kProactivelySwapBrowsingInstanceLevelParameterName] = "SameSite";
      feature_list_.InitAndEnableFeatureWithParameters(
          features::kProactivelySwapBrowsingInstance, parameters);
    }
  
    ~ProactivelySwapBrowsingInstancesSameSiteTest() override = default;
  
    void ExpectTotalCount(base::StringPiece name,
                          base::HistogramBase::Count count) {
      FetchHistogramsFromChildProcesses();
      histogram_tester_.ExpectTotalCount(name, count);
    }
  
    template <typename T>
    void ExpectBucketCount(base::StringPiece name,
                           T sample,
                           base::HistogramBase::Count expected_count) {
      FetchHistogramsFromChildProcesses();
      histogram_tester_.ExpectBucketCount(name, sample, expected_count);
    }
  
   protected:
    const char* kActionAfterPagehideHistogramName =
        "BackForwardCache.SameSite.ActionAfterPagehide2";
  
   private:
    base::test::ScopedFeatureList feature_list_;
    base::HistogramTester histogram_tester_;
  };
  
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         RendererInitiatedSameSiteNavigationReusesProcess) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/title2.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // Navigate to title2.html. The navigation is document/renderer initiated.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_2));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that title1.html and title2.html are in different BrowsingInstances
    // but have the same renderer process.
    EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
    EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
  }
  
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         BrowserInitiatedSameSiteNavigationReusesProcess) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/title2.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // 2) Navigate to title2.html. The navigation is browser initiated.
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that title1.html and title2.html are in different BrowsingInstances
    // but have the same renderer process.
    EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
    EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
  
    // 3) Do a back navigation to title1.html.
    shell()->web_contents()->GetController().GoBack();
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
    scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // We will reuse the SiteInstance and renderer process of |site_instance_1|.
    EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
    EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
              site_instance_1->GetProcess());
  }
  
  // Tests that navigations that started but haven't committed yet will be
  // overridden by navigations started later if both navigations created
  // speculative RFHs.
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         MultipleNavigationsStarted) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL a1_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL a2_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
    GURL b1_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
    GURL b2_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to A1.
    EXPECT_TRUE(NavigateToURL(shell(), a1_url));
    auto* a1_rfh = web_contents->GetMainFrame();
    FrameTreeNode* node = a1_rfh->frame_tree_node();
    scoped_refptr<SiteInstanceImpl> a1_site_instance =
        static_cast<SiteInstanceImpl*>(a1_rfh->GetSiteInstance());
  
    // 2) Start same-site navigation to A2 without committing.
    TestNavigationManager navigation_a2(shell()->web_contents(), a2_url);
    shell()->LoadURL(a2_url);
    EXPECT_TRUE(navigation_a2.WaitForRequestStart());
    // Verify that we're now navigating to |a2_url|.
    EXPECT_EQ(node->navigation_request()->GetURL(), a2_url);
    // We should have a speculative RFH for this navigation.
    RenderFrameHostImpl* a2_speculative_rfh =
        node->render_manager()->speculative_frame_host();
    EXPECT_TRUE(a2_speculative_rfh);
    EXPECT_NE(a1_rfh, a2_speculative_rfh);
    // The speculative RFH should use a different BrowsingInstance than the
    // current RFH.
    scoped_refptr<SiteInstanceImpl> a2_site_instance =
        static_cast<SiteInstanceImpl*>(a2_speculative_rfh->GetSiteInstance());
    EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(a2_site_instance.get()));
  
    // 3) Start cross-site navigation to B1 without committing.
    TestNavigationManager navigation_b1(shell()->web_contents(), b1_url);
    shell()->LoadURL(b1_url);
    EXPECT_TRUE(navigation_b1.WaitForRequestStart());
    // Verify that we're now navigating to |b1_url|.
    EXPECT_EQ(node->navigation_request()->GetURL(), b1_url);
    // We should have a speculative RFH for this navigation.
    RenderFrameHostImpl* b1_speculative_rfh =
        node->render_manager()->speculative_frame_host();
    EXPECT_TRUE(b1_speculative_rfh);
    EXPECT_NE(a1_rfh, b1_speculative_rfh);
    // The speculative RFH should use a different BrowsingInstance than the
    // current RFH.
    scoped_refptr<SiteInstanceImpl> b1_site_instance =
        static_cast<SiteInstanceImpl*>(b1_speculative_rfh->GetSiteInstance());
    EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(b1_site_instance.get()));
  
    // 4) Start same-site navigation to B2 without committing.
    TestNavigationManager navigation_b2(shell()->web_contents(), b2_url);
    shell()->LoadURL(b2_url);
    EXPECT_TRUE(navigation_b2.WaitForRequestStart());
    // Verify that we're now navigating to |b2_url|.
    EXPECT_EQ(node->navigation_request()->GetURL(), b2_url);
    // We should have a speculative RFH for this navigation.
    RenderFrameHostImpl* b2_speculative_rfh =
        node->render_manager()->speculative_frame_host();
    EXPECT_TRUE(b2_speculative_rfh);
    EXPECT_NE(a1_rfh, b2_speculative_rfh);
    // The speculative RFH should use a different BrowsingInstance than the
    // current RFH.
    scoped_refptr<SiteInstanceImpl> b2_site_instance =
        static_cast<SiteInstanceImpl*>(b2_speculative_rfh->GetSiteInstance());
    EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(b2_site_instance.get()));
  }
  
  // Tests history same-site process reuse:
  // 1. Visit A1, A2, B.
  // 2. Go back to A2 (should use new process).
  // 3. Go back to A1 (should reuse A2's process).
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         HistoryNavigationReusesProcess) {
    // This test expects a renderer process to eventually get deleted when we
    // navigate away from the page using it, which won't happen if the page is
    // kept alive in the back-forward cache.  So, we should disable back-forward
    // cache for this test.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/title2.html"));
    GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title3.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // 2) Navigate same-site to title2.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that title1.html and title2.html are in different BrowsingInstances
    // but have the same renderer process.
    EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
    EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
  
    // 3) Navigate cross-site to b.com/title3.html.
    RenderFrameDeletedObserver rfh_2_deleted_observer(
        web_contents->GetMainFrame());
    EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
    scoped_refptr<SiteInstanceImpl> site_instance_3 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Wait until the RFH for title2.html got deleted, and check that
    // title2.html and b.com/title3.html are in different BrowsingInstances and
    // renderer processes (We check this by checking whether |site_instance_2|
    // still has a process or not - if it's gone then that means
    // |site_instance_3| uses a different process).
    rfh_2_deleted_observer.WaitUntilDeleted();
    EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
    EXPECT_FALSE(site_instance_2->HasProcess());
  
    // 4) Do a back navigation to title2.html.
    RenderFrameDeletedObserver rfh_3_deleted_observer(
        web_contents->GetMainFrame());
    shell()->web_contents()->GetController().GoBack();
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
    scoped_refptr<SiteInstanceImpl> site_instance_2_history_nav =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // We should use different BrowsingInstances and processes after going back to
    // title2.html because it's a cross-site navigation.
    rfh_3_deleted_observer.WaitUntilDeleted();
    EXPECT_FALSE(site_instance_2_history_nav->IsRelatedSiteInstance(
        site_instance_3.get()));
    EXPECT_FALSE(site_instance_3->HasProcess());
  
    // 5) Do a back navigation to title1.html.
    shell()->web_contents()->GetController().GoBack();
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
    scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // We should use different BrowsingInstances for title1.html and title2.html,
    // but reuse the process (because in the original navigation, the BI change
    // was caused by proactive BI swap).
    EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
        site_instance_2_history_nav.get()));
    EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
    EXPECT_TRUE(site_instance_2_history_nav->HasProcess());
    EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
              site_instance_2_history_nav->GetProcess());
  }
  
  // Tests history same-site process reuse:
  // 1. Visit A1, A2, B.
  // 2. Go back two entries to A1 (should use new process).
  // 3. Go forward to A2 (should reuse A1's process).
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         HistoryNavigationReusesProcess_SkipSameSiteEntry) {
    // This test expects a renderer process to eventually get deleted when we
    // navigate away from the page using it, which won't happen if the page is
    // kept alive in the back-forward cache.  So, we should disable back-forward
    // cache for this test.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/title2.html"));
    GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title3.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // 2) Navigate same-site to title2.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that title1.html and title2.html are in different BrowsingInstances
    // but have the same renderer process.
    EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
    EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
  
    // 3) Navigate cross-site to b.com/title3.html.
    RenderFrameDeletedObserver rfh_2_deleted_observer(
        web_contents->GetMainFrame());
    EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
    scoped_refptr<SiteInstanceImpl> site_instance_3 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Wait until the RFH for title2.html got deleted, and check that
    // title2.html and b.com/title3.html are in different BrowsingInstances and
    // renderer processes (We check this by checking whether |site_instance_2|
    // still has a process or not - if it's gone then that means
    // |site_instance_3| uses a different process).
    rfh_2_deleted_observer.WaitUntilDeleted();
    EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
    EXPECT_FALSE(site_instance_2->HasProcess());
  
    // 4) Navigate back 2 entries to title1.html.
    RenderFrameDeletedObserver rfh_3_deleted_observer(
        web_contents->GetMainFrame());
    EXPECT_TRUE(ExecJs(shell(), "history.go(-2)"));
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
    scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // We should use different BrowsingInstances and processes after going back to
    // title2.html because it's a cross-site navigation.
    rfh_3_deleted_observer.WaitUntilDeleted();
    EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
        site_instance_3.get()));
    EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
    EXPECT_FALSE(site_instance_3->HasProcess());
  
    // 5) Navigate 1 entry forward to title2.html.
    EXPECT_TRUE(ExecJs(shell(), "history.go(1)"));
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
    scoped_refptr<SiteInstanceImpl> site_instance_2_history_nav =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // We should use different BrowsingInstances for title1.html and title2.html,
    // but reuse the process (because in the original navigation, the BI change
    // was caused by proactive BI swap).
    EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
        site_instance_2_history_nav.get()));
    EXPECT_EQ(site_instance_2_history_nav, site_instance_2);
    EXPECT_TRUE(site_instance_1_history_nav->HasProcess());
    EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
              site_instance_2_history_nav->GetProcess());
  }
  
  // Tests history same-site process reuse:
  // 1. Visit A1, B, A3.
  // 2. Go back two entries to A1 (should use A3's process).
  // 3. Go forward to B (should use new process).
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         HistoryNavigationReusesProcess_SkipCrossSiteEntry) {
    // This test expects a renderer process to eventually get deleted when we
    // navigate away from the page using it, which won't happen if the page is
    // kept alive in the back-forward cache.  So, we should disable back-forward
    // cache for this test.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
    GURL url_3(embedded_test_server()->GetURL("/title3.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    RenderFrameDeletedObserver rfh_1_deleted_observer(
        web_contents->GetMainFrame());
    // 2) Navigate cross-site to b.com/title2.html.
    EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that title1.html and b.com/title2.html are in different
    // BrowsingInstances and renderer processes (We check this by checking
    // whether |site_instance_1| still has a process or not - if it's gone then
    // that means |site_instance_2| uses a different process).
    rfh_1_deleted_observer.WaitUntilDeleted();
    EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
    EXPECT_FALSE(site_instance_1->HasProcess());
  
    // 3) Navigate cross-site to title3.html.
    RenderFrameDeletedObserver rfh_2_deleted_observer(
        web_contents->GetMainFrame());
    EXPECT_TRUE(NavigateToURL(shell(), url_3));
    scoped_refptr<SiteInstanceImpl> site_instance_3 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Wait until the RFH for title2.html got deleted, and check that
    // b.com/title2.html and title3.html are in different BrowsingInstances and
    // renderer processes (We check this by checking whether |site_instance_2|
    // still has a process or not - if it's gone then that means
    // |site_instance_3| uses a different process).
    rfh_2_deleted_observer.WaitUntilDeleted();
    EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
    EXPECT_FALSE(site_instance_2->HasProcess());
  
    // 4) Navigate back 2 entries from title3.html to title1.html.
    EXPECT_TRUE(ExecJs(shell(), "history.go(-2)"));
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
    scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // We should use different BrowsingInstances but reuse the process when going
    // back from title3.html to title1.html because it's a same-site history
    // navigation.
    EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
        site_instance_3.get()));
    EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
    EXPECT_TRUE(site_instance_3->HasProcess());
    EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
              site_instance_3->GetProcess());
  }
  
  // Tests history same-site process reuse:
  // 1. Visit A1 (which window.opens A2) then B.
  // 2. Visit A3, which should use a new process (can't use A2's process).
  // 2. Go back two entries to A1 (should use A2's process - the same process it
  // used originally).
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         HistoryNavigationReusesProcessThatIsStillAlive) {
    // This test expects a renderer process to eventually get deleted when we
    // navigate away from the page using it, which won't happen if the page is
    // kept alive in the back-forward cache.  So, we should disable back-forward
    // cache for this test.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_to_open(embedded_test_server()->GetURL("/empty.html"));
    GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
    GURL url_3(embedded_test_server()->GetURL("/title3.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    // 1) Navigate to title1.html and open a popup.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    OpenPopup(shell(), url_to_open, "foo");
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // 2) Navigate cross-site to b.com/title2.html.
    EXPECT_TRUE(NavigateToURL(shell(), cross_site_url));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that title1.html and b.com/title2.html are in different
    // BrowsingInstances and renderer processes. title1.html's process will still
    // be around because the window it opened earlier is still alive.
    EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
    EXPECT_TRUE(site_instance_1->HasProcess());
    EXPECT_NE(site_instance_1->GetProcess(), site_instance_2->GetProcess());
  
    // 3) Navigate cross-site to title3.html (same-site with title1.html).
    RenderFrameDeletedObserver rfh_2_deleted_observer(
        web_contents->GetMainFrame());
    EXPECT_TRUE(NavigateToURL(shell(), url_3));
    scoped_refptr<SiteInstanceImpl> site_instance_3 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Wait until the RFH for b.com/title2.html got deleted, and check that
    // b.com/title2.html and title3.html are in different BrowsingInstances and
    // renderer processes (We check this by checking whether |site_instance_2|
    // still has a process or not - if it's gone then that means
    // |site_instance_3| uses a different process).
    rfh_2_deleted_observer.WaitUntilDeleted();
    EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
    EXPECT_FALSE(site_instance_2->HasProcess());
    // Even though title1.html and title3.html are same-site, they should use
    // different processes.
    EXPECT_NE(site_instance_1->GetProcess(), site_instance_3->GetProcess());
  
    // 4) Navigate back 2 entries from title3.html to title1.html.
    RenderFrameDeletedObserver rfh_3_deleted_observer(
        web_contents->GetMainFrame());
    EXPECT_TRUE(ExecJs(shell(), "history.go(-2)"));
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
    scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // We should use different BrowsingInstances and not reuse the process when
    // going back from title3.html to title1.html because the original process
    // for title1.html is still around (also title3.html shouldn't be able to
    // script the window opened by title1.html).
    rfh_3_deleted_observer.WaitUntilDeleted();
    EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
        site_instance_3.get()));
    EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
    EXPECT_FALSE(site_instance_3->HasProcess());
  }
  
  class ProactivelySwapBrowsingInstancesSameSiteCoopTest
      : public ProactivelySwapBrowsingInstancesSameSiteTest {
   public:
    ProactivelySwapBrowsingInstancesSameSiteCoopTest()
        : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
      std::vector<base::Feature> features;
      feature_list_.InitWithFeatures(
          {
              network::features::kCrossOriginOpenerPolicy,
              network::features::kCrossOriginOpenerPolicyReporting,
              network::features::kCrossOriginIsolated,
          },
          {});
      base::CommandLine::ForCurrentProcess()->AppendSwitch(
          switches::kIgnoreCertificateErrors);
    }
  
    ~ProactivelySwapBrowsingInstancesSameSiteCoopTest() override = default;
  
    net::EmbeddedTestServer* https_server() { return &https_server_; }
  
   protected:
    void SetUpOnMainThread() override {
      host_resolver()->AddRule("*", "127.0.0.1");
      ASSERT_TRUE(embedded_test_server()->Start());
      https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
      SetupCrossSiteRedirector(https_server());
      net::test_server::RegisterDefaultHandlers(&https_server_);
      ASSERT_TRUE(https_server()->Start());
    }
  
    void SetUpCommandLine(base::CommandLine* command_line) override {
      ProactivelySwapBrowsingInstancesSameSiteTest::SetUpCommandLine(
          command_line);
      command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
    }
  
   private:
    base::test::ScopedFeatureList feature_list_;
    net::EmbeddedTestServer https_server_;
  };
  
  // Tests history same-site process reuse:
  // 1. Visit A1 (non-COOP), A2 (non-COOP, should reuse A1's process), A3 (uses
  // COOP + COEP, should use new process).
  // 2. Go back to A2 (should use new process).
  // 3. Go back to A1 (should reuse A2's process).
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteCoopTest,
                         HistoryNavigationReusesProcess_COOP) {
    // This test expects a renderer process to eventually get deleted when we
    // navigate away from the page using it, which won't happen if the page is
    // kept alive in the back-forward cache.  So, we should disable back-forward
    // cache for this test.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    GURL url_1(https_server()->GetURL("a.com", "/title1.html"));
    GURL url_2(https_server()->GetURL("a.com", "/title2.html"));
    GURL coop_url(
        https_server()->GetURL("a.com",
                               "/set-header?"
                               "Cross-Origin-Opener-Policy: same-origin&"
                               "Cross-Origin-Embedder-Policy: require-corp"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    EXPECT_EQ(web_contents->GetMainFrame()->cross_origin_opener_policy().value,
              network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone);
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // 2) Navigate same-site to title2.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
    EXPECT_EQ(web_contents->GetMainFrame()->cross_origin_opener_policy().value,
              network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone);
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that title1.html and title2.html are in different BrowsingInstances
    // but have the same renderer process.
    EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
    EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
  
    // 3) Navigate same-site to a crossOriginIsolated page (uses COOP+COEP).
    RenderFrameDeletedObserver rfh_2_deleted_observer(
        web_contents->GetMainFrame());
    EXPECT_TRUE(NavigateToURL(shell(), coop_url));
    EXPECT_EQ(web_contents->GetMainFrame()->cross_origin_opener_policy().value,
              network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep);
  
    // Wait until the RFH for title2.html got deleted, and check that
    // title2.html and title3.html are in different BrowsingInstances and
    // renderer processes (We check this by checking whether |site_instance_2|
    // still has a process or not - if it's gone then that means
    // |site_instance_3| uses a different process).
    rfh_2_deleted_observer.WaitUntilDeleted();
    scoped_refptr<SiteInstanceImpl> site_instance_3 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
    EXPECT_FALSE(site_instance_2->HasProcess());
    EXPECT_NE(site_instance_2->GetProcess(), site_instance_3->GetProcess());
  
    // 4) Do a back navigation to title2.html.
    RenderFrameDeletedObserver rfh_3_deleted_observer(
        web_contents->GetMainFrame());
    shell()->web_contents()->GetController().GoBack();
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
    scoped_refptr<SiteInstanceImpl> site_instance_2_history_nav =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // We should use different BrowsingInstances and processes after going back to
    // title2.html because it's transitioning from a crossOriginIsolated page
    // (COOP+COEP) to a non-crossOriginIsolated page, even though the two are
    // same-site.
    rfh_3_deleted_observer.WaitUntilDeleted();
    EXPECT_FALSE(site_instance_2_history_nav->IsRelatedSiteInstance(
        site_instance_3.get()));
    EXPECT_FALSE(site_instance_3->HasProcess());
  
    // 5) Do a back navigation to title1.html.
    shell()->web_contents()->GetController().GoBack();
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_1);
    scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // We should use different BrowsingInstances for title1.html and title2.html,
    // but reuse the process (because in the original navigation, the BI change
    // was caused by proactive BI swap).
    EXPECT_FALSE(site_instance_1_history_nav->IsRelatedSiteInstance(
        site_instance_2_history_nav.get()));
    EXPECT_EQ(site_instance_1_history_nav, site_instance_1);
    EXPECT_TRUE(site_instance_2_history_nav->HasProcess());
    EXPECT_EQ(site_instance_1_history_nav->GetProcess(),
              site_instance_2_history_nav->GetProcess());
  }
  
  // If the navigation is same-document or ends up using the same NavigationEntry
  // (e.g., enter in omnibox converted to a reload), we should not do a proactive
  // BrowsingInstance swap.
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         SameEntryAndSameDocumentNavigationDoesNotSwap) {
    ASSERT_TRUE(embedded_test_server()->Start());
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to title1.html#foo.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/title1.html#foo")));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // 2) Navigate from title1.html#foo to title1.html.
    // This is a same-document, different-entry navigation.
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that #1 and #2 are in the same SiteInstance.
    EXPECT_EQ(site_instance_1, site_instance_2);
  
    // 3) Navigate from title1.html to title1.html.
    // This is a different-document, same-entry navigation.
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
    scoped_refptr<SiteInstanceImpl> site_instance_3 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // We should keep the same SiteInstance again.
    EXPECT_EQ(site_instance_2, site_instance_3);
  
    // 4) Navigate from title1.html to title1.html#foo.
    // This is a same document navigation.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/title1.html#foo")));
    scoped_refptr<SiteInstanceImpl> site_instance_4 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // We should keep the same SiteInstance again.
    EXPECT_EQ(site_instance_3, site_instance_4);
  
    // 5) Navigate from title1.html#foo to title1.html#foo.
    // This is a different-document, same-entry navigation.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/title1.html#foo")));
    scoped_refptr<SiteInstanceImpl> site_instance_5 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // We should keep the same SiteInstance again.
    EXPECT_EQ(site_instance_4, site_instance_5);
  
    // 6) Navigate from title1.html#foo to title1.html#bar.
    // This is a same document navigation.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/title1.html#bar")));
    scoped_refptr<SiteInstanceImpl> site_instance_6 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // We should keep the same SiteInstance again.
    EXPECT_EQ(site_instance_5, site_instance_6);
  
    // 7) Do a history navigation from title1.html#bar to title1.html#foo.
    // This is a same-document, different-entry history navigation.
    shell()->web_contents()->GetController().GoBack();
    EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
    scoped_refptr<SiteInstanceImpl> site_instance_7 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // We should keep the same SiteInstance again.
    EXPECT_EQ(site_instance_6, site_instance_7);
  }
  
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         ReloadDoesNotSwap) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url(embedded_test_server()->GetURL("/title1.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
  
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // 2) Request a reload to happen when the controller becomes active (e.g.
    // after the renderer gets killed in background on Android).
    NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
        shell()->web_contents()->GetController());
    ASSERT_FALSE(controller.NeedsReload());
    controller.SetNeedsReload();
    ASSERT_TRUE(controller.NeedsReload());
  
    // Set the controller as active, triggering the requested reload.
    controller.SetActive(true);
    EXPECT_TRUE(WaitForLoadStop(web_contents));
    ASSERT_FALSE(controller.NeedsReload());
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // Check that we're still in the same SiteInstance.
    EXPECT_EQ(site_instance_1, site_instance_2);
  
    // 3) Trigger reload using Reload().
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
      reload_observer.Wait();
      EXPECT_TRUE(reload_observer.last_navigation_succeeded());
    }
    scoped_refptr<SiteInstanceImpl> site_instance_3 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // Check that we're still in the same SiteInstance.
    EXPECT_EQ(site_instance_2, site_instance_3);
  
    // 4) Trigger reload using location.reload().
    {
      TestNavigationObserver reload_observer(shell()->web_contents());
      EXPECT_TRUE(ExecuteScript(shell(), "location.reload();"));
      reload_observer.Wait();
      EXPECT_TRUE(reload_observer.last_navigation_succeeded());
    }
    scoped_refptr<SiteInstanceImpl> site_instance_4 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // Check that we're still in the same SiteInstance.
    EXPECT_EQ(site_instance_3, site_instance_4);
  
    // 5) Do a replaceState to another URL.
    {
      TestNavigationObserver observer(web_contents);
      std::string script = "history.replaceState({}, '', '/title2.html')";
      EXPECT_TRUE(ExecJs(root, script));
      observer.Wait();
    }
    scoped_refptr<SiteInstanceImpl> site_instance_5 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // Check that we're still in the same SiteInstance.
    EXPECT_EQ(site_instance_4, site_instance_5);
  
    // 6) Reload after a replaceState by simulating the user hitting Enter in the
    // omnibox without changing the URL.
    {
      TestNavigationObserver observer(web_contents);
      web_contents->GetController().LoadURL(web_contents->GetLastCommittedURL(),
                                            Referrer(), ui::PAGE_TRANSITION_LINK,
                                            std::string());
      observer.Wait();
    }
    scoped_refptr<SiteInstanceImpl> site_instance_6 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // Check that we're still in the same SiteInstance.
    EXPECT_EQ(site_instance_5, site_instance_6);
  }
  
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         SwapOnNavigationToPageThatRedirects) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/title2.html"));
    // This is a same-site URL, and will redirect to another same-site URL.
    GURL same_site_redirector_url(
        embedded_test_server()->GetURL("/server-redirect?" + url_2.spec()));
    GURL url_3(embedded_test_server()->GetURL("/title3.html"));
    // This is a cross-site URL, but will redirect to a same-site URL.
    GURL cross_site_redirector_url(embedded_test_server()->GetURL(
        "b.com", "/server-redirect?" + url_3.spec()));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // 2) Go to a same-site URL that will redirect us same-site to /title2.html.
    EXPECT_TRUE(NavigateToURL(shell(), same_site_redirector_url,
                              url_2 /* expected_commit_url */));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that we are using a different BrowsingInstance but still using the
    // same renderer process.
    EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
    EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
  
    // 3) Go to a cross-site URL that will redirect us same-site to /title3.html.
    // Note that we're using a renderer-initiated navigation here. If we do a
    // browser-initiated navigation, it will hit the case at crbug.com/1094147
    // where we can't reuse |url_2|'s process even though |url_3| is same-site.
    // TODO(crbug.com/1094147): Test with browser-initiated navigation too once
    // the issue is fixed.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), cross_site_redirector_url,
                                          url_3 /* expected_commit_url */));
    scoped_refptr<SiteInstanceImpl> site_instance_3 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that we are using a different BrowsingInstance but still using the
    // same renderer process.
    EXPECT_FALSE(site_instance_2->IsRelatedSiteInstance(site_instance_3.get()));
    EXPECT_EQ(site_instance_2->GetProcess(), site_instance_3->GetProcess());
  }
  
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         DoNotSwapWhenReplacingHistoryEntry) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/title2.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // 2) Do a location.replace() to title2.html.
    {
      TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
      EXPECT_TRUE(
          ExecJs(shell(), JsReplace("window.location.replace($1)", url_2)));
      navigation_observer.Wait();
      EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
      EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), url_2);
    }
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    EXPECT_EQ(site_instance_1, site_instance_2);
  }
  
  // When we do a same-document navigation from A to A#foo then a navigation that
  // does replacement (e.g., cross-process reload, or location.replace, or other
  // client redirects) such that B takes the place of A#foo, we can go back to A
  // with the back navigation. In this case, we might want to do a proactive BI
  // swap so that page A can be bfcached.
  // However, this test is currently disabled because we won't swap on any
  // navigation that will replace the current history entry.
  // TODO(rakina): Support this case.
  IN_PROC_BROWSER_TEST_P(
      ProactivelySwapBrowsingInstancesSameSiteTest,
      DISABLED_ShouldSwapWhenReplacingEntryWithSameDocumentPreviousEntry) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_1_anchor(embedded_test_server()->GetURL("/title1.html#foo"));
    GURL url_2(embedded_test_server()->GetURL("/title2.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // 2) Navigate same-document to title1.html#foo.
    EXPECT_TRUE(NavigateToURL(shell(), url_1_anchor));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    EXPECT_EQ(site_instance_1, site_instance_2);
  
    // 3) Do a location.replace() to title2.html.
    {
      TestNavigationObserver navigation_observer(web_contents, 1);
      EXPECT_TRUE(
          ExecJs(shell(), JsReplace("window.location.replace($1)", url_2)));
      navigation_observer.Wait();
      EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
      EXPECT_EQ(web_contents->GetLastCommittedURL(), url_2);
    }
    scoped_refptr<SiteInstanceImpl> site_instance_3 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    // We should swap BrowsingInstance here so that the page at url_1 (which is
    // now the previous history entry) can be bfcached.
    EXPECT_NE(site_instance_2, site_instance_3);
  
    // Assert that a back navigation will go to |url_1|.
    {
      TestNavigationObserver navigation_observer(web_contents);
      web_contents->GetController().GoBack();
      navigation_observer.Wait();
      EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
      EXPECT_EQ(web_contents->GetLastCommittedURL(), url_1);
    }
  }
  
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         DoNotSwapWhenRelatedContentsPresent) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/title2.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate and open a new window.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    OpenPopup(shell(), url_1, "foo");
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // 2) Navigate to title2.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
  
    // Check that title1.html and title2.html are using the same SiteInstance.
    EXPECT_EQ(site_instance_1, site_instance_2);
  }
  
  // We should reuse the current process on same-site navigations even if the
  // site requires a dedicated process (because we are still in the same site).
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         NavigationToSiteThatRequiresDedicatedProcess) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/title2.html"));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    // Make sure the site require a dedicated process.
    EffectiveURLContentBrowserClient modified_client(
        url_1 /* url_to_modify */, url_1, /* requires_dedicated_process */ true);
    ContentBrowserClient* old_client =
        SetBrowserClientForTesting(&modified_client);
  
    // 1) Navigate to A.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    scoped_refptr<SiteInstanceImpl> site_instance_1 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    EXPECT_TRUE(site_instance_1->RequiresDedicatedProcess());
  
    // 2) Navigate cross-site to B. The navigation is document/renderer initiated.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_2));
    scoped_refptr<SiteInstanceImpl> site_instance_2 =
        static_cast<SiteInstanceImpl*>(
            web_contents->GetMainFrame()->GetSiteInstance());
    EXPECT_TRUE(site_instance_2->RequiresDedicatedProcess());
  
    // Check that A and B are in different BrowsingInstances but reuse the same
    // process.
    EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get()));
    EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess());
    SetBrowserClientForTesting(old_client);
  }
  
  // Tests that pagehide handlers of the old RFH are run during the commit
  // of the new RFH when swapping RFH for same-site navigations due to proactive
  // BrowsingInstance swap.
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         PagehideRunsDuringCommit) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
    // Create a pagehide handler that sets item "pagehide_storage" in
    // localStorage.
    EXPECT_TRUE(ExecJs(
        main_frame_1,
        base::StringPrintf(R"(
              localStorage.setItem('pagehide_storage', 'not_dispatched');
              var dispatched_pagehide = false;
              window.onpagehide = function(e) {
                if (dispatched_pagehide) {
                  // We shouldn't dispatch pagehide more than once.
                  localStorage.setItem('pagehide_storage',
                    'dispatched_more_than_once');
                } else if (e.persisted != %s) {
                  localStorage.setItem('pagehide_storage', 'wrong_persisted');
                } else {
                  localStorage.setItem('pagehide_storage',
                    'dispatched_once');
                }
                dispatched_pagehide = true;
              })",
                           IsBackForwardCacheEnabled() ? "true" : "false")));
  
    // 2) Navigate to local_storage.html.
    RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
  
    // Check that title1.html and local_storage.html are in different RFHs.
    RenderFrameHostImpl* main_frame_2 = web_contents->GetMainFrame();
    EXPECT_NE(main_frame_1, main_frame_2);
  
    // Check that the value set by |main_frame_1|'s pagehide handler can be
    // accessed by |main_frame_2| at load time (the first time the new page runs
    // scripts), setting the |pagehide_storage_at_load| variable correctly.
    EXPECT_EQ("dispatched_once",
              EvalJs(main_frame_2, "pagehide_storage_at_load"));
  
    // Check that the value for 'pagehide_storage' stays the same after
    // |main_frame_2| finished loading (or |main_frame_1| deleted if bfcache is
    // not enabled).
    if (!IsBackForwardCacheEnabled())
      main_frame_1_deleted_observer.WaitUntilDeleted();
    EXPECT_EQ("dispatched_once",
              EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
  }
  
  // Tests that visibilitychange handlers of the old RFH are run during the commit
  // of the new RFH when swapping RFH for same-site navigations due to proactive
  // BrowsingInstance swap.
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         VisibilitychangeRunsDuringCommit) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
    // Create a visibilitychange handler that sets item "visibilitychange_storage"
    // localStorage.
    EXPECT_TRUE(ExecJs(main_frame_1, R"(
              localStorage.setItem('visibilitychange_storage', 'not_dispatched');
              var dispatched_visibilitychange = false;
              document.onvisibilitychange = function(e) {
                if (dispatched_visibilitychange) {
                  // We shouldn't dispatch visibilitychange more than once.
                  localStorage.setItem('visibilitychange_storage',
                    'dispatched_more_than_once');
                } else if (document.visibilityState != 'hidden') {
                  // We should dispatch the event when the visibilityState is
                  // 'hidden'.
                  localStorage.setItem('visibilitychange_storage', 'not_hidden');
                } else {
                  localStorage.setItem('visibilitychange_storage',
                    'dispatched_once');
                }
                dispatched_visibilitychange = true;
              })"));
  
    // 2) Navigate to local_storage.html.
    RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
  
    // Check that title1.html and local_storage.html are in different RFHs.
    RenderFrameHostImpl* main_frame_2 = web_contents->GetMainFrame();
    EXPECT_NE(main_frame_1, main_frame_2);
  
    // Check that the value set by |main_frame_1|'s pagehide handler can be
    // accessed by |main_frame_2| at load time (the first time the new page runs
    // scripts), setting the |visibilitychange_storage_at_load| variable
    // correctly.
    EXPECT_EQ("dispatched_once",
              EvalJs(main_frame_2, "visibilitychange_storage_at_load"));
  
    // Check that the value for 'visibilitychange_storage' stays the same after
    // |main_frame_2| finished loading (or |main_frame_1| got deleted, if bfcache
    // is not enabled).
    if (!IsBackForwardCacheEnabled())
      main_frame_1_deleted_observer.WaitUntilDeleted();
    EXPECT_EQ(
        "dispatched_once",
        EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
  }
  
  // Tests that unload handlers of the old RFH are run during commit of the new
  // RFH when swapping RFH for same-site navigations due to proactive
  // BrowsingInstance swap.
  // TODO(crbug.com/1110744): support this.
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         DISABLED_UnloadRunsDuringCommit) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
    // Create an unload handler that sets item "unload_storage" in localStorage.
    EXPECT_TRUE(ExecJs(main_frame_1, R"(
              localStorage.setItem('unload_storage', 'not_dispatched');
              var dispatched_unload = false;
              window.onunload = function(e) {
                if (dispatched_unload) {
                  // We shouldn't dispatch unload more than once.
                  localStorage.setItem('unload_storage',
                    'dispatched_more_than_once');
                } else {
                  localStorage.setItem('unload_storage', 'dispatched_once');
                }
                dispatched_unload = true;
              };)"));
  
    // 2) Navigate to local_storage.html.
    RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
  
    // Check that title1.html and local_storage.html are in different RFHs.
    RenderFrameHostImpl* main_frame_2 = web_contents->GetMainFrame();
    EXPECT_NE(main_frame_1, main_frame_2);
  
    // Check that the value set by |main_frame_1|'s unload handler can be
    // accessed by |main_frame_2| at load time (the first time the new page runs
    // scripts), setting the |unload_storage_at_load| variable correctly.
    EXPECT_EQ("dispatched_once", EvalJs(main_frame_2, "unload_storage_at_load"));
  
    // Check that the value for 'unload_storage' stays the same after
    // |main_frame_2| finished loading (or |main_frame_1| got deleted, if bfcache
    // is not enabled).
    if (!IsBackForwardCacheEnabled())
      main_frame_1_deleted_observer.WaitUntilDeleted();
    EXPECT_EQ("dispatched_once",
              EvalJs(main_frame_2, "localStorage.getItem('unload_storage')"));
  }
  
  // Tests that pagehide and visibilitychange handlers of a subframe in the old
  // page are run during the commit of a new main RFH when swapping RFH for
  // same-site navigations due to proactive BrowsingInstance swap.
  IN_PROC_BROWSER_TEST_P(
      ProactivelySwapBrowsingInstancesSameSiteTest,
      PagehideAndVisibilitychangeInSubframesAreRunDuringCommit) {
    if (IsBackForwardCacheEnabled()) {
      // bfcached subframes with unload/pagehide/visibilitychange handlers will
      // crash on a failed DCHECK due to crbug.com/1109742.
      // TODO(crbug.com/1109742): don't skip this test when bfcache is enabled.
      return;
    }
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL main_url(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a.com(a.com)"));
    GURL child_url = embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a.com()");
    GURL url_2(embedded_test_server()->GetURL("a.com", "/local_storage.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to |main_url|.
    EXPECT_TRUE(NavigateToURL(shell(), main_url));
    RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
    ASSERT_EQ(1U, root->child_count());
    // Check if the subframe is navigated to the correct URL.
    FrameTreeNode* child = root->child_at(0);
    EXPECT_EQ(child_url, child->current_url());
  
    // Create a pagehide handler that sets item "pagehide_storage" and a
    // visibilitychange handler that sets item "visibilitychange_storage" in
    // localStorage in the subframe.
    EXPECT_TRUE(ExecJs(
        child,
        base::StringPrintf(R"(
            localStorage.setItem('pagehide_storage', 'not_dispatched');
            var dispatched_pagehide = false;
            window.onpagehide = function(e) {
              if (dispatched_pagehide) {
                // We shouldn't dispatch pagehide more than once.
                localStorage.setItem('pagehide_storage',
                  'dispatched_more_than_once');
              } else if (e.persisted != %s) {
                localStorage.setItem('pagehide_storage', 'wrong_persisted');
              } else {
                localStorage.setItem('pagehide_storage', 'dispatched_once');
              }
              dispatched_pagehide = true;
            }
  
            localStorage.setItem('visibilitychange_storage', 'not_dispatched');
            var dispatched_visibilitychange = false;
            document.onvisibilitychange = function(e) {
              if (dispatched_visibilitychange) {
                // We shouldn't dispatch visibilitychange more than once.
                localStorage.setItem('visibilitychange_storage',
                  'dispatched_more_than_once');
              } else if (document.visibilityState != 'hidden') {
                // We should dispatch the event when the visibilityState is
                // 'hidden'.
                localStorage.setItem('visibilitychange_storage', 'not_hidden');
              } else {
                localStorage.setItem('visibilitychange_storage',
                  'dispatched_once');
              }
              dispatched_visibilitychange = true;
            })",
                           IsBackForwardCacheEnabled() ? "true" : "false")));
    // 2) Navigate to local_storage.html.
    RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
  
    // Check that |main_url| and local_storage.html are in different RFHs.
    RenderFrameHostImpl* main_frame_2 = web_contents->GetMainFrame();
    EXPECT_NE(main_frame_1, main_frame_2);
  
    // Check that the value set by |child|'s pagehide and visibilitychange
    // handlers can be accessed by |main_frame_2| at load time (the first time the
    // new page runs scripts), setting the |pagehide_storage_at_load| and
    // |visibilitychange_storage_at_load| variable correctly.
    EXPECT_EQ("dispatched_once",
              EvalJs(main_frame_2, "pagehide_storage_at_load"));
    EXPECT_EQ("dispatched_once",
              EvalJs(main_frame_2, "visibilitychange_storage_at_load"));
  
    // Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
    // stays the same after |main_frame_2| finished loading (or |main_frame_1| got
    // deleted, if bfcache is not enabled).
    if (!IsBackForwardCacheEnabled())
      main_frame_1_deleted_observer.WaitUntilDeleted();
    EXPECT_EQ("dispatched_once",
              EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
    EXPECT_EQ(
        "dispatched_once",
        EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
  }
  
  // Tests that pagehide handlers of the old RFH are run during the commit
  // of the new RFH when swapping RFH for same-site navigations due to proactive
  // BrowsingInstance swap even if the page is already hidden (and
  // visibilitychange won't run).
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         PagehideRunsDuringCommitOfHiddenPage) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_1(embedded_test_server()->GetURL("/title1.html"));
    GURL url_2(embedded_test_server()->GetURL("/local_storage.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to |url_1| and hide the tab.
    EXPECT_TRUE(NavigateToURL(shell(), url_1));
    RenderFrameHostImpl* main_frame_1 = web_contents->GetMainFrame();
    // We need to set it to Visibility::VISIBLE first in case this is the first
    // time the visibility is updated.
    web_contents->UpdateWebContentsVisibility(Visibility::VISIBLE);
    web_contents->UpdateWebContentsVisibility(Visibility::HIDDEN);
    EXPECT_EQ(Visibility::HIDDEN, web_contents->GetVisibility());
  
    // Create a pagehide handler that sets item "pagehide_storage" and a
    // visibilitychange handler that sets item "visibilitychange_storage" in
    // localStorage.
    EXPECT_TRUE(ExecJs(
        main_frame_1,
        base::StringPrintf(R"(
            localStorage.setItem('pagehide_storage', 'not_dispatched');
            var dispatched_pagehide = false;
            window.onpagehide = function(e) {
              if (dispatched_pagehide) {
                // We shouldn't dispatch pagehide more than once.
                localStorage.setItem('pagehide_storage',
                  'dispatched_more_than_once');
              } else if (e.persisted != %s) {
                localStorage.setItem('pagehide_storage', 'wrong_persisted');
              } else {
                localStorage.setItem('pagehide_storage', 'dispatched_once');
              }
              dispatched_pagehide = true;
            }
  
            localStorage.setItem('visibilitychange_storage', 'not_dispatched');
            document.onvisibilitychange = function(e) {
              localStorage.setItem('visibilitychange_storage',
                  'should_not_be_dispatched');
            })",
                           IsBackForwardCacheEnabled() ? "true" : "false")));
    // |visibilitychange_storage| should be set to its initial correct value.
    EXPECT_EQ(
        "not_dispatched",
        EvalJs(main_frame_1, "localStorage.getItem('visibilitychange_storage')"));
  
    // 2) Navigate to local_storage.html.
    RenderFrameDeletedObserver main_frame_1_deleted_observer(main_frame_1);
    EXPECT_TRUE(NavigateToURL(shell(), url_2));
  
    // Check that |url_1| and local_storage.html are in different RFHs.
    RenderFrameHostImpl* main_frame_2 = web_contents->GetMainFrame();
    EXPECT_NE(main_frame_1, main_frame_2);
  
    // Check that the value set by |main_frame_1|'s pagehide handler can be
    // accessed by |main_frame_2| at load time (the first time the new page runs
    // scripts), setting the |pagehide_storage_at_load| and variable correctly.
    EXPECT_EQ("dispatched_once",
              EvalJs(main_frame_2, "pagehide_storage_at_load"));
    // |visibilitychange_storage_at_load| should not be modified.
    EXPECT_EQ("not_dispatched",
              EvalJs(main_frame_2, "visibilitychange_storage_at_load"));
  
    // Check that the value for 'pagehide_storage' and 'visibilitychange_storage'
    // stays the same after |main_frame_2| finished loading (or |main_frame_1| got
    // deleted, if bfcache is not enabled).
    if (!IsBackForwardCacheEnabled())
      main_frame_1_deleted_observer.WaitUntilDeleted();
    EXPECT_EQ("dispatched_once",
              EvalJs(main_frame_2, "localStorage.getItem('pagehide_storage')"));
    EXPECT_EQ(
        "not_dispatched",
        EvalJs(main_frame_2, "localStorage.getItem('visibilitychange_storage')"));
  }
  
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         NavigationAfterPagehideHistogram) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to a.com/title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_a1));
    ExpectTotalCount(kActionAfterPagehideHistogramName, 0);
  
    // 2) Set up a navigation that will start after we commit the next navigation.
    RenderFrameHostChangedCallbackRunner navigate_after_commit(
        web_contents,
        GetAsyncScriptExecutorCallback("window.location.reload();"));
  
    // 3) Navigate same-site to a.com/title2.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_a2));
  
    // We should record the fact that a navigation started after pagehide was
    // dispatched.
    content::FetchHistogramsFromChildProcesses();
    ExpectBucketCount(kActionAfterPagehideHistogramName,
                      ActionAfterPagehide::kNavigation, 1);
  }
  
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         PostMessageAfterPagehideHistogram) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
    GURL url_b1(embedded_test_server()->GetURL("b.com", "/title1.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to a.com/title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_a1));
    ExpectTotalCount(kActionAfterPagehideHistogramName, 0);
  
    {
      // 2) Set up a script that will call postMessage on the current window
      // after we commit the next navigation.
      // TODO(https://crbug.com/1110497): GetAsyncScriptExecutorCallback() must be
      // removed in favor of GetSyncExecutorCallback()
      RenderFrameHostChangedCallbackRunner post_message_after_same_site_commit(
          web_contents,
          GetScriptExecutorCallback("window.postMessage('hello', '*')"));
  
      // 3) Navigate same-site to a.com/title2.html.
      EXPECT_TRUE(NavigateToURL(shell(), url_a2));
  
      // We should record the fact that a postMessage call was done after
      // pagehide was dispatched and since we're calling it on our own window, we
      // are also receiving the message.
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kSentPostMessage, 1);
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kReceivedPostMessage, 1);
    }
  
    {
      // 4) Set up a script that will call postMessage on the current window
      // after we commit the next navigation.
      // TODO(https://crbug.com/1110497): GetAsyncScriptExecutorCallback() must be
      // removed in favor of GetSyncExecutorCallback()
      RenderFrameHostChangedCallbackRunner post_message_after_cross_site_commit(
          web_contents,
          GetScriptExecutorCallback("window.postMessage('hello', '*')"));
      // 5) Navigate cross-site to |url_b1|.
      EXPECT_TRUE(NavigateToURL(shell(), url_b1));
  
      // Since the navigation is cross-site, the postMessage will happen before
      // pagehide gets dispatched (at unload time), so the histogram stays the
      // same.
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kSentPostMessage, 1);
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kReceivedPostMessage, 1);
    }
  }
  
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         PostMessageAfterPagehideHistogramSubframe) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_a1(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(a)"));
    GURL url_a2(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(b)"));
    GURL url_a3(embedded_test_server()->GetURL("a.com", "/title3.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to |url_a1|, which has one same-site iframe.
    EXPECT_TRUE(NavigateToURL(shell(), url_a1));
    ExpectTotalCount(kActionAfterPagehideHistogramName, 0);
    {
      // 2) Set up a script that will call postMessage on a same-site iframe
      // after we commit the next navigation.
      // TODO(https://crbug.com/1110497): GetAsyncScriptExecutorCallback() must be
      // removed in favor of GetSyncExecutorCallback()
      RenderFrameHostChangedCallbackRunner post_message_after_same_site_commit(
          web_contents, GetScriptExecutorCallback(
                            "window.frames[0].postMessage('hello', '*')"));
  
      // 3) Navigate same-site to |url_a2|, which has one cross-site iframe.
      EXPECT_TRUE(NavigateToURL(shell(), url_a2));
  
      // We should record the fact that a postMessage call was done after
      // pagehide was dispatched and since we're calling it on a same-site
      // iframe's window, we will track it.
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kSentPostMessage, 1);
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kReceivedPostMessage, 1);
    }
  
    {
      FrameTreeNode* root = web_contents->GetFrameTree()->root();
      RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();
      bool subframe_was_in_same_site_instance =
          root->current_frame_host()->GetSiteInstance() ==
          child_rfh->GetSiteInstance();
      // 4) Set up a script that will call postMessage on a cross-site iframe
      // after we commit the next navigation.
      // TODO(https://crbug.com/1110497): GetAsyncScriptExecutorCallback() must be
      // removed in favor of GetSyncExecutorCallback()
      RenderFrameHostChangedCallbackRunner post_message_after_same_site_commit(
          web_contents, GetScriptExecutorCallback(
                            "window.frames[0].postMessage('hello', '*')"));
  
      // 5) Navigate same-site to |url_a3|.
      EXPECT_TRUE(NavigateToURL(shell(), url_a3));
  
      // We should record the fact that a postMessage call was done after
      // pagehide was dispatched. On the receiving part, if the cross-site
      // subframe is in the same SiteInstance as the main frame (it's not
      // isolated) or the iframe is saved in the back-forward cache, the
      // postMessage will arrive after we dispatch pagehide. The former case
      // happens because we will dispatch pagehide at commit time for all
      // subframes in the same SiteInstance as the main frame, and the latter case
      // happens because we will send the freeze message to the subframe before
      // the postMessage arrives in the subframe. Otherwise (if the cross-site
      // subframe is isolated and it's not saved in the back-forward cache), the
      // postMessage will never actually arrive in the subframe (because we would
      // have already unloaded).
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kSentPostMessage, 2);
  
      if (!subframe_was_in_same_site_instance && !IsBackForwardCacheEnabled()) {
        ExpectBucketCount(kActionAfterPagehideHistogramName,
                          ActionAfterPagehide::kReceivedPostMessage, 1);
      } else {
        ExpectBucketCount(kActionAfterPagehideHistogramName,
                          ActionAfterPagehide::kReceivedPostMessage, 2);
      }
    }
  }
  
  // Flaky on all major platforms: https://crbug.com/1156218
  IN_PROC_BROWSER_TEST_P(ProactivelySwapBrowsingInstancesSameSiteTest,
                         DISABLED_StorageModificationAfterPagehideHistogram) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
    GURL url_a3(embedded_test_server()->GetURL("a.com", "/title3.html"));
    GURL url_b1(embedded_test_server()->GetURL("b.com", "/title1.html"));
    GURL url_b2(embedded_test_server()->GetURL("b.com", "/title2.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // 1) Navigate to a.com/title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_a1));
    ExpectTotalCount(kActionAfterPagehideHistogramName, 0);
  
    {
      // 2) Set up a script that will modify localStorage after we commit the next
      // navigation.
      RenderFrameHostChangedCallbackRunner
          set_local_storage_after_same_site_commit(
              web_contents, GetAsyncScriptExecutorCallback(
                                "localStorage.setItem('foo', 'bar'); "));
  
      // 3) Navigate same-site to a.com/title2.html.
      EXPECT_TRUE(NavigateToURL(shell(), url_a2));
  
      // We should record the fact that we modified localStorage after pagehide
      // was dispatched.
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kLocalStorageModification, 1);
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kSessionStorageModification, 0);
    }
  
    {
      // 4) Set up a script that will modify sessionStorage after we commit the
      // next navigation.
      RenderFrameHostChangedCallbackRunner
          set_session_storage_after_same_site_commit(
              web_contents, GetAsyncScriptExecutorCallback(
                                "sessionStorage.setItem('foo', 'bar'); "));
  
      // 5) Navigate same-site to a.com/title3.html.
      EXPECT_TRUE(NavigateToURL(shell(), url_a3));
  
      // We should record the fact that we modified sessionStorage after pagehide
      // was dispatched.
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kLocalStorageModification, 1);
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kSessionStorageModification, 1);
    }
    {
      // 6) Set up a script that will modify localStorage and sessionStorage after
      // we commit the next navigation.
      RenderFrameHostChangedCallbackRunner set_storage_after_cross_site_commit(
          web_contents, GetAsyncScriptExecutorCallback(R"(
        localStorage.setItem('foo', 'bar');
        sessionStorage.setItem('foo', 'bar');
      )"));
  
      // 7) Navigate cross-site to b.com/title1.html.
      EXPECT_TRUE(NavigateToURL(shell(), url_b1));
  
      // Since the navigation is cross-site, the localStorage and sessionStorage
      // modification (which is done at committ time) will happen before pagehide
      // gets dispatched (at unload time), so the histogram stays the same.
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kLocalStorageModification, 1);
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kSessionStorageModification, 1);
    }
    {
      // 8) Set up a script that will access localStorage and sessionStorage after
      // we commit the next navigation.
      RenderFrameHostChangedCallbackRunner get_storage_after_same_site_commit(
          web_contents, GetAsyncScriptExecutorCallback(R"(
        localStorage.getItem('foo');
        sessionStorage.getItem('foo');
      )"));
  
      // 7) Navigate same-site to b.com/title2.html.
      EXPECT_TRUE(NavigateToURL(shell(), url_b2));
  
      // Even though the script runs after pagehide was dispatched, we did not
      // modify anything in localStorage/sessionStorage (getItem only reads
      // values), so the histogram stays the same.
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kLocalStorageModification, 1);
      ExpectBucketCount(kActionAfterPagehideHistogramName,
                        ActionAfterPagehide::kSessionStorageModification, 1);
    }
  }
  
  // Helper class to simplify testing of unload handlers.  It allows waiting for
  // particular HTTP requests to be made to the embedded_test_server(); the tests
  // use this to wait for termination pings (e.g., navigator.sendBeacon()) made
  // from unload handlers.
  class RenderFrameHostManagerUnloadBrowserTest
      : public RenderFrameHostManagerTest {
   public:
    RenderFrameHostManagerUnloadBrowserTest() = default;
  
    // Starts monitoring requests made to the embedded_http_server() looking for
    // one made to |url|.  To be used together with WaitForMonitoredRequest().
    void StartMonitoringRequestsFor(const GURL& url) {
      base::AutoLock lock(lock_);
      request_url_ = url;
      saw_request_url_ = false;
    }
  
    // Waits for a request to a URL set earlier via StartMonitoringRequestsFor().
    // Returns right away if that request was already made.
    void WaitForMonitoredRequest() {
      base::AutoLock lock(lock_);
      if (saw_request_url_)
        return;
  
      run_loop_ = std::make_unique<base::RunLoop>();
      {
        base::RunLoop* run_loop = run_loop_.get();
        base::AutoUnlock unlock(lock_);
        run_loop->Run();
      }
      run_loop_.reset();
    }
  
    // Returns the body of the monitored request if it was a POST.
    const std::string& GetRequestContent() {
      base::AutoLock lock(lock_);
      return request_content_;
    }
  
    blink::mojom::SuddenTerminationDisablerType DisablerTypeForEvent(
        const std::string& event_name) {
      if (event_name == "unload")
        return blink::mojom::SuddenTerminationDisablerType::kUnloadHandler;
      if (event_name == "pagehide")
        return blink::mojom::SuddenTerminationDisablerType::kPageHideHandler;
      if (event_name == "visibilitychange")
        return blink::mojom::SuddenTerminationDisablerType::
            kVisibilityChangeHandler;
      NOTREACHED();
      return blink::mojom::SuddenTerminationDisablerType::kUnloadHandler;
    }
  
    // Adds an unload event handler (can be for the unload, pagehide, or
    // visibilitychange event) to |rfh| and verifies that the unload state
    // bookkeeping on |rfh| is updated properly.
    void AddUnloadEventHandler(RenderFrameHostImpl* rfh,
                               const std::string& event_name,
                               const std::string& script) {
      EXPECT_THAT(event_name,
                  testing::AnyOf(testing::Eq("unload"), testing::Eq("pagehide"),
                                 testing::Eq("visibilitychange")));
      EXPECT_FALSE(rfh->GetSuddenTerminationDisablerState(
          DisablerTypeForEvent(event_name)));
      EXPECT_FALSE(rfh->has_unload_handlers());
      std::string event_target =
          event_name == "visibilitychange" ? "document" : "window";
      EXPECT_TRUE(ExecuteScript(
          rfh, base::StringPrintf("%s.addEventListener('%s', (e) => { %s });",
                                  event_target.c_str(), event_name.c_str(),
                                  script.c_str())));
      EXPECT_TRUE(rfh->GetSuddenTerminationDisablerState(
          DisablerTypeForEvent(event_name)));
      EXPECT_TRUE(rfh->has_unload_handlers());
    }
  
    // Extend the timeout for keeping the subframe process alive for unload
    // processing to prevent any test flakiness.  This is the time that the ping
    // request will have to make it from the renderer to the test server.
    void ExtendSubframeUnloadTimeoutForTerminationPing(RenderFrameHostImpl* rfh) {
      rfh->SetSubframeUnloadTimeoutForTesting(base::TimeDelta::FromSeconds(30));
    }
  
   protected:
    void SetUpOnMainThread() override {
      // Request interceptor needs to be installed before the test server is
      // started.
      embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
          &RenderFrameHostManagerUnloadBrowserTest::MonitorResourceRequest,
          base::Unretained(this)));
  
      RenderFrameHostManagerTest::SetUpOnMainThread();
  
      StartEmbeddedServer();
    }
  
   private:
    void MonitorResourceRequest(const net::test_server::HttpRequest& request) {
      // |request.GetURL()| gives us the URL after it's already resolved to
      // 127.0.0.1, so reconstruct the requested host via the Host header (which
      // includes host+port).
      GURL requested_url = request.GetURL();
      auto it = request.headers.find("Host");
      if (it != request.headers.end())
        requested_url = GURL("http://" + it->second + request.relative_url);
  
      base::AutoLock lock(lock_);
      if (!saw_request_url_ && request_url_ == requested_url) {
        saw_request_url_ = true;
        request_content_ = request.content;
        if (run_loop_)
          run_loop_->Quit();
      }
    }
  
    base::Lock lock_;
    GURL request_url_ GUARDED_BY(lock_);
    std::string request_content_ GUARDED_BY(lock_);
    bool saw_request_url_ GUARDED_BY(lock_) = false;
    std::unique_ptr<base::RunLoop> run_loop_ GUARDED_BY(lock_);
  
    DISALLOW_COPY_AND_ASSIGN(RenderFrameHostManagerUnloadBrowserTest);
  };
  
  // Ensure that after a main frame with a cross-site iframe is itself navigated
  // cross-site, the unload event handlers (for unload, pagehide, or
  // visibilitychange events) in the iframe can use navigator.sendBeacon() to do a
  // termination ping.  See https://crbug.com/852204, where this was broken with
  // site isolation if the iframe was in its own process.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerUnloadBrowserTest,
                         SubframeTerminationPing_SendBeacon) {
    // See BackForwardCache::DisableForTestingReason for explanation.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_USES_UNLOAD_EVENT);
    const std::string unload_event_names[] = {"unload", "pagehide",
                                              "visibilitychange"};
    GURL main_url(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(b)"));
    GURL ping_url(embedded_test_server()->GetURL("b.com", "/empty.html"));
    GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
  
    for (const std::string& unload_event_name : unload_event_names) {
      EXPECT_TRUE(NavigateToURL(shell(), main_url));
      FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                                ->GetFrameTree()
                                ->root();
      RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();
  
      // Add a subframe unload handler to do a termination ping via sendBeacon.
      AddUnloadEventHandler(
          child_rfh, unload_event_name,
          base::StringPrintf("navigator.sendBeacon('%s', 'ping');",
                             ping_url.spec().c_str()));
      ExtendSubframeUnloadTimeoutForTerminationPing(child_rfh);
  
      // Navigate the main frame to c.com and wait for the ping.
      StartMonitoringRequestsFor(ping_url);
      EXPECT_TRUE(NavigateToURL(shell(), c_url));
      // Test succeeds if this doesn't time out while waiting for |ping_url|.
      WaitForMonitoredRequest();
      EXPECT_EQ("ping", GetRequestContent());
    }
  }
  
  // Ensure that after a main frame with a cross-site iframe is itself navigated
  // cross-site, the unload handler in the iframe can use an image load to do a
  // termination ping. See https://crbug.com/852204, where this was broken with
  // site isolation if the iframe was in its own process.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerUnloadBrowserTest,
                         SubframeTerminationPing_Image) {
    // See BackForwardCache::DisableForTestingReason for explanation.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_USES_UNLOAD_EVENT);
  
    GURL main_url(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(b)"));
    EXPECT_TRUE(NavigateToURL(shell(), main_url));
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();
  
    // Add a subframe unload handler to do a termination ping by loading an
    // image.
    GURL ping_url(embedded_test_server()->GetURL("b.com", "/blank.jpg"));
    AddUnloadEventHandler(
        child_rfh, "unload",
        base::StringPrintf("var img = document.createElement('img');"
                           "img.src = '%s';"
                           "document.body.appendChild(img);",
                           ping_url.spec().c_str()));
    ExtendSubframeUnloadTimeoutForTerminationPing(child_rfh);
  
    // Navigate the main frame to c.com and wait for the ping.
    StartMonitoringRequestsFor(ping_url);
    GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), c_url));
    // Test succeeds if this doesn't time out while waiting for |ping_url|.
    WaitForMonitoredRequest();
  }
  
  // Ensure that when closing a window containing a page with a cross-site
  // iframe, the iframe still runs its unload handler and can do a sendBeacon
  // termination ping.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerUnloadBrowserTest,
                         SubframeTerminationPingWhenWindowCloses) {
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
  
    // Open a popup window with a page containing a cross-site iframe.
    GURL popup_url(embedded_test_server()->GetURL(
        "b.com", "/cross_site_iframe_factory.html?b(c)"));
    Shell* popup = OpenPopup(root, popup_url, "popup");
    WebContentsImpl* popup_contents =
        static_cast<WebContentsImpl*>(popup->web_contents());
    EXPECT_TRUE(WaitForLoadStop(popup_contents));
    EXPECT_EQ(popup_url, popup_contents->GetLastCommittedURL());
  
    FrameTreeNode* popup_root = popup_contents->GetFrameTree()->root();
    RenderFrameHostImpl* child_rfh =
        popup_root->child_at(0)->current_frame_host();
  
    // In the popup, add a subframe unload handler to do a termination ping via
    // sendBeacon.
    GURL ping_url(embedded_test_server()->GetURL("c.com", "/empty.html"));
    AddUnloadEventHandler(
        child_rfh, "unload",
        base::StringPrintf("navigator.sendBeacon('%s', 'ping');",
                           ping_url.spec().c_str()));
    ExtendSubframeUnloadTimeoutForTerminationPing(child_rfh);
  
    // Close the popup and wait for the ping.
    StartMonitoringRequestsFor(ping_url);
    popup->Close();
    // Test succeeds if this doesn't time out while waiting for |ping_url|.
    WaitForMonitoredRequest();
    EXPECT_EQ("ping", GetRequestContent());
  }
  
  // Ensure that after a main frame with a cross-site iframe is navigated
  // cross-site, and the iframe had an unload handler which never finishes, the
  // iframe's process eventually exits.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerUnloadBrowserTest,
                         SubframeProcessGoesAwayAfterUnloadTimeout) {
    // See BackForwardCache::DisableForTestingReason for explanation.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_USES_UNLOAD_EVENT);
  
    GURL main_url(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(b)"));
    EXPECT_TRUE(NavigateToURL(shell(), main_url));
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();
  
    // Add an unload handler which never finishes to b.com subframe.
    AddUnloadEventHandler(child_rfh, "unload", "while(1);");
  
    // Navigate the main frame to c.com and wait for the subframe process to
    // shut down.  This should happen when the subframe unload timeout happens,
    // roughly in one second.  Note that depending on whether site isolation is
    // enabled, the subframe process may or may not be the same as the old main
    // frame process, but it should shut down regardless.
    GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
    RenderProcessHostWatcher process_exit_observer(
        child_rfh->GetProcess(),
        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    EXPECT_TRUE(NavigateToURL(shell(), c_url));
    process_exit_observer.Wait();
  }
  
  // Verify that when an OOPIF with an unload handler navigates cross-process,
  // its unload handler is able to send a postMessage to the parent frame.
  // See https://crbug.com/857274.
  #if defined(OS_MAC) || (defined(OS_WIN) && defined(ADDRESS_SANITIZER))
  #define MAYBE_PostMessageToParentWhenSubframeNavigates \
    DISABLED_PostMessageToParentWhenSubframeNavigates
  #else
  #define MAYBE_PostMessageToParentWhenSubframeNavigates \
    PostMessageToParentWhenSubframeNavigates
  #endif
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerUnloadBrowserTest,
                         MAYBE_PostMessageToParentWhenSubframeNavigates) {
    GURL main_url(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(b)"));
    EXPECT_TRUE(NavigateToURL(shell(), main_url));
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    FrameTreeNode* child = root->child_at(0);
  
    // Add an onmessage listener in the main frame.
    EXPECT_TRUE(ExecuteScript(root, R"(
        window.addEventListener('message', function(e) {
          domAutomationController.send(e.data);
        });)"));
  
    // Add an unload handler in the child frame to send a postMessage to the
    // parent frame.
    AddUnloadEventHandler(child->current_frame_host(), "unload",
                          "parent.postMessage('foo', '*')");
  
    // Navigate the subframe cross-site to c.com and wait for the message.
    GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
    std::string message;
    EXPECT_TRUE(ExecuteScriptAndExtractString(
        root,
        base::StringPrintf("document.querySelector('iframe').src = '%s';",
                           c_url.spec().c_str()),
        &message));
    EXPECT_EQ("foo", message);
  
    // Now repeat the test with a remote-to-local navigation that brings the
    // subframe back to a.com.
    AddUnloadEventHandler(child->current_frame_host(), "unload",
                          "parent.postMessage('bar', '*')");
    GURL a_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
    EXPECT_TRUE(ExecuteScriptAndExtractString(
        root,
        base::StringPrintf("document.querySelector('iframe').src = '%s';",
                           a_url.spec().c_str()),
        &message));
    EXPECT_EQ("bar", message);
  }
  
  // Ensure that when a pending delete RenderFrameHost's process dies, the
  // current RenderFrameHost does not lose its child frames.  See
  // https://crbug.com/867274.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerUnloadBrowserTest,
                         PendingDeleteRFHProcessShutdownDoesNotRemoveSubframes) {
    GURL first_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), first_url));
    FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                              ->GetFrameTree()
                              ->root();
    RenderFrameHostImpl* rfh = root->current_frame_host();
  
    // Set up an unload handler which never finishes to force |rfh| to stay
    // around in pending delete state and never receive the
    // mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame.
    EXPECT_TRUE(
        ExecuteScript(rfh, "window.onunload = function(e) { while(1); };\n"));
    rfh->DisableUnloadTimerForTesting();
  
    // Navigate to another page with two subframes.
    RenderFrameDeletedObserver rfh_observer(rfh);
  
    // Ensure that current document won't enter the BackForwardCache, so that it
    // properly execute its unload handler. So, we disable back-forward cache for
    // this test.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_USES_UNLOAD_EVENT);
  
    GURL second_url(embedded_test_server()->GetURL(
        "b.com", "/cross_site_iframe_factory.html?b(c,b)"));
    EXPECT_TRUE(NavigateToURL(shell(), second_url));
  
    // At this point, |rfh| should still be live and pending deletion.
    EXPECT_FALSE(rfh_observer.deleted());
    EXPECT_TRUE(rfh->IsPendingDeletion());
    EXPECT_TRUE(rfh->IsRenderFrameLive());
  
    // Meanwhile, the new page should have two subframes.
    EXPECT_EQ(2U, root->child_count());
  
    // Kill the pending delete RFH's process.
    RenderProcessHostWatcher crash_observer(
        rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    rfh->GetProcess()->Shutdown(0);
    crash_observer.Wait();
  
    // The process kill should simulate a
    // mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame and trigger
    // destruction of the pending delete RFH.
    rfh_observer.WaitUntilDeleted();
  
    // Ensure that the process kill didn't incorrectly remove subframes from the
    // new page.
    ASSERT_EQ(2U, root->child_count());
    EXPECT_TRUE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());
    EXPECT_TRUE(root->child_at(1)->current_frame_host()->IsRenderFrameLive());
  }
  
  namespace {
  
  // A helper to post a recurring check that a renderer process is foregrounded.
  // The recurring check uses WeakPtr semantic and will die when this class goes
  // out of scope.
  class AssertForegroundHelper {
   public:
    AssertForegroundHelper() = default;
  
  #if defined(OS_MAC)
    // Asserts that |renderer_process| isn't backgrounded and reposts self to
    // check again shortly. |renderer_process| must outlive this
    // AssertForegroundHelper instance.
    void AssertForegroundAndRepost(const base::Process& renderer_process,
                                   base::PortProvider* port_provider) {
      ASSERT_FALSE(renderer_process.IsProcessBackgrounded(port_provider));
      base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&AssertForegroundHelper::AssertForegroundAndRepost,
                         weak_ptr_factory_.GetWeakPtr(),
                         std::cref(renderer_process), port_provider),
          base::TimeDelta::FromMilliseconds(1));
    }
  #else   // defined(OS_MAC)
    // Same as above without the Mac specific base::PortProvider.
    void AssertForegroundAndRepost(const base::Process& renderer_process) {
      ASSERT_FALSE(renderer_process.IsProcessBackgrounded());
      base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&AssertForegroundHelper::AssertForegroundAndRepost,
                         weak_ptr_factory_.GetWeakPtr(),
                         std::cref(renderer_process)),
          base::TimeDelta::FromMilliseconds(1));
    }
  #endif  // defined(OS_MAC)
  
   private:
    base::WeakPtrFactory<AssertForegroundHelper> weak_ptr_factory_{this};
  
    DISALLOW_COPY_AND_ASSIGN(AssertForegroundHelper);
  };
  
  }  // namespace
  
  // This is a regression test for https://crbug.com/560446. It ensures the
  // newly launched process for cross-process navigation in the foreground
  // WebContents isn't backgrounded prior to the navigation committing and a
  // "visible" widget being added to the process. This test discards the spare
  // RenderProcessHost if present, to ensure that it is not used in the
  // cross-process navigation.
  IN_PROC_BROWSER_TEST_P(
      RenderFrameHostManagerTest,
      ForegroundNavigationIsNeverBackgroundedWithoutSpareProcess) {
    StartEmbeddedServer();
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
  #if defined(OS_MAC)
    base::PortProvider* port_provider =
        BrowserChildProcessHost::GetPortProvider();
  #endif  //  defined(OS_MAC)
  
    // Start off navigating to a.com and capture the process used to commit.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
    RenderProcessHost* start_rph = web_contents->GetMainFrame()->GetProcess();
  
    // Discard the spare RenderProcessHost to ensure a new RenderProcessHost
    // is created and has the right prioritization.
    RenderProcessHostImpl::DiscardSpareRenderProcessHostForTesting();
    EXPECT_FALSE(RenderProcessHostImpl::GetSpareRenderProcessHostForTesting());
  
    // Start a navigation to b.com to ensure a cross-process navigation is
    // in progress and ensure the process for the speculative host is different.
    GURL url(embedded_test_server()->GetURL("b.com", "/title2.html"));
    content::TestNavigationManager navigation_manager(web_contents, url);
  
    shell()->LoadURL(url);
    RenderProcessHost* speculative_rph = web_contents->GetFrameTree()
                                             ->root()
                                             ->render_manager()
                                             ->speculative_frame_host()
                                             ->GetProcess();
    EXPECT_NE(start_rph, speculative_rph);
    EXPECT_FALSE(speculative_rph->IsReady());
  
  #if !defined(OS_ANDROID)
    // TODO(gab, nasko): On Android IsProcessBackgrounded is currently giving
    // incorrect value at this stage of the process lifetime. This should be
    // fixed in follow up cleanup work. See https://crbug.com/560446.
    EXPECT_FALSE(speculative_rph->IsProcessBackgrounded());
  #endif
  
    // Wait for the underlying OS process to have launched and be ready to
    // receive IPCs.
    RenderProcessHostWatcher process_observer(
        speculative_rph, RenderProcessHostWatcher::WATCH_FOR_PROCESS_READY);
    process_observer.Wait();
  
    // Kick off an infinite check against self that the process used for
    // navigation is never backgrounded. The WaitForNavigationFinished will wait
    // inside a RunLoop() and hence perform this check regularly throughout the
    // navigation.
    const base::Process& process = speculative_rph->GetProcess();
    EXPECT_TRUE(process.IsValid());
    AssertForegroundHelper assert_foreground_helper;
  #if defined(OS_MAC)
    assert_foreground_helper.AssertForegroundAndRepost(process, port_provider);
  #else
    assert_foreground_helper.AssertForegroundAndRepost(process);
  #endif
  
    // The process should be foreground priority before commit because it is
    // pending, and foreground after commit because it has a visible widget.
    navigation_manager.WaitForNavigationFinished();
    EXPECT_NE(start_rph, web_contents->GetMainFrame()->GetProcess());
    EXPECT_EQ(speculative_rph, web_contents->GetMainFrame()->GetProcess());
  }
  
  // Similar to the test above, but verifies the spare RenderProcessHost uses the
  // right priority.
  IN_PROC_BROWSER_TEST_P(
      RenderFrameHostManagerTest,
      ForegroundNavigationIsNeverBackgroundedWithSpareProcess) {
    // This test applies only when spare RenderProcessHost is enabled and in use.
    if (!RenderProcessHostImpl::IsSpareProcessKeptAtAllTimes())
      return;
  
    StartEmbeddedServer();
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
  #if defined(OS_MAC)
    base::PortProvider* port_provider =
        BrowserChildProcessHost::GetPortProvider();
  #endif  //  defined(OS_MAC)
  
    // Start off navigating to a.com and capture the process used to commit.
    EXPECT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
    RenderProcessHost* start_rph = web_contents->GetMainFrame()->GetProcess();
  
    // At this time, there should be a spare RenderProcesHost. Capture it for
    // testing expectations later.
    RenderProcessHost* spare_rph =
        RenderProcessHostImpl::GetSpareRenderProcessHostForTesting();
    EXPECT_TRUE(spare_rph);
    EXPECT_TRUE(spare_rph->IsProcessBackgrounded());
  
    // Start a navigation to b.com to ensure a cross-process navigation is
    // in progress and ensure the process for the speculative host is
    // different, but matches the spare RenderProcessHost.
    GURL url(embedded_test_server()->GetURL("b.com", "/title2.html"));
    content::TestNavigationManager navigation_manager(web_contents, url);
  
    shell()->LoadURL(url);
    RenderProcessHost* speculative_rph = web_contents->GetFrameTree()
                                             ->root()
                                             ->render_manager()
                                             ->speculative_frame_host()
                                             ->GetProcess();
    EXPECT_NE(start_rph, speculative_rph);
  
    // In this test case, the spare RenderProcessHost will be used, so verify it
    // and ensure it is ready.
    EXPECT_EQ(spare_rph, speculative_rph);
    EXPECT_TRUE(spare_rph->IsReady());
  
    // The creation of the speculative RenderFrameHost should change the
    // RenderProcessHost's copy of the priority of the spare process from
    // background to foreground.
    EXPECT_FALSE(spare_rph->IsProcessBackgrounded());
  
    // The OS process itself is updated on the process launcher thread, so it
    // cannot be observed immediately here. Perform a thread hop to and back to
    // allow for the priority change to occur before using the
    // AssertForegroundHelper object to check the OS process priority.
    {
      base::RunLoop run_loop;
      GetProcessLauncherTaskRunner()->PostTaskAndReply(
          FROM_HERE, base::DoNothing(), run_loop.QuitWhenIdleClosure());
      run_loop.Run();
    }
  
    // Kick off an infinite check against self that the process used for
    // navigation is never backgrounded. The WaitForNavigationFinished will wait
    // inside a RunLoop() and hence perform this check regularly throughout the
    // navigation.
    const base::Process& process = spare_rph->GetProcess();
    EXPECT_TRUE(process.IsValid());
    AssertForegroundHelper assert_foreground_helper;
  #if defined(OS_MAC)
    assert_foreground_helper.AssertForegroundAndRepost(process, port_provider);
  #else
    assert_foreground_helper.AssertForegroundAndRepost(process);
  #endif
  
    // The process should be foreground priority before commit because it is
    // pending, and foreground after commit because it has a visible widget.
    navigation_manager.WaitForNavigationFinished();
    EXPECT_NE(start_rph, web_contents->GetMainFrame()->GetProcess());
    EXPECT_EQ(speculative_rph, web_contents->GetMainFrame()->GetProcess());
  }
  
  namespace {
  
  // ContentBrowserClient that skips assigning a site URL for all URLs that match
  // a given URL's scheme and host.
  class DontAssignSiteContentBrowserClient : public TestContentBrowserClient {
   public:
    // Any visit to |url_to_skip| will not cause the site to be assigned to the
    // SiteInstance.
    explicit DontAssignSiteContentBrowserClient(const GURL& url_to_skip)
        : url_to_skip_(url_to_skip) {}
  
    bool ShouldAssignSiteForURL(const GURL& url) override {
      return url.host() != url_to_skip_.host() ||
             url.scheme() != url_to_skip_.scheme();
    }
  
   private:
    GURL url_to_skip_;
  
    DISALLOW_COPY_AND_ASSIGN(DontAssignSiteContentBrowserClient);
  };
  
  }  // namespace
  
  // Ensure that coming back to a NavigationEntry with a previously unassigned
  // SiteInstance (which is now used for another site) properly switches processes
  // and SiteInstances.  See https://crbug.com/945399.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         NavigateWithUnassignedSiteInstance) {
    ASSERT_TRUE(embedded_test_server()->Start());
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // Navigate to a URL that does not assign site URLs.
    GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));
    DontAssignSiteContentBrowserClient content_browser_client(url1);
    ContentBrowserClient* old_client =
        SetBrowserClientForTesting(&content_browser_client);
    EXPECT_TRUE(NavigateToURL(shell(), url1));
    EXPECT_EQ(url1, web_contents->GetLastCommittedURL());
    scoped_refptr<SiteInstanceImpl> instance1(
        web_contents->GetMainFrame()->GetSiteInstance());
    RenderProcessHost* process1 = instance1->GetProcess();
    EXPECT_EQ(GURL(), instance1->GetSiteURL());
  
    // Navigate to foo.com, which uses the previous SiteInstance and sets its site
    // URL.
    GURL url2(embedded_test_server()->GetURL("foo.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(shell(), url2));
    EXPECT_EQ(instance1, web_contents->GetMainFrame()->GetSiteInstance());
    if (AreDefaultSiteInstancesEnabled()) {
      EXPECT_TRUE(instance1->IsDefaultSiteInstance());
    } else {
      EXPECT_EQ(GURL("http://foo.com"), instance1->GetSiteURL());
    }
  
    // The previously committed entry should get a new, related instance to avoid
    // a SiteInstance mismatch when returning to it. See http://crbug.com/992198
    // for further context.
    SiteInstanceImpl* prev_entry_instance = web_contents->GetController()
                                                .GetEntryAtIndex(0)
                                                ->root_node()
                                                ->frame_entry->site_instance();
    EXPECT_NE(prev_entry_instance, instance1);
    EXPECT_NE(prev_entry_instance, nullptr);
    EXPECT_TRUE(prev_entry_instance->IsRelatedSiteInstance(instance1.get()));
    EXPECT_EQ(GURL(), prev_entry_instance->GetSiteURL());
  
    // Navigate to bar.com, which destroys the previous RenderProcessHost.
    GURL url3(embedded_test_server()->GetURL("bar.com", "/title1.html"));
    RenderProcessHostWatcher exit_observer(
        process1, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  
    // With BackForwardCache, old process won't be deleted on navigation as it is
    // still in use by the bfcached document, disable back-forward cache to ensure
    // that the process gets deleted.
    DisableBackForwardCache(BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING);
  
    EXPECT_TRUE(NavigateToURL(shell(), url3));
    exit_observer.Wait();
  
    if (AreDefaultSiteInstancesEnabled()) {
      // Verify that the new navigation also results in a default SiteInstance,
      // and verify that it is not related to |instance1| because the navigation
      // swapped to a new BrowsingInstance.
      EXPECT_TRUE(web_contents->GetMainFrame()
                      ->GetSiteInstance()
                      ->IsDefaultSiteInstance());
      EXPECT_FALSE(instance1->IsRelatedSiteInstance(
          web_contents->GetMainFrame()->GetSiteInstance()));
    } else {
      EXPECT_NE(instance1, web_contents->GetMainFrame()->GetSiteInstance());
    }
  
    // At this point, process1 is deleted, and the first entry is unfortunately
    // pointing to instance1, which has been locked to url2 and has no process.
    EXPECT_FALSE(instance1->HasProcess());
    if (AreAllSitesIsolatedForTesting()) {
      // In site-per-process, we cannot use foo.com's SiteInstance for a.com.
      EXPECT_FALSE(
          instance1->IsSuitableForUrlInfo(UrlInfo::CreateForTesting(url1)));
    } else if (AreDefaultSiteInstancesEnabled()) {
      // Since |instance1| is a default SiteInstance AND this test explicitly
      // ensures that ShouldAssignSiteForURL(url1) will return false, |url1|
      // cannot be placed in the default SiteInstance. This also means that |url1|
      // cannot be placed in the same process as the default SiteInstance.
      EXPECT_FALSE(
          instance1->IsSuitableForUrlInfo(UrlInfo::CreateForTesting(url1)));
    } else {
      // If neither foo.com nor a.com require dedicated processes, then we can use
      // the same process.
      EXPECT_TRUE(
          instance1->IsSuitableForUrlInfo(UrlInfo::CreateForTesting(url1)));
    }
  
    // Go back to url1's entry, which should swap to a new SiteInstance with an
    // unused site URL.
    TestNavigationObserver observer(web_contents);
    web_contents->GetController().GoToOffset(-2);
    observer.Wait();
    scoped_refptr<SiteInstanceImpl> new_instance =
        web_contents->GetMainFrame()->GetSiteInstance();
    EXPECT_EQ(url1, web_contents->GetLastCommittedURL());
    EXPECT_NE(instance1, new_instance);
    EXPECT_EQ(GURL(), new_instance->GetSiteURL());
    EXPECT_TRUE(new_instance->HasProcess());
  
    // Because url1 does not set a site URL, it should not lock the new process
    // either, so that it can be used for subsequent navigations.
    content::RenderProcessHost* new_process = new_instance->GetProcess();
    auto* policy = ChildProcessSecurityPolicy::GetInstance();
    EXPECT_TRUE(policy->CanAccessDataForOrigin(new_process->GetID(),
                                               url::Origin::Create(url1)));
    EXPECT_TRUE(policy->CanAccessDataForOrigin(new_process->GetID(),
                                               url::Origin::Create(url2)));
  
    SetBrowserClientForTesting(old_client);
  }
  
  // Check that when a navigation to a URL that doesn't require assigning a site
  // URL is in progress, another navigation can't reuse the same process in the
  // meantime.  Such reuse previously led to a renderer kill when the siteless
  // URL later committed; a real-world example of the siteless URL was
  // chrome-native://newtab.  See https://crbug.com/970046.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         NavigationRacesWithCommitInUnassignedSiteInstance) {
    ASSERT_TRUE(embedded_test_server()->Start());
  
    // Set up a URL for which ShouldAssignSiteForURL will return false.  The
    // corresponding SiteInstance's site will be left unassigned, and its process
    // won't be locked.  The test will navigate to this URL first.
    GURL siteless_url(
        embedded_test_server()->GetURL("siteless.com", "/title1.html"));
    DontAssignSiteContentBrowserClient content_browser_client(siteless_url);
    ContentBrowserClient* old_client =
        SetBrowserClientForTesting(&content_browser_client);
  
    // Prepare for a second navigation to a normal URL.  Ensure it's isolated so
    // that it requires a process lock on all platforms.
    GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
    auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
    policy->AddFutureIsolatedOrigins(
        {url::Origin::Create(foo_url)},
        ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
  
    // Create a new shell where the foo.com origin isolation will take effect.
    Shell* shell = CreateBrowser();
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell->web_contents());
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
    RenderProcessHost* foo_process = nullptr;
    TestNavigationManager foo_manager(web_contents, foo_url);
    auto& current_isolation_context =
        root->current_frame_host()->GetSiteInstance()->GetIsolationContext();
    auto site_info = SiteInfo::CreateForTesting(current_isolation_context,
                                                GURL("http://foo.com"));
    EXPECT_TRUE(site_info.RequiresDedicatedProcess(current_isolation_context));
  
    // Set up the work to be done after the renderer is asked to commit
    // |siteless_url|, but before the corresponding DidCommitProvisionalLoad IPC
    // is processed.  This will start a navigation to |foo_url| and wait for its
    // response.
    auto did_commit_callback =
        base::BindLambdaForTesting([&](RenderFrameHost* rfh) {
          // The navigation should stay in the initial empty SiteInstance, with
          // the site still unassigned.
          EXPECT_FALSE(
              static_cast<SiteInstanceImpl*>(rfh->GetSiteInstance())->HasSite());
          EXPECT_FALSE(root->render_manager()->speculative_frame_host());
  
          shell->LoadURL(foo_url);
  
          // The foo.com navigation should swap to a new process, since it is not
          // safe to reuse |siteless_url|'s process before |siteless_url|
          // commits.
          EXPECT_TRUE(root->render_manager()->speculative_frame_host());
          foo_process =
              root->render_manager()->speculative_frame_host()->GetProcess();
  
          // Wait for response.  This will cause |foo_manager| to spin up a
          // nested message loop while we're blocked in the current message loop
          // (within DidCommitNavigationInterceptor).  Thus, it's important to
          // allow nestable tasks in |foo_manager|'s message loop, so that it can
          // process the response before we unblock the
          // DidCommitNavigationInterceptor's message loop and finish processing
          // the commit.
          foo_manager.AllowNestableTasks();
          EXPECT_TRUE(foo_manager.WaitForResponse());
  
          foo_manager.ResumeNavigation();
          // After returning here, the commit for |siteless_url| will be
          // processed.
        });
  
    CommitMessageDelayer commit_delayer(web_contents,
                                        siteless_url /* deferred_url */,
                                        std::move(did_commit_callback));
  
    // Start the first navigation, which does not assign a site URL.
    shell->LoadURL(siteless_url);
  
    // The navigation should stay in the initial empty SiteInstance, so there
    // shouldn't be a speculative RFH at this point.
    EXPECT_FALSE(root->render_manager()->speculative_frame_host());
  
    // Wait for the DidCommit IPC for |siteless_url|, and before processing it,
    // trigger a navigation to |foo_url| and wait for its response.
    commit_delayer.Wait();
  
    // Check that the renderer hasn't been killed.  At this point, it should've
    // successfully committed the navigation to |siteless_url|, and it shouldn't
    // be locked.
    EXPECT_TRUE(web_contents->GetMainFrame()->IsRenderFrameLive());
    EXPECT_EQ(siteless_url, web_contents->GetMainFrame()->GetLastCommittedURL());
    RenderProcessHost* process1 = web_contents->GetMainFrame()->GetProcess();
    EXPECT_FALSE(web_contents->GetMainFrame()->GetSiteInstance()->HasSite());
    auto process1_lock = policy->GetProcessLock(process1->GetID());
    EXPECT_FALSE(process1_lock.is_invalid());
    EXPECT_TRUE(process1_lock.allows_any_site());
  
    // Now wait for second navigation to finish and ensure it also succeeds.
    foo_manager.WaitForNavigationFinished();
    EXPECT_TRUE(foo_manager.was_successful());
    EXPECT_TRUE(web_contents->GetMainFrame()->IsRenderFrameLive());
    EXPECT_EQ(foo_url, web_contents->GetMainFrame()->GetLastCommittedURL());
  
    // The foo.com navigation should've used a different process, locked to
    // foo.com.
    RenderProcessHost* process2 = web_contents->GetMainFrame()->GetProcess();
    EXPECT_NE(process1, process2);
    EXPECT_EQ(GURL("http://foo.com"),
              web_contents->GetMainFrame()->GetSiteInstance()->GetSiteURL());
    EXPECT_EQ(ProcessLock(
                  SiteInfo(GURL("http://foo.com"), GURL("http://foo.com"),
                           false /* is_origin_keyed */,
                           CoopCoepCrossOriginIsolatedInfo::CreateNonIsolated())),
              policy->GetProcessLock(process2->GetID()));
  
    // Ensure also that the foo.com process didn't change midway through the
    // navigation.
    EXPECT_EQ(foo_process, process2);
  
    SetBrowserClientForTesting(old_client);
  }
  
  // When ProactivelySwapBrowsingInstance is enabled, the browser switch to a new
  // BrowsingInstance on cross-site HTTP(S) main frame navigations, when there are
  // no other windows in the BrowsingInstance.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         ProactivelySwapBrowsingInstance) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // Navigate to A.
    EXPECT_TRUE(NavigateToURL(shell(), a_url));
    scoped_refptr<SiteInstance> a_site_instance =
        web_contents->GetMainFrame()->GetSiteInstance();
  
    // Navigate to B. The navigation is document initiated. It swaps
    // BrowsingInstance only if  ProactivelySwapBrowsingInstance is enabled.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
    EXPECT_TRUE(WaitForLoadStop(web_contents));
    scoped_refptr<SiteInstance> b_site_instance =
        web_contents->GetMainFrame()->GetSiteInstance();
  
    if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances())
      EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
    else
      EXPECT_TRUE(a_site_instance->IsRelatedSiteInstance(b_site_instance.get()));
  }
  
  // Tests specific to the "default process" mode (which creates strict
  // SiteInstances that can share a default process per BrowsingInstance).
  class RenderFrameHostManagerDefaultProcessTest
      : public RenderFrameHostManagerTest {
   public:
    RenderFrameHostManagerDefaultProcessTest() {
      feature_list_.InitAndEnableFeature(
          features::kProcessSharingWithStrictSiteInstances);
    }
    ~RenderFrameHostManagerDefaultProcessTest() override = default;
  
    void SetUpCommandLine(base::CommandLine* command_line) override {
      RenderFrameHostManagerTest::SetUpCommandLine(command_line);
      command_line->AppendSwitch(switches::kDisableSiteIsolation);
  
      if (AreAllSitesIsolatedForTesting()) {
        LOG(WARNING) << "This test should be run without strict site isolation. "
                     << "It does nothing when --site-per-process is specified.";
      }
    }
  
   private:
    base::test::ScopedFeatureList feature_list_;
  
    DISALLOW_COPY_AND_ASSIGN(RenderFrameHostManagerDefaultProcessTest);
  };
  
  // Ensure that the default process can be used for URLs that don't assign a site
  // to the SiteInstance, when Site Isolation is not enabled.
  // 1. Visit foo.com.
  // 2. Start to navigate to a siteless URL.
  // 3. When the commit is pending, start a navigation to bar.com in a popup.
  // (Using a popup avoids a crash when replacting the speculative RFH, per
  // https://crbug.com/838348.)
  // All navigations should use the default process, and we should not crash.
  // See https://crbug.com/977956.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerDefaultProcessTest,
                         NavigationRacesWithSitelessCommitInDefaultProcess) {
    // This test is designed to run without strict site isolation.
    if (AreAllSitesIsolatedForTesting())
      return;
  
    ASSERT_TRUE(embedded_test_server()->Start());
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
    GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html"));
  
    // Step 1: Visit foo.com in the default process.
    EXPECT_TRUE(NavigateToURL(shell(), foo_url));
    RenderProcessHost* original_process =
        web_contents->GetMainFrame()->GetProcess();
    EXPECT_EQ(original_process, web_contents->GetMainFrame()
                                    ->GetSiteInstance()
                                    ->GetSiteInstanceGroupProcessIfAvailable());
    // This test expect a cross-site navigation to be same BrowsingInstance. With
    // ProactivelySwapBrowsingInstance, it won't be the case. Opening a popup
    // prevent the BrowsingInstance to change.
    if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) {
      GURL popup_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
      EXPECT_TRUE(OpenPopup(web_contents->GetMainFrame(), popup_url, ""));
    }
  
    // Set up a URL for which ShouldAssignSiteForURL will return false.  The
    // corresponding SiteInstance's site will be left unassigned, and its process
    // won't be locked.
    GURL siteless_url(
        embedded_test_server()->GetURL("siteless.com", "/title1.html"));
    DontAssignSiteContentBrowserClient content_browser_client(siteless_url);
    ContentBrowserClient* old_client =
        SetBrowserClientForTesting(&content_browser_client);
  
    // Set up the work to be done after the renderer is asked to commit
    // |siteless_url|, but before the corresponding DidCommitProvisionalLoad IPC
    // is processed.  This will start a navigation to |bar_url| in a popup and
    // wait for its response.  We use a popup to avoid trampling the speculative
    // RFH while it is committing (per https://crbug.com/838348).
    auto did_commit_callback =
        base::BindLambdaForTesting([&](RenderFrameHost* rfh) {
          Shell* new_shell = OpenPopup(shell(), GURL(url::kAboutBlankURL), "foo");
          EXPECT_TRUE(new_shell);
  
          // Step 3: Navigate to bar.com in the same BrowsingInstance, while the
          // commit to siteless_url is pending.  This used to crash because it
          // picked the default process, but IsSuitableHost said it was not ok due
          // to the pending siteless URL commit (in https://crbug.com/977956).
          TestNavigationManager bar_manager(new_shell->web_contents(), bar_url);
          EXPECT_TRUE(ExecuteScript(new_shell,
                                    base::StringPrintf("location.href = '%s'",
                                                       bar_url.spec().c_str())));
  
          // Wait for response.  This will cause |bar_manager| to spin up a
          // nested message loop while we're blocked in the current message loop
          // (within DidCommitNavigationInterceptor).  Thus, it's important to
          // allow nestable tasks in |bar_manager|'s message loop, so that it can
          // process the response before we unblock the
          // DidCommitNavigationInterceptor's message loop and finish processing
          // the commit.
          bar_manager.AllowNestableTasks();
          EXPECT_TRUE(bar_manager.WaitForResponse());
  
          bar_manager.ResumeNavigation();
  
          // After returning here, the commit for |siteless_url| will be
          // processed.
        });
  
    // Step 2: Visit siteless_url in the same BrowsingInstance, but wait before
    // the commit IPC is processed.  (See did_commit_callback above for step 3.)
    CommitMessageDelayer commit_delayer(web_contents,
                                        siteless_url /* deferred_url */,
                                        std::move(did_commit_callback));
    EXPECT_TRUE(ExecuteScript(
        shell(),
        base::StringPrintf("location.href = '%s'", siteless_url.spec().c_str())));
  
    // Wait for the DidCommit IPC for |siteless_url|, and before processing it,
    // trigger a navigation to |foo_url| and wait for its response.
    commit_delayer.Wait();
  
    EXPECT_EQ(original_process, web_contents->GetMainFrame()->GetProcess());
  
    SetBrowserClientForTesting(old_client);
  }
  
  // 1. Navigate to A1(B2, B3(B4), C5)
  // 2. Crash process B
  // 3. Reload B2, creating RFH B6.
  //
  // Along the way, check the RenderFrameProxies.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         CrashFrameReloadAndCheckProxy) {
    // This test explicitly requires multiple processes to be used. It won't mean
    // anything without SiteIsolation.
    if (!AreAllSitesIsolatedForTesting())
      return;
  
    // 1. Navigate to A1(B2, B3(B4), C5).
    StartEmbeddedServer();
    GURL url(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(b,b(b),c)"));
    EXPECT_TRUE(NavigateToURL(shell(), url));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    RenderFrameHostImpl* a1 = web_contents->GetMainFrame();
    RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host();
    RenderFrameHostImpl* b3 = a1->child_at(1)->current_frame_host();
    RenderFrameHostImpl* b4 = b3->child_at(0)->current_frame_host();
    RenderFrameHostImpl* c5 = a1->child_at(2)->current_frame_host();
  
    RenderFrameDeletedObserver delete_a1(a1);
    RenderFrameDeletedObserver delete_b2(b2);
    RenderFrameDeletedObserver delete_b3(b3);
    RenderFrameDeletedObserver delete_b4(b4);
    RenderFrameDeletedObserver delete_c5(c5);
  
    GURL b2_url = b2->GetLastCommittedURL();
    int b2_routing_id = b2->routing_id();
  
    auto proxy_count = [](RenderFrameHostImpl* rfh) {
      return rfh->frame_tree_node()->render_manager()->GetProxyCount();
    };
  
    // There are 3 processes, so every frame has 2 frame proxies.
    EXPECT_EQ(2u, proxy_count(a1));
    EXPECT_EQ(2u, proxy_count(b2));
    EXPECT_EQ(2u, proxy_count(b3));
    EXPECT_EQ(2u, proxy_count(b4));
    EXPECT_EQ(2u, proxy_count(c5));
  
    auto is_proxy_live = [](RenderFrameHostImpl* rfh,
                            scoped_refptr<SiteInstance> site_instance) {
      return rfh->frame_tree_node()
          ->render_manager()
          ->GetRenderFrameProxyHost(site_instance.get())
          ->is_render_frame_proxy_live();
    };
  
    // Store SiteInstance for later comparison.
    scoped_refptr<SiteInstance> a_site_instance(a1->GetSiteInstance());
    scoped_refptr<SiteInstance> b_site_instance(b2->GetSiteInstance());
    scoped_refptr<SiteInstance> c_site_instance(c5->GetSiteInstance());
  
    // Check the state of the proxies before the crash:
    EXPECT_TRUE(is_proxy_live(a1, b_site_instance));
    EXPECT_TRUE(is_proxy_live(a1, c_site_instance));
    EXPECT_TRUE(is_proxy_live(b2, a_site_instance));
    EXPECT_TRUE(is_proxy_live(b2, c_site_instance));
    EXPECT_TRUE(is_proxy_live(b3, a_site_instance));
    EXPECT_TRUE(is_proxy_live(b3, c_site_instance));
    EXPECT_TRUE(is_proxy_live(c5, a_site_instance));
    EXPECT_TRUE(is_proxy_live(c5, b_site_instance));
  
    // 2. Crash process B.
    RenderProcessHost* process = b2->GetProcess();
    RenderProcessHostWatcher crash_observer(
        process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    process->Shutdown(0);
    crash_observer.Wait();
  
    // Only B4 is deleted. B2 and B3 are still there in a "crashed" state.
    delete_b4.WaitUntilDeleted();
  
    // B2, B3, B4 RenderFrame are gone.
    EXPECT_FALSE(delete_a1.deleted());
    EXPECT_TRUE(delete_b2.deleted());
    EXPECT_TRUE(delete_b3.deleted());
    EXPECT_TRUE(delete_b4.deleted());
    EXPECT_FALSE(delete_c5.deleted());
  
    // B2 and B3 RenderFrameHost are still there, but B4 is definitely gone.
    ASSERT_EQ(3u, a1->child_count());
    EXPECT_EQ(b2, a1->child_at(0)->current_frame_host());
    EXPECT_EQ(b3, a1->child_at(1)->current_frame_host());
    ASSERT_EQ(0u, b3->child_count());
  
    EXPECT_FALSE(a1->must_be_replaced());
    EXPECT_TRUE(b2->must_be_replaced());
    EXPECT_TRUE(b3->must_be_replaced());
    EXPECT_FALSE(c5->must_be_replaced());
  
    EXPECT_EQ(2u, proxy_count(a1));
    EXPECT_EQ(2u, proxy_count(b2));
    EXPECT_EQ(2u, proxy_count(b3));
    EXPECT_EQ(2u, proxy_count(c5));
  
    // Check the state of the proxies after the crash:
    EXPECT_FALSE(is_proxy_live(a1, b_site_instance));
    EXPECT_TRUE(is_proxy_live(a1, c_site_instance));
    EXPECT_TRUE(is_proxy_live(b2, a_site_instance));
    EXPECT_TRUE(is_proxy_live(b2, c_site_instance));
    EXPECT_TRUE(is_proxy_live(b3, a_site_instance));
    EXPECT_TRUE(is_proxy_live(b3, c_site_instance));
    EXPECT_TRUE(is_proxy_live(c5, a_site_instance));
    EXPECT_FALSE(is_proxy_live(c5, b_site_instance));
  
    // 3. Reload B2, B6 is created.
    NavigateFrameToURL(b2->frame_tree_node(), b2_url);
  
    // B2 has been replaced
    EXPECT_NE(b2_routing_id, a1->child_at(0)->current_frame_host()->routing_id());
    // B3 hasn't been replaced.
    EXPECT_EQ(b3, a1->child_at(1)->current_frame_host());
    RenderFrameHostImpl* b6 = a1->child_at(0)->current_frame_host();
    EXPECT_TRUE(b3->must_be_replaced());
    EXPECT_FALSE(b6->must_be_replaced());
  
    EXPECT_EQ(a_site_instance, a1->GetSiteInstance());
    EXPECT_EQ(b_site_instance, b6->GetSiteInstance());
    EXPECT_EQ(c_site_instance, c5->GetSiteInstance());
  
    EXPECT_EQ(2u, proxy_count(a1));
    EXPECT_EQ(2u, proxy_count(b6));
    EXPECT_EQ(2u, proxy_count(b3));
    EXPECT_EQ(2u, proxy_count(c5));
  
    // Check the state of the proxies after the reload.
    EXPECT_TRUE(is_proxy_live(a1, b_site_instance));
    EXPECT_TRUE(is_proxy_live(a1, c_site_instance));
    EXPECT_TRUE(is_proxy_live(b6, a_site_instance));
    EXPECT_TRUE(is_proxy_live(b6, c_site_instance));
    EXPECT_TRUE(is_proxy_live(b3, a_site_instance));
    EXPECT_TRUE(is_proxy_live(b3, c_site_instance));
    EXPECT_TRUE(is_proxy_live(c5, a_site_instance));
    EXPECT_TRUE(is_proxy_live(c5, b_site_instance));
  }
  
  // With just the right initial navigations using RendererDebugURLs, creating a
  // new RenderFrameHost can fail. https://crbug.com/1006814
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         NavigateFromRevivedRendererDebugURL) {
    StartEmbeddedServer();
    // This matches IsRendererDebugURL.
    GURL debug_url("javascript:'hello'");
    // Just needs to be any URL that would navigate successfully.
    GURL other_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // Go to the debug URL. This is a synchronous navigation.
    shell()->LoadURL(debug_url);
    ASSERT_EQ("hello", EvalJs(shell(), "document.body.innerText"));
  
    // Crash the renderer.
    FrameTreeNode* root = web_contents->GetFrameTree()->root();
    RenderFrameHostImpl* rfh = root->current_frame_host();
    RenderProcessHost* process = rfh->GetProcess();
    RenderProcessHostWatcher crash_observer(
        process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    process->Shutdown(0);
    crash_observer.Wait();
  
    // Load the URL again. This will cause the RenderWidgetHost to be revived,
    // pointing to a RenderWidget in a new process.
    shell()->LoadURL(debug_url);
    ASSERT_EQ("hello", EvalJs(shell(), "document.body.innerText"));
    RenderProcessHost* new_process = root->current_frame_host()->GetProcess();
  
    // Now try load another URL. It should cope smoothly with the fact that the
    // RenderWidgetHost is already revived.
    ASSERT_TRUE(NavigateToURL(web_contents, other_url));
  
    // In https://crbug.com/1006814 with site-isolation disabled when creating new
    // hosts for crashed frames, the process does not change. We check that here
    // to make sure that we actually recreated the bug. With site-isolation
    // enabled, the process should change.
    if (!AreAllSitesIsolatedForTesting()) {
      ASSERT_EQ(new_process, root->current_frame_host()->GetProcess());
    } else {
      ASSERT_NE(new_process, root->current_frame_host()->GetProcess());
    }
  }
  
  // Helper class to run tests without site isolation.
  class RenderFrameHostManagerNoSiteIsolationTest
      : public RenderFrameHostManagerTest {
   public:
    RenderFrameHostManagerNoSiteIsolationTest() = default;
    ~RenderFrameHostManagerNoSiteIsolationTest() override = default;
  
    void SetUpCommandLine(base::CommandLine* command_line) override {
      command_line->AppendSwitch(switches::kDisableSiteIsolation);
    }
  
   private:
    DISALLOW_COPY_AND_ASSIGN(RenderFrameHostManagerNoSiteIsolationTest);
  };
  
  // Ensure that when a process that allows any site gets reused by new
  // BrowsingInstances, ChildProcessSecurityPolicy gets notified about those new
  // BrowsingInstances.  Failure to do so will lead to a crash at commit time due
  // to mismatched process locks.  See https://crbug.com/1141877.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerNoSiteIsolationTest,
                         IncludeIsolationContextInProcessThatAllowsAnySite) {
    StartEmbeddedServer();
    // Ensure we have one renderer process that's reused for everything.
    RenderProcessHost::SetMaxRendererProcessCount(1);
  
    // The test starts out with an initial window with a blank SiteInstance.
    // Create a new window in a new BrowsingInstance and another blank
    // SiteInstance.
    Shell* shell2 =
        Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
                               GURL(), nullptr, gfx::Size());
    SiteInstanceImpl* old_instance = static_cast<SiteInstanceImpl*>(
        shell()->web_contents()->GetMainFrame()->GetSiteInstance());
    SiteInstanceImpl* new_instance = static_cast<SiteInstanceImpl*>(
        shell2->web_contents()->GetMainFrame()->GetSiteInstance());
    EXPECT_FALSE(old_instance->IsRelatedSiteInstance(new_instance));
  
    // At this point, neither SiteInstance should have a site assigned.
    EXPECT_FALSE(old_instance->HasSite());
    EXPECT_FALSE(new_instance->HasSite());
  
    // Both should use the same process.
    EXPECT_EQ(old_instance->GetProcess(), new_instance->GetProcess());
  
    // Make sure the BrowsingInstanceId is cleaned up immediately.
    ChildProcessSecurityPolicyImpl::GetInstance()
        ->SetBrowsingInstanceCleanupDelayForTesting(0);
  
    // Close the test's initial window.  This should destroy the initial
    // BrowsingInstance and remove it from ChildProcessSecurityPolicy.
    shell()->Close();
  
    // Navigate to a web URL in the second window.  This shouldn't crash.
    GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    ASSERT_TRUE(NavigateToURL(shell2->web_contents(), url));
  }
  
  // With RenderDocument for subframes, removing a frame while it is executing
  // its own unload handler caused a crash. https://crbug.com/1148793
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         RemoveSubframeInUnload_SameSite) {
    // TODO(https://crbug.com/1148793): Remove this early return.
    AssertCanRemoveSubframeInUnload(/*same_site=*/true);
  }
  
  // See RemoveSubframeInUnload_SameSite
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         RemoveSubframeInUnload_CrossSite) {
    // TODO(https://crbug.com/1148793): Remove this early return.
    if (ShouldCreateNewHostForSameSiteSubframe() &&
        !AreAllSitesIsolatedForTesting())
      return;
    AssertCanRemoveSubframeInUnload(/*same_site=*/false);
  }
  
  // This test demonstrates a similar issue to the previous two tests, but
  // triggers it in a slightly different way. The previous two tests navigate a
  // subframe and rely on some variant of RenderDocument being enabled to trigger
  // the crash. If the navigation commits in a new RenderFrameHostImpl, the
  // renderer does not correctly handle the case where running the unload handler
  // while swapping in the new frame detaches the navigated frame.
  //
  // However, this bug actually precedes RenderDocument, as detaching a document
  // for navigation at swap time must also detach the subtree. Given a frame tree
  // A1(B(A2)) and a navigation from B->A3, committing A3 will unload B. However,
  // unloading B will also detach A2, and A2's unload handler can detach B since
  // it can script A1 and remove the frame owner element synchronously.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, NestedUnload) {
    // These tests require site isolation to trigger the (formerly problematic)
    // delayed detach of the remote frame when swapping in the new local frame.
    if (!AreAllSitesIsolatedForTesting())
      return;
  
    SetupCrossSiteRedirector(embedded_test_server());
    StartEmbeddedServer();
    EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL(
                                           "a.com", "/nested-unload-0.html")));
  
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    EXPECT_EQ(
        " Site A ------------ proxies for B\n"
        "   +--Site B ------- proxies for A\n"
        "        +--Site A -- proxies for B\n"
        "Where A = http://a.com/\n"
        "      B = http://b.com/",
        FrameTreeVisualizer().DepictFrameTree(
            web_contents->GetFrameTree()->root()));
  
    // Navigate the subframe, triggering unload.
    FrameTreeNode* subframe = web_contents->GetMainFrame()->child_at(0);
    RenderFrameDeletedObserver observer(
        subframe->render_manager()->current_frame_host());
  
    GURL other_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    // The navigation will remove the frame that was navigating. Various Navigate
    // helpers run into problems with this because there is no successful commit
    // nor is there a DidStopLoading (because the destination frame should not
    // load at all). So instead we start the Navigation and just wait for the
    // deletion.
    ASSERT_TRUE(ExecJs(subframe, JsReplace("location = $1", other_url)));
    observer.WaitUntilDeleted();
  
    // The subframe has been removed.
    EXPECT_EQ(0UL, web_contents->GetMainFrame()->child_count());
    // TODO(https://crbug.com/1111191): Remove this. Without this, the crash in
    // the renderer in https://crbug.com/1148793 is usually not caught.
    ASSERT_TRUE(ExecJs(shell(), ""));
  }
  
  // See RemoveSubframeInUnload_SameSite
  void RenderFrameHostManagerTest::AssertCanRemoveSubframeInUnload(
      bool same_site) {
    StartEmbeddedServer();
  
    // Create a page with a subframe.
    GURL frame_url(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(a)"));
    ASSERT_TRUE(NavigateToURL(shell(), frame_url));
  
    // Set up the subframe's unload handler to remove the subframe.
    ASSERT_TRUE(ExecJs(shell(), R"(
      const subframe = document.getElementById("child-0");
      subframe.contentWindow.onunload = () => {
        subframe.remove();
      }
    )"));
  
    // Navigate the subframe, triggering unload.
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    FrameTreeNode* subframe = web_contents->GetMainFrame()->child_at(0);
    RenderFrameDeletedObserver observer(
        subframe->render_manager()->current_frame_host());
  
    GURL other_url(embedded_test_server()->GetURL(same_site ? "a.com" : "b.com",
                                                  "/title1.html"));
    // The navigation will remove the frame that was navigating. Various Navigate
    // helpers run into problems with this because there is no successful commit
    // nor is there a DidStopLoading (because the destination frame should not
    // load at all). So instead we start the Navigation and just wait for the
    // deletion.
    ASSERT_TRUE(ExecJs(subframe, JsReplace("location = $1", other_url)));
    observer.WaitUntilDeleted();
  
    // The subframe has been removed.
    EXPECT_EQ(0UL, web_contents->GetMainFrame()->child_count());
    // TODO(https://crbug.com/1111191): Remove this. Without this, the crash in
    // the renderer in https://crbug.com/1148793 is usually not caught.
    ASSERT_TRUE(ExecJs(shell(), ""));
  }
  
  // From https://crbug.com/1169844. Verify that crashing a cross-site subframe
  // and navigating it to a new site does not cause the browser to crash.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                         NavigateCrossSiteSubframeAfterCrash) {
    StartEmbeddedServer();
    // Ensure that all 3 pages are isolated from each other (even on
    // Android, where site-per-process is not the default).
    IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                             {"a.com", "b.com", "c.com"});
  
    // Create a page with a subframe and navigate it cross-site.
    GURL url(embedded_test_server()->GetURL(
        "a.com", "/cross_site_iframe_factory.html?a(b)"));
    ASSERT_TRUE(NavigateToURL(shell(), url));
  
    // Crash the subframe.
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
    FrameTreeNode* subframe = web_contents->GetMainFrame()->child_at(0);
    RenderFrameHostImpl* rfh = subframe->current_frame_host();
    RenderProcessHost* process = rfh->GetProcess();
    {
      RenderProcessHostWatcher crash_observer(
          process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
      process->Shutdown(0);
      crash_observer.Wait();
    }
    ASSERT_FALSE(rfh->IsRenderFrameLive());
    subframe = web_contents->GetMainFrame()->child_at(0);
  
    // Navigate the subframe cross-site.
    GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
    ASSERT_TRUE(NavigateFrameToURL(subframe, url_c));
    ASSERT_TRUE(subframe->current_frame_host()->IsRenderFrameLive());
  }
  
  // Tests that enable clearing window.name on cross-site
  // cross-BrowsingInstance navigations.
  class RenderFrameHostManagerClearWindowNameTest
      : public RenderFrameHostManagerTest {
   public:
    RenderFrameHostManagerClearWindowNameTest() {
      feature_list_.InitAndEnableFeature(
          features::kClearCrossBrowsingContextGroupMainFrameName);
    }
    ~RenderFrameHostManagerClearWindowNameTest() override = default;
  
   private:
    base::test::ScopedFeatureList feature_list_;
  };
  
  // Verify that cross-site main frame navigation that swaps BrowsingInstances
  // clears window.name.
  IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerClearWindowNameTest,
                         ClearWindowNameCrossSite) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
    GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // Navigate to a.com/title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_a));
    // Set window.name.
    EXPECT_TRUE(content::ExecuteScript(web_contents, "window.name='foo'"));
    auto* frame_a = web_contents->GetMainFrame();
    EXPECT_EQ("foo", frame_a->GetFrameName());
  
    scoped_refptr<SiteInstance> site_instance_a = frame_a->GetSiteInstance();
  
    // Renderer-initiated navigate to b.com/title2.html.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_b));
    auto* frame_b = web_contents->GetMainFrame();
    scoped_refptr<SiteInstance> site_instance_b = frame_b->GetSiteInstance();
  
    // Whether renderer-initiated top-level cross-site navigates swap
    // BrowsingInstances is based on whether ProactivelySwapBrowsingInstances or
    // BackForwardCache is enabled.
    if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) {
      EXPECT_FALSE(site_instance_a->IsRelatedSiteInstance(site_instance_b.get()));
      // The BrowsingInstance got swapped, window.name is cleared.
      EXPECT_EQ("", frame_b->GetFrameName());
    } else {
      EXPECT_TRUE(site_instance_a->IsRelatedSiteInstance(site_instance_b.get()));
      // Window.name is not cleared.
      EXPECT_EQ("foo", frame_b->GetFrameName());
    }
  
    // Navigate to c.com/title1.html. The navigation is cross-site, top-level and
    // swaps BrowsingInstances, thus should clear window.name.
    EXPECT_TRUE(NavigateToURL(shell(), url_c));
    auto* frame_c = web_contents->GetMainFrame();
    // Check that b.com/title1.html and c.com/title1.html are in different
    // BrowsingInstances.
    scoped_refptr<SiteInstance> site_instance_c = frame_c->GetSiteInstance();
    EXPECT_FALSE(site_instance_b->IsRelatedSiteInstance(site_instance_c.get()));
    // Window.name should be cleared.
    EXPECT_EQ("", frame_c->GetFrameName());
  }
  
  // Tests that enable clearing window.name on on cross-site
  // cross-BrowsingInstance navigations when
  // ProactivelySwapBrowsingInstancesSameSite is enabled.
  class ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest
      : public ProactivelySwapBrowsingInstancesSameSiteTest {
   public:
    ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest() {
      feature_list_.InitAndEnableFeature(
          features::kClearCrossBrowsingContextGroupMainFrameName);
    }
    ~ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest() override =
        default;
  
   private:
    base::test::ScopedFeatureList feature_list_;
  };
  
  // Verify that same-site main frame navigation that swaps BrowsingInstances
  // does not clear window.name.
  IN_PROC_BROWSER_TEST_P(
      ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest,
      NotClearWindowNameSameSite) {
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html"));
    GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html"));
    WebContentsImpl* web_contents =
        static_cast<WebContentsImpl*>(shell()->web_contents());
  
    // Navigate to a.com/title1.html.
    EXPECT_TRUE(NavigateToURL(shell(), url_a1));
    // Set window.name.
    EXPECT_TRUE(content::ExecuteScript(web_contents, "window.name='foo'"));
    auto* frame_a1 = web_contents->GetMainFrame();
    EXPECT_EQ("foo", frame_a1->GetFrameName());
  
    scoped_refptr<SiteInstance> site_instance_a1 = frame_a1->GetSiteInstance();
  
    // Navigate to a.com/title2.html. Even though we proactively swap
    // BrowsingInstances for same-site navigation as well, we should only clear
    // window.name for cross-BrowsingInstance navigation that's not same-site.
    // https://html.spec.whatwg.org/multipage/browsing-the-web.html#resetBCName.
    EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_a2));
    auto* frame_a2 = web_contents->GetMainFrame();
    // Check that title1.html and title2.html are in different BrowsingInstances.
    scoped_refptr<SiteInstance> site_instance_a2 = frame_a2->GetSiteInstance();
    EXPECT_FALSE(site_instance_a1->IsRelatedSiteInstance(site_instance_a2.get()));
    // Window.name should not be cleared.
    EXPECT_EQ("foo", frame_a2->GetFrameName());
  }
  
  INSTANTIATE_TEST_SUITE_P(All,
                           RenderFrameHostManagerTest,
                           testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(
      All,
      ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest,
      testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(
      All,
      ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
      testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(All,
                           ProactivelySwapBrowsingInstancesSameSiteTest,
                           testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(All,
                           ProactivelySwapBrowsingInstancesSameSiteCoopTest,
                           testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(All,
                           RenderFrameHostManagerUnloadBrowserTest,
                           testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(All,
                           RenderFrameHostManagerSpoofingTest,
                           testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(All,
                           RFHMProcessPerTabTest,
                           testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(All,
                           RenderFrameHostManagerDefaultProcessTest,
                           testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(All,
                           RenderFrameHostManagerNoSiteIsolationTest,
                           testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(All,
                           RenderFrameHostManagerClearWindowNameTest,
                           testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  INSTANTIATE_TEST_SUITE_P(
      All,
      ProactivelySwapBrowsingInstancesSameSiteClearWindowNameTest,
      testing::ValuesIn(RenderDocumentFeatureLevelValues()));
  }  // namespace content
  