// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/render_frame_host_manager_browsertest.h"

#include <stddef.h>
#include <stdint.h>

#include <functional>
#include <memory>
#include <set>

#include "base/cfi_buildflags.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/memory/raw_ptr.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/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/browser/process_lock.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_entry_restore_context_impl.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_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/spare_render_process_host_manager_impl.h"
#include "content/browser/security/cpsp/child_process_security_policy_impl.h"
#include "content/browser/site_info.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/common/features.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/reload_type.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/security_principal.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/child_process_id.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/isolated_world_ids.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_content_browser_client.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/storage_partition_test_helpers.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/chrome_debug_urls.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"

using base::ASCIIToUTF16;

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 !AreStrictSiteInstancesEnabled() &&
         !CanCrossSiteNavigationsProactivelySwapBrowsingInstances();
}

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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, message_loop_runner_->QuitClosure());
    }
  }

 private:
  scoped_refptr<MessageLoopRunner> message_loop_runner_;
  bool deleted_;
  raw_ptr<RenderFrameHost, AcrossTasksDanglingUntriaged> 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(
      NavigationThrottleRegistry& registry)
      : NavigationThrottle(registry) {}

  RequestBlockingNavigationThrottle(const RequestBlockingNavigationThrottle&) =
      delete;
  RequestBlockingNavigationThrottle& operator=(
      const RequestBlockingNavigationThrottle&) = delete;

  static void Create(NavigationThrottleRegistry& registry) {
    registry.AddThrottle(
        std::make_unique<RequestBlockingNavigationThrottle>(registry));
  }

 private:
  ThrottleCheckResult WillStartRequest() override {
    return NavigationThrottle::BLOCK_REQUEST;
  }

  const char* GetNameForLogging() override {
    return "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.DeprecatedGetOriginAsURL();
  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())
          ->GetPrimaryFrameTree()
          .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 site_instance->GetProcess()->GetProcessLock().is_error_page();
}

// Simple test content browser client that allows tests to change process flags
// for specific hosts.
class ProcessFlagsContentBrowserClient
    : public ContentBrowserTestContentBrowserClient {
 public:
  bool IsJitDisabledForSite(content::BrowserContext* browser_context,
                            const GURL& site_url) override {
    return disabled_jit_hosts_.contains(std::string(site_url.host()));
  }

  void SetJitDisabledForHost(const std::string& host, bool disabled) {
    if (disabled) {
      disabled_jit_hosts_.insert(host);
    } else {
      disabled_jit_hosts_.erase(host);
    }
  }

  bool AreV8OptimizationsEnabledForSite(
      BrowserContext* browser_context,
      const std::optional<base::SafeRef<ProcessSelectionUserData>>&
          process_selection_user_data,
      const GURL& site_url) override {
    return !disabled_v8_opt_hosts_.contains(std::string(site_url.host()));
  }

  void SetV8OptimizationsDisabledForHost(const std::string& host,
                                         bool disabled) {
    if (disabled) {
      disabled_v8_opt_hosts_.insert(host);
    } else {
      disabled_v8_opt_hosts_.erase(host);
    }
  }

 private:
  std::set<std::string> disabled_jit_hosts_;
  std::set<std::string> disabled_v8_opt_hosts_;
};

}  // anonymous namespace

RenderFrameHostManagerTest::RenderFrameHostManagerTest() : foo_com_("foo.com") {
  replace_host_.SetHostStr(foo_com_);
  InitAndEnableRenderDocumentFeature(&feature_list_, GetParam());
}

RenderFrameHostManagerTest::~RenderFrameHostManagerTest() = default;

void RenderFrameHostManagerTest::SetUpOnMainThread() {
  // Support multiple sites on the test server.
  host_resolver()->AddRule("*", "127.0.0.1");
}

void RenderFrameHostManagerTest::DisableBackForwardCache(
    BackForwardCacheImpl::DisableForTestingReason reason) const {
  return static_cast<WebContentsImpl*>(shell()->web_contents())
      ->GetController()
      .GetBackForwardCache()
      .DisableForTesting(reason);
}

void RenderFrameHostManagerTest::StartServer() {
  ASSERT_TRUE(embedded_test_server()->Start());

  foo_host_port_ = embedded_test_server()->host_port_pair();
  foo_host_port_.set_host(foo_com_);
}

void RenderFrameHostManagerTest::StartEmbeddedServer() {
  SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());
}

std::unique_ptr<content::URLLoaderInterceptor>
RenderFrameHostManagerTest::SetupRequestFailForURL(const GURL& url) {
  return URLLoaderInterceptor::SetupRequestFailForURL(url,
                                                      net::ERR_DNS_TIMED_OUT);
}

// Returns a URL on foo.com with the given path.
GURL RenderFrameHostManagerTest::GetCrossSiteURL(const std::string& path) {
  GURL cross_site_url(embedded_test_server()->GetURL(path));
  return cross_site_url.ReplaceComponents(replace_host_);
}

void RenderFrameHostManagerTest::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().GetPort() + "/');";
  EXPECT_TRUE(ExecJs(shell, script));
}

// 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;
  EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteTargetedLink();"));
  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().GetPath());

  // 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.
  EXPECT_EQ(true, EvalJs(shell(), "testScriptAccessToWindow();"));

  // 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 (AreStrictSiteInstancesEnabled()) {
    EXPECT_NE(orig_site_instance, new_site_instance);
  } else {
    EXPECT_EQ(orig_site_instance, new_site_instance);
  }

  // We should no longer have script access to the opened window's location.
  EXPECT_EQ(false, EvalJs(shell(), "testScriptAccessToWindow();"));

  // We now navigate the window to an about:blank page.
  TestNavigationObserver navigation_observer(new_shell->web_contents());
  EXPECT_EQ(true, EvalJs(shell(), "clickBlankTargetedLink();"));

  // 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.
  EXPECT_EQ(true, EvalJs(shell(), "testScriptAccessToWindow();"));
}

// 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;
  EXPECT_EQ(true, EvalJs(shell(), "clickNoRefTargetBlankLink();"));

  // Wait for the window to open.
  Shell* new_shell = new_shell_observer.GetShell();

  EXPECT_EQ("/title2.html",
            new_shell->web_contents()->GetVisibleURL().GetPath());

  // Check that `window.opener` is not set.
  EXPECT_EQ(true, EvalJs(new_shell, "window.opener == null;"));

  // 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;
  EXPECT_EQ(true, EvalJs(shell(), "clickNoOpenerTargetBlankLink();"));

  // Wait for the window to open.
  Shell* new_shell = new_shell_observer.GetShell();

  EXPECT_EQ("/title2.html",
            new_shell->web_contents()->GetVisibleURL().GetPath());

  // Check that `window.opener` is not set.
  EXPECT_EQ(true, EvalJs(new_shell, "window.opener == null;"));

  // 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("/").DeprecatedGetOriginAsURL().spec();
  EXPECT_EQ(true, EvalJs(new_shell,
                         "document.referrer == '" + expected_referrer + "';"));

  // 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;
  // We should not get a reference to the opened window.
  EXPECT_EQ(
      false,
      EvalJs(
          shell(),
          "openWindowWithTargetAndFeatures('/title2.html', '', 'noopener');"));

  // 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().GetPath());

  // Check that `window.opener` is not set.
  EXPECT_EQ(true, EvalJs(new_shell, "window.opener == null;"));

  // Check that the referrer is set correctly.
  std::string expected_referrer =
      embedded_test_server()->GetURL("/click-noreferrer-links.html").spec();
  EXPECT_EQ(true, EvalJs(new_shell,
                         "document.referrer == '" + expected_referrer + "';"));

  // 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;
  EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteNoRefTargetedLink();"));

  // 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().GetPath());

  // Check that `window.opener` is not set.
  EXPECT_EQ(true, EvalJs(new_shell, "window.opener == null;"));

  // 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;
  EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteNoOpenerTargetedLink();"));

  // 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().GetPath());

  // Check that `window.opener` is not set.
  EXPECT_EQ(true, EvalJs(new_shell, "window.opener == null;"));

  // 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;
  EXPECT_EQ(true, EvalJs(shell(), "clickTargetBlankLink();"));

  // 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().GetPath());

  // Should have the same SiteInstance unless we're in site-per-process mode, or
  // default SiteInstanceGroups mode.
  scoped_refptr<SiteInstance> blank_site_instance(
      new_shell->web_contents()->GetSiteInstance());
  if (AreStrictSiteInstancesEnabled()) {
    EXPECT_NE(orig_site_instance, blank_site_instance);
  } else {
    EXPECT_EQ(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.
  EXPECT_EQ(true, EvalJs(shell(), "clickNoRefLink();"));

  // 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().GetPath());

  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.
  EXPECT_EQ(true, EvalJs(shell(), "clickNoRefLink();"));

  // 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().GetPath());

  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().GetPath());

  // 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;
  EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteTargetBlankLink();"));
  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().GetPath());

  // 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(ExecJs(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();
  }
  EXPECT_EQ(true, EvalJs(new_shell, "window.opener == null;"));
  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));
  EXPECT_EQ(true, EvalJs(new_shell, "window.opener == null;"));
  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(ExecJs(shell(), "window.open('about:blank','foo');"));

  // Check that the browser process updates the subframe's opener.
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();
  EXPECT_EQ(root, root->child_at(0)->opener());
  EXPECT_EQ(
      nullptr,
      root->child_at(0)->first_live_main_frame_in_original_opener_chain());

  // Now disown the frame's opener.  Shouldn't crash.
  EXPECT_TRUE(ExecJs(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)->first_live_main_frame_in_original_opener_chain());
}

// 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".
  EXPECT_EQ("foo", EvalJs(new_shell, "window.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".
  EXPECT_EQ("foo", EvalJs(new_shell, "window.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.
  EXPECT_EQ(true,
            EvalJs(new_shell2, "window.opener === window.open('','foo');"));
}

// 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)
          ->GetPrimaryFrameTree()
          .root()
          ->render_manager();

  // 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;
  EXPECT_EQ(true, EvalJs(opener_contents, "clickSameSiteTargetedLink();"));
  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().GetPath());
  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);
  EXPECT_NE(static_cast<SiteInstanceImpl*>(orig_site_instance.get())->group(),
            static_cast<SiteInstanceImpl*>(foo_site_instance.get())->group());

  // Second, a target=_blank window.
  ShellAddedObserver new_shell_observer2;
  EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteTargetBlankLink();"));

  // 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().GetPath());
  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)
          ->GetPrimaryFrameTree()
          .root()
          ->render_manager();

  // We now have three windows.  The opener should have a RenderFrameProxyHost
  // for the new SiteInstanceGroup, but the _blank window should not.
  EXPECT_EQ(3u, Shell::windows().size());
  EXPECT_TRUE(opener_manager->current_frame_host()
                  ->browsing_context_state()
                  ->GetRenderFrameProxyHost(
                      static_cast<SiteInstanceImpl*>(foo_site_instance.get())
                          ->group()));
  EXPECT_FALSE(new_manager->current_frame_host()
                   ->browsing_context_state()
                   ->GetRenderFrameProxyHost(
                       static_cast<SiteInstanceImpl*>(foo_site_instance.get())
                           ->group()));

  // 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_EQ(true,
            EvalJs(foo_contents, "postToOpener('msg', 'http://google.com');"));
  ASSERT_FALSE(opener_manager->current_frame_host()
                   ->browsing_context_state()
                   ->GetRenderFrameProxyHost(
                       static_cast<SiteInstanceImpl*>(orig_site_instance.get())
                           ->group()));

  // 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_EQ(true, EvalJs(foo_contents, "postToOpener('msg','*');"));
  ASSERT_FALSE(opener_manager->current_frame_host()
                   ->browsing_context_state()
                   ->GetRenderFrameProxyHost(
                       static_cast<SiteInstanceImpl*>(orig_site_instance.get())
                           ->group()));
  ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());

  // We should have received only 1 message in the opener and "foo" tabs,
  // and updated the title.
  EXPECT_EQ(1, EvalJs(opener_contents, "window.receivedMessages;"));
  EXPECT_EQ(1, EvalJs(foo_contents, "window.receivedMessages;"));
  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_EQ(true, EvalJs(new_contents, "postToFoo('msg2');"));
  ASSERT_EQ(expected_title, title_watcher2.WaitAndGetTitle());

  // This postMessage should have created a RenderFrameProxyHost for the new
  // SiteInstanceGroup in the target=_blank window.
  EXPECT_TRUE(new_manager->current_frame_host()
                  ->browsing_context_state()
                  ->GetRenderFrameProxyHost(
                      static_cast<SiteInstanceImpl*>(foo_site_instance.get())
                          ->group()));

  // 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)
          ->GetPrimaryFrameTree()
          .root()
          ->render_manager();

  // 1) Open a named target=foo window. We will later post a message between the
  // opener and the new window.
  ShellAddedObserver new_shell_observer;
  EXPECT_EQ(true, EvalJs(opener_contents, "clickSameSiteTargetedLink();"));
  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().GetPath());
  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 SiteInstanceGroup.
  EXPECT_EQ(2u, Shell::windows().size());
  EXPECT_TRUE(opener_manager->current_frame_host()
                  ->browsing_context_state()
                  ->GetRenderFrameProxyHost(
                      static_cast<SiteInstanceImpl*>(foo_site_instance.get())
                          ->group()));

  // 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_EQ(true, EvalJs(opener_contents, "postWithPortToFoo();"));
  ASSERT_FALSE(opener_manager->current_frame_host()
                   ->browsing_context_state()
                   ->GetRenderFrameProxyHost(
                       static_cast<SiteInstanceImpl*>(orig_site_instance.get())
                           ->group()));
  ASSERT_EQ(expected_title, title_observer.WaitAndGetTitle());

  // Check message counts.
  EXPECT_EQ(1, EvalJs(opener_contents, "window.receivedMessagesViaPort;"));
  EXPECT_EQ(1, EvalJs(foo_contents, "window.receivedMessages;"));
  EXPECT_EQ(1, EvalJs(foo_contents, "window.receivedMessagesWithPort;"));
  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;
  EXPECT_EQ(true, EvalJs(orig_contents, "clickSameSiteTargetedLink();"));
  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().GetPath());

  // 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_EQ(true, EvalJs(new_shell, "navigateOpener();"));
  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())
                            ->GetPrimaryFrameTree()
                            .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(ExecJs(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;
  EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteTargetedLink();"));
  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().GetPath());

  // 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().GetPath());
  NavigationEntry* current_entry =
      shell()->web_contents()->GetController().GetLastCommittedEntry();
  EXPECT_TRUE(!current_entry || current_entry->IsInitialEntry());

  // 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(ExecJs(
      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().GetPath());

  // 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(),
        ISOLATED_WORLD_ID_GLOBAL);
  }
};

// Helper to wait until a WebContent's NavigationController has a visible entry
// that is not the initial NavigationEntry.
class VisibleEntryWaiter : public WebContentsObserver {
 public:
  explicit VisibleEntryWaiter(WebContents* web_contents)
      : WebContentsObserver(web_contents) {}

  void Wait() {
    if (auto* entry = web_contents()->GetController().GetVisibleEntry()) {
      if (entry && !entry->IsInitialEntry()) {
        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().GetPath());

  // 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().GetPath());
}

// 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 the initial entry (or null), resulting in
  // about:blank in the address bar.
  NavigationEntry* visible_entry =
      new_shell->web_contents()->GetController().GetLastCommittedEntry();
  EXPECT_TRUE(!visible_entry || visible_entry->IsInitialEntry());
}

// 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 the initial entry (or null), resulting in
  // about:blank in the address bar.
  NavigationEntry* visible_entry =
      new_shell->web_contents()->GetController().GetLastCommittedEntry();
  EXPECT_TRUE(!visible_entry || visible_entry->IsInitialEntry());
}

// 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().GetPath());

  // 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 the initial entry (or null), resulting in
  // about:blank in the address bar.
  NavigationEntry* visible_entry =
      new_shell->web_contents()->GetController().GetLastCommittedEntry();
  EXPECT_TRUE(!visible_entry || visible_entry->IsInitialEntry());
}

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().LoadOriginalRequestURL();
  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);
  ASSERT_TRUE(second_navigation.WaitForNavigationFinished());
  const char kDiscardedStateJS[] = "window.document.wasDiscarded;";
  EXPECT_EQ(false, content::EvalJs(shell(), kDiscardedStateJS));
}

// 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(crbug.com/41448629): 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())
          ->GetPrimaryFrameTree()
          .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(ExecJs(shell()->web_contents(),
                     "location.href = \"" + kAttackURL.spec() + "\";",
                     EXECUTE_SCRIPT_NO_USER_GESTURE));
  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())
                        ->GetPrimaryFrameTree()
                        .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");
  ASSERT_TRUE(attack_navigation.WaitForNavigationFinished());
  speculative_rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
                        ->GetPrimaryFrameTree()
                        .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());
}

// If a navigation to a pending NavigationEntry is in pending commit, starting
// a new navigation to the same pending NavigationEntry should not crash. This
// is a regression test for crbug.com/796135.
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                       TwoNavigationsToSamePendingNavigationEntry) {
  if (!AreAllSitesIsolatedForTesting()) {
    GTEST_SKIP() << "This test requires speculative RenderFrameHosts, so skip "
                    "it when site isolation is turned off";
  }

  const std::string kOriginalPath = "/original.html";
  const std::string kFirstRedirectPath = "/redirect1.html";
  const std::string kSecondRedirectPath = "/redirect2.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().LoadOriginalRequestURL();
  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();
  EXPECT_TRUE(first_reload.WaitForRequestRedirected());
  first_reload.ResumeNavigation();
  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());

  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();
  RenderFrameHostImplWrapper first_speculative_rfh(
      root->render_manager()->speculative_frame_host());
  EXPECT_TRUE(first_speculative_rfh.get());

  // The user requests a new reload while the previous reload hasn't committed
  // yet. This second reload starts immediately after pausing the commit of the
  // first reload.
  TestNavigationManager second_reload(shell()->web_contents(), kOriginalURL);
  CommitNavigationPauser commit_pauser(first_speculative_rfh.get());
  first_reload.ResumeNavigation();
  commit_pauser.WaitForCommitAndPause();
  shell()->web_contents()->GetController().LoadOriginalRequestURL();
  EXPECT_TRUE(second_reload.WaitForRequestStart());

  RenderFrameHostImplWrapper second_speculative_rfh(
      root->render_manager()->speculative_frame_host());

  EXPECT_TRUE(second_speculative_rfh.get());
  // Navigation queueing means the first speculative RFH will be kept around as
  // it is pending commit.
  EXPECT_TRUE(first_speculative_rfh.get());
  EXPECT_EQ(first_speculative_rfh.get(), second_speculative_rfh.get());

  // 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");
  ASSERT_TRUE(second_reload.WaitForNavigationFinished());

  // Because of navigation queueing, the first reload's speculative RFH will be
  // kept.
  EXPECT_TRUE(root->render_manager()->speculative_frame_host());
}

// 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;
  EXPECT_EQ(true,
            EvalJs(orig_contents, "clickNoContentScriptedTargetedLink();"));

  // 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());
  // The visible entry should be the entry for the synchronously committed
  // about:blank, resulting in about:blank in the address bar.
  EXPECT_EQ(GURL(url::kAboutBlankURL),
            contents->GetController().GetVisibleEntry()->GetURL());
}

// 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(), embedded_test_server()->GetURL("/empty.html")));

  // 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<raw_ptr<RenderViewHost, SetExperimental>> 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()->GetPrimaryMainFrame()->GetRenderViewHost();
  SiteInstance* blank_site_instance =
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
  EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), GURL());
  EXPECT_EQ(blank_site_instance->GetSecurityPrincipal().GetDeprecatedSiteURL(),
            GURL());
  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()->GetPrimaryMainFrame()->GetRenderViewHost());
  EXPECT_NE(blank_site_instance,
            shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
  rvh_observers.EnsureRVHGetsDestructed(
      shell()->web_contents()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->GetSiteInstance();
  rvh_observers.EnsureRVHGetsDestructed(
      shell()->web_contents()->GetPrimaryMainFrame()->GetRenderViewHost());

  EXPECT_TRUE(NavigateToURL(shell(), view_source_url));
  rvh_observers.EnsureRVHGetsDestructed(
      shell()->web_contents()->GetPrimaryMainFrame()->GetRenderViewHost());
  SiteInstance* site_instance2 =
      shell()->web_contents()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->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;
  EXPECT_EQ(true, EvalJs(shell(), "openWindow();"));
  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().GetPath());

  // 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 BUILDFLAG(IS_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(blink::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()
          ->GetPrimaryMainFrame()
          ->GetProcess()
          ->GetDeprecatedID()));

  // Crash the renderer of the WebUI page.
  RenderProcessHostWatcher crash_observer(
      shell()->web_contents(),
      RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  EXPECT_TRUE(
      NavigateToURLAndExpectNoCommit(shell(), GURL(blink::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(blink::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(blink::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 BUILDFLAG(IS_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()
          ->GetPrimaryMainFrame()
          ->GetProcess()
          ->GetDeprecatedID()));

  // 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()
          ->GetPrimaryMainFrame()
          ->GetProcess()
          ->GetDeprecatedID()));
}

// 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()
          ->GetPrimaryMainFrame()
          ->GetProcess()
          ->GetDeprecatedID()));

  // 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()
          ->GetPrimaryMainFrame()
          ->GetProcess()
          ->GetDeprecatedID()));
}

// 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())
          ->GetPrimaryMainFrame();

  // Set up a slow unload handler to force the RFH to linger in the unloaded
  // but not-yet-deleted state.
  EXPECT_TRUE(ExecJs(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())
                            ->GetPrimaryFrameTree()
                            .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->current_frame_host()
                  ->browsing_context_state()
                  ->GetRenderFrameProxyHost(web_ui_site_instance->group()));

  // 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));
  ChildProcessId process_id =
      shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID();
  std::unique_ptr<FileChooserDelegate> delegate(
      new FileChooserDelegate(file, run_loop.QuitClosure()));
  shell()->web_contents()->SetDelegate(delegate.get());
  EXPECT_TRUE(ExecJs(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())
      ->GetPrimaryMainFrame()
      ->DisableUnloadTimerForTesting();

  // Navigate to a different process and wait for the old process to exit.
  RenderProcessHostWatcher exit_observer(
      shell()->web_contents()->GetPrimaryMainFrame()->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_REQUIRES_NO_CACHING);

  EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
  exit_observer.Wait();
  EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
      shell()->web_contents()->GetPrimaryMainFrame()->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 BUILDFLAG(IS_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));
  ChildProcessId process_id = wc->GetPrimaryMainFrame()->GetProcess()->GetID();
  std::unique_ptr<FileChooserDelegate> delegate(
      new FileChooserDelegate(file, run_loop.QuitClosure()));
  wc->SetDelegate(delegate.get());
  EXPECT_TRUE(ExecJs(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->GetPrimaryMainFrame()->DisableUnloadTimerForTesting();

  // Navigate to a different process without access to the file, and wait for
  // the old process to exit.
  RenderProcessHostWatcher exit_observer(
      wc->GetPrimaryMainFrame()->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_REQUIRES_NO_CACHING);

  EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
  exit_observer.Wait();
  EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
      wc->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->GetProcess()->GetID());

  // Ensure that the file access still exists in the new process ID.
  EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
      wc->GetPrimaryMainFrame()->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));
  ChildProcessId process_id = wc->GetPrimaryMainFrame()->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(ExecJs(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->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->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->GetPrimaryFrameTree().root();
  ChildProcessId process_id =
      shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID();
  std::unique_ptr<FileChooserDelegate> delegate(
      new FileChooserDelegate(file, run_loop.QuitClosure()));
  shell()->web_contents()->SetDelegate(delegate.get());
  EXPECT_TRUE(ExecJs(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(ExecJs(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()->GetPrimaryMainFrame()->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_REQUIRES_NO_CACHING);

  EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteURL("/title1.html")));
  exit_observer.Wait();
  EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
      shell()->web_contents()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->GetProcess()->GetID());

  // Ensure that the file access still exists in the new process ID.
  EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
      shell()->web_contents()->GetPrimaryMainFrame()->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(ExecJs(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(), /* initiator_origin= */ std::nullopt,
              /* initiator_base_url= */ std::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);

  NavigationEntryRestoreContextImpl context;
  cloned_entry->SetPageState(prev_entry->GetPageState(), &context);
  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(), nullptr, gfx::Size());
  FrameTreeNode* new_root =
      static_cast<WebContentsImpl*>(new_shell->web_contents())
          ->GetPrimaryFrameTree()
          .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.
  EXPECT_EQ("bar",
            EvalJs(new_root->child_at(0),
                   "document.getElementById('fileinput').files[0].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())
                            ->GetPrimaryFrameTree()
                            .root();
  auto orig_site_instance_id =
      root->current_frame_host()->GetSiteInstance()->GetId();
  int initial_process_id = root->current_frame_host()
                               ->GetSiteInstance()
                               ->GetProcess()
                               ->GetDeprecatedID();
  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_REQUIRES_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())
                            ->GetPrimaryFrameTree()
                            .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())
          ->GetPrimaryFrameTree()
          .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 SiteInstanceGroup
  // should not be live.
  RenderFrameHostManager* opener_manager = root->render_manager();
  RenderFrameProxyHost* opener_rfph =
      opener_manager->current_frame_host()
          ->browsing_context_state()
          ->GetRenderFrameProxyHost(
              static_cast<SiteInstanceImpl*>(foo_site_instance.get())->group());
  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 SiteInstanceGroup.
  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())
                            ->GetPrimaryFrameTree()
                            .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())
          ->GetPrimaryFrameTree()
          .root();
  FrameTreeNode* bar_root =
      static_cast<WebContentsImpl*>(bar_shell->web_contents())
          ->GetPrimaryFrameTree()
          .root();
  EXPECT_EQ(root, foo_root->opener());
  EXPECT_EQ(root, foo_root->first_live_main_frame_in_original_opener_chain());
  EXPECT_EQ(root, bar_root->opener());
  EXPECT_EQ(root, bar_root->first_live_main_frame_in_original_opener_chain());

  // 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.
  EXPECT_EQ(true, EvalJs(bar_shell, "!!window.open('','foo');"));
  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->first_live_main_frame_in_original_opener_chain());

  // 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);
  EXPECT_EQ(true, EvalJs(foo_shell, "postToOpener('opener-msg', '*');"));
  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(ExecJs(foo_shell, "window.opener = window;"));
  EXPECT_EQ(bar_root, foo_root->opener());
  EXPECT_EQ(root, foo_root->first_live_main_frame_in_original_opener_chain());
}

// 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;
  EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteTargetedLink();"));
  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().GetPath());

  // 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(ExecJs(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.
  EXPECT_EQ(expected_url.spec(),
            EvalJs(shell(), "getLastOpenedWindowLocation();"));
}

// 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"));
  GURL popup_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Open a popup to navigate.
  Shell* new_shell = OpenPopup(shell(), popup_url, "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
// `blink::WebView` and `blink::RemoteFrame` 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())
          ->GetPrimaryFrameTree()
          .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
  // `blink::WebView` 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())
                            ->GetPrimaryFrameTree()
                            .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(ExecJs(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())
          ->GetPrimaryFrameTree()
          .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();
  scoped_refptr<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->current_frame_host()
                  ->browsing_context_state()
                  ->GetRenderFrameProxyHost(site_instance_a->group()));

  // 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->current_frame_host()
                  ->browsing_context_state()
                  ->GetRenderFrameProxyHost(site_instance_a->group()));
  EXPECT_FALSE(root->current_frame_host()
                   ->browsing_context_state()
                   ->GetRenderFrameProxyHost(site_instance_a->group())
                   ->is_render_frame_proxy_live());

  // Close the popup so there is no proxy for a.com in the original tab.
  new_shell->Close();

  // Verify that there are no proxies, meaning there's no proxy for a.com. At
  // this point, |site_instance_group_a| has been freed, so searching the proxy
  // host map using it isn't an option.
  EXPECT_EQ(nullptr, site_instance_a->group());
  EXPECT_EQ(0u, root->current_frame_host()
                    ->browsing_context_state()
                    ->proxy_hosts()
                    .size());

  // This should delete the RVH as well. Check this by verifying that there's
  // only one RVH in the frame tree, and it's for the current SiteInstanceGroup,
  // not |site_instance_group_a|.
  EXPECT_TRUE(root->frame_tree().GetRenderViewHost(
      root->current_frame_host()->GetSiteInstance()->group()));
  EXPECT_EQ(1u, root->frame_tree().render_view_host_map_.size());

  // 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())
                            ->GetPrimaryFrameTree()
                            .root();

  // Give an initial page a pagehide handler that never completes.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
  EXPECT_TRUE(ExecJs(root, "window.onpagehide=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_REQUIRES_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->current_frame_host()
                  ->browsing_context_state()
                  ->GetRenderFrameProxyHost(site_instance_a->group()));

  // 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->current_frame_host()
                  ->browsing_context_state()
                  ->GetRenderFrameProxyHost(site_instance_a->group()));

  // 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()->GetPrimaryMainFrame()->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->GetPrimaryFrameTree()
                                      .root()
                                      ->render_manager()
                                      ->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->GetPrimaryFrameTree()
                          .root()
                          ->render_manager()
                          ->speculative_frame_host());
  navigation_observer.Wait();
  EXPECT_EQ(cross_site_url2, web_contents->GetLastCommittedURL());
  EXPECT_EQ(next_rfh, web_contents->GetPrimaryMainFrame());
  EXPECT_FALSE(web_contents->GetPrimaryFrameTree()
                   .root()
                   ->render_manager()
                   ->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())
                            ->GetPrimaryFrameTree()
                            .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(ExecJs(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().GetPort() + "/');";
  EXPECT_TRUE(ExecJs(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;
    EXPECT_EQ(true, EvalJs(root->child_at(0), link_opening_script));

    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.
    EXPECT_EQ("null", EvalJs(new_shell, "self.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;
  EXPECT_EQ(true, EvalJs(shell(),
                         "const result = clickSameSiteTargetedLink();"
                         "saveWindowReference();"
                         "result;"));
  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().GetPath());

  // 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 = EvalJs(shell(),
                              "(function() {\n"
                              "  try {\n"
                              "    return getLastOpenedWindowLocation();\n"
                              "  } catch (e) {\n"
                              "    return e.toString();\n"
                              "  }\n"
                              "})()")
                           .ExtractString();
  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(
      ExecJs(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->GetPrimaryFrameTree().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(ExecJs(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 BUILDFLAG(IS_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->GetPrimaryFrameTree().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(file_url.SchemeIsFile());
  ASSERT_TRUE(NavigateToURL(shell(), file_url));
  EXPECT_EQ(1, web_contents->GetController().GetEntryCount());
  EXPECT_TRUE(
      ExecJs(root, "history.pushState({}, '', 'https://chromium.org');"));
  ASSERT_TRUE(web_contents->GetPrimaryMainFrame()->IsRenderFrameLive());
  EXPECT_EQ(2, web_contents->GetController().GetEntryCount());

  // At this point, we should still consider the current origin to be file://,
  // so that subsequent web or file URLs would still be legal for same-document
  // navigations.  See https://crbug.com/553418.
  const url::Origin file_origin = url::Origin::Create(file_url);
  EXPECT_TRUE(file_origin.IsSameOriginWith(
      web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin()));
  EXPECT_TRUE(ExecJs(root, "history.pushState({}, '', 'https://foo.com');"));
  ASSERT_TRUE(web_contents->GetPrimaryMainFrame()->IsRenderFrameLive());
  EXPECT_EQ(3, web_contents->GetController().GetEntryCount());
  EXPECT_TRUE(
      ExecJs(root, JsReplace("history.pushState({}, '', $1);", file_url)));
  ASSERT_TRUE(web_contents->GetPrimaryMainFrame()->IsRenderFrameLive());
  EXPECT_EQ(4, web_contents->GetController().GetEntryCount());

  // Illegal schemes would not normally be allowed to commit by CanCommitURL,
  // but they are granted an exception if allow_universal_access_from_file_urls
  // is in use.
  GURL illegal_url("google:com");
  EXPECT_TRUE(ExecJs(
      root, JsReplace("history.replaceState({}, '', $1);", illegal_url)));
  ASSERT_TRUE(web_contents->GetPrimaryMainFrame()->IsRenderFrameLive());
  EXPECT_EQ(4, web_contents->GetController().GetEntryCount());

  // Illegal schemes should also work for document.open on same-origin frames,
  // where the initiator's URL is inherited (in the renderer process).
  std::string create_frame_and_open_script =
      "var new_iframe = document.createElement('iframe');"
      "document.documentElement.appendChild(new_iframe);"
      "new_iframe.contentDocument.open();";
  EXPECT_TRUE(ExecJs(shell(), create_frame_and_open_script));
  EXPECT_EQ(
      illegal_url,
      root->child_at(0)->current_frame_host()->last_document_url_in_renderer());
  // Ensure the renderer process has not crashed.
  ASSERT_TRUE(ExecJs(shell(), "true"));
  ASSERT_TRUE(root->child_at(0)->current_frame_host()->IsRenderFrameLive());

  // Now disable universal access, while still allowing file URLs to access each
  // other. This generally turns off the exemption from commit-time security
  // checks, while still allowing document.open to work in file:// origins.
  prefs.allow_universal_access_from_file_urls = false;
  prefs.allow_file_access_from_file_urls = true;
  web_contents->SetWebPreferences(prefs);

  // Calling document.open on another iframe should remember that the process
  // already had an exemption for file:// origins and continue to work.
  // See https://crbug.com/326250356#comment26.
  EXPECT_TRUE(ExecJs(shell(), create_frame_and_open_script));
  EXPECT_EQ(
      illegal_url,
      root->child_at(1)->current_frame_host()->last_document_url_in_renderer());
  // Ensure the renderer process has not crashed.
  ASSERT_TRUE(ExecJs(shell(), "true"));
  ASSERT_TRUE(root->child_at(1)->current_frame_host()->IsRenderFrameLive());
}

// Check that file:// URLs are correctly isolated. This test runs with the
// default site isolation of the platform, e.g. enabled on desktop and disabled
// on Android.
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest, FileURLIsolation) {
  StartEmbeddedServer();

  // Navigate to a main frame file:// URL.
  GURL file_url = GetTestUrl("", "title1.html");
  ASSERT_TRUE(file_url.SchemeIsFile());
  ASSERT_TRUE(NavigateToURL(shell(), file_url));
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  ChildProcessId file_process_id =
      web_contents->GetPrimaryMainFrame()->GetProcess()->GetID();

  // Navigate to a regular web page, a.com, with a same-site subframe.
  GURL a_url(embedded_test_server()->GetURL("a.com", "/page_with_iframe.html"));
  ASSERT_TRUE(NavigateToURL(shell(), a_url));

  // The file:// URL and a.com should be in different processes, to avoid file
  // permission grants accumulating on a single process.
  RenderProcessHost* a_process =
      web_contents->GetPrimaryMainFrame()->GetProcess();
  EXPECT_NE(a_process->GetID(), file_process_id);
  // With full site isolation, the process should be locked to the file:// URL
  // like any other site. Otherwise, it will be in an unlocked process. On
  // Android WebView (though this test doesn't run there), navigating to a
  // file:// URL should not cause a process swap.
  EXPECT_EQ(a_process->IsProcessLockedToSiteForTesting(),
            AreAllSitesIsolatedForTesting());

  // Do a browser initiated navigation of the a.com subframe to a file:// URL.
  FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
  scoped_refptr<SiteInstanceImpl> root_instance =
      root->current_frame_host()->GetSiteInstance();
  FrameTreeNode* child = root->child_at(0);
  NavigateFrameToURL(child, file_url);

  scoped_refptr<SiteInstanceImpl> child_instance =
      child->current_frame_host()->GetSiteInstance();
  RenderProcessHost* child_process = child->current_frame_host()->GetProcess();

  // With full site isolation, the file:// URL should get its own process.
  // Otherwise, it should share a process with the parent.
  if (AreAllSitesIsolatedForTesting()) {
    EXPECT_NE(a_process, child_process);

    // With full site isolation, the file:// subframe should be in a different
    // SiteInstance from the parent.
    EXPECT_NE(root_instance, child_instance);
    EXPECT_NE(root_instance->group(), child_instance->group());
  } else {
    // TODO(crbug.com/40704573): When default SiteInstanceGroup supports file://
    // URLs on Android WebView and low-end Clank, the file:// SiteInstance
    // should be in the default SiteInstanceGroup when site isolation is not
    // enabled.
    EXPECT_EQ(a_process, child_process);

    // With default SiteInstanceGroup enabled, the file:// subframe should be in
    // a different SiteInstance from the parent. Otherwise it should share a
    // site instance with the parent.
    if (ShouldUseDefaultSiteInstanceGroup()) {
      EXPECT_NE(root_instance, child_instance);
      EXPECT_NE(root_instance->group(), child_instance->group());
    } else {
      EXPECT_EQ(root_instance, child_instance);
      EXPECT_EQ(root_instance->group(), child_instance->group());
    }
  }
}

// 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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->IsRenderFrameLive());
  EXPECT_EQ(popup->web_contents()->GetPrimaryMainFrame()->GetSiteInstance(),
            shell()->web_contents()->GetPrimaryMainFrame()->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_REQUIRES_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->GetPrimaryFrameTree().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->GetPrimaryMainFrame());

  // 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"));
  TestNavigationManager navigation_manager(web_contents, url_b);
  RenderFrameDeletedObserver deleted_observer(rfh_a);
  shell()->LoadURL(url_b);
  navigation_manager.WaitForSpeculativeRenderFrameHostCreation();

  // The speculative RFH shouldn'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.
  ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
  EXPECT_EQ(url::Origin::Create(url_b), rfh_b->GetLastCommittedOrigin());
  EXPECT_EQ(rfh_b, web_contents->GetPrimaryMainFrame());

  // 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 (ShouldCreateNewHostForAllFrames()) {
    // If main-frame RenderDocument is enabled, the navigation will result in a
    // new RFH.
    EXPECT_NE(rfh_b, web_contents->GetPrimaryMainFrame());
    rfh_b = web_contents->GetPrimaryMainFrame();
  } else {
    EXPECT_EQ(rfh_b, web_contents->GetPrimaryMainFrame());
  }
  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 child_commit_observer(root->child_at(0));
    EXPECT_TRUE(ExecJs(child, "location.href = '" + url_c.spec() + "';"));
    child_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->GetPrimaryFrameTree().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 (AreAllSitesIsolatedForTesting()) {
    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 if (ShouldUseDefaultSiteInstanceGroup()) {
    // No proxies needed for different SiteInstance in the same
    // SiteInstanceGroup.
    EXPECT_EQ(
        " Site A\n"
        "   +--Site B\n"
        "        +--Site A\n"
        "             +--Site B\n"
        "                  +--Site B\n"
        "Where A = http://a.com/\n"
        "      B = http://b.com/",
        DepictFrameTree(*root));
  } else {
    const GURL kExpectedSiteURL = AreAllSitesIsolatedForTesting()
                                      ? GURL("http://a.com/")
                                      : SiteInstanceImpl::GetDefaultSiteURL();
    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_TRUE(bottom_child->is_on_initial_empty_document());
}

// 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->GetPrimaryFrameTree().root();
  FrameTreeNode* child = root->child_at(0);

  // ExecJs 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(ExecJs(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(
      ExecJs(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 = AreStrictSiteInstancesEnabled()
                                    ? GURL("http://a.com/")
                                    : SiteInstanceImpl::GetDefaultSiteURL();
  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->GetPrimaryFrameTree().root();

  // The third navigation should fail and be cancelled, leaving a FrameTree with
  // a height of 2.
  const GURL kExpectedSiteURL = AreStrictSiteInstancesEnabled()
                                    ? GURL("http://a.com/")
                                    : SiteInstanceImpl::GetDefaultSiteURL();
  // The FrameTreeVisualizer test ensure that the childmost frame is not loaded.
  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());

  // The frame is no longer on the initial empty document.
  EXPECT_FALSE(root->child_at(0)->child_at(0)->is_on_initial_empty_document());
}

// 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->GetPrimaryFrameTree().root();
  FrameTreeNode* child = root->child_at(0);

  TestFrameNavigationObserver observer1(child);
  EXPECT_TRUE(ExecJs(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(ExecJs(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->GetPrimaryFrameTree().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(ExecJs(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(ExecJs(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);
  cross_site_navigation.WaitForSpeculativeRenderFrameHostCreation();

  RenderFrameHostImpl* speculative_rfh =
      static_cast<WebContentsImpl*>(shell()->web_contents())
          ->GetPrimaryFrameTree()
          .root()
          ->render_manager()
          ->speculative_frame_host();
  CHECK(speculative_rfh);
  shell()->web_contents()->GetController().LoadURL(
      GURL("javascript:(0)"), Referrer(), ui::PAGE_TRANSITION_TYPED,
      std::string());

  ASSERT_TRUE(cross_site_navigation.WaitForNavigationFinished());
  // 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()->GetPrimaryMainFrame();
  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()->GetPrimaryMainFrame();
  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(ExecJs(tab1, "window.name = 'tab1';"));
  EXPECT_TRUE(ExecJs(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;
  EXPECT_EQ(url::kAboutBlankURL, EvalJs(tab1,
                                        "var w = window.open('', 'tab2');\n"
                                        "w.location.href;"));
  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> updated_curr_instance(
        popup->web_contents()->GetSiteInstance());
    EXPECT_NE(a_site_instance, updated_curr_instance);
    EXPECT_FALSE(
        a_site_instance->IsRelatedSiteInstance(updated_curr_instance.get()));
    EXPECT_NE(prev_instance, updated_curr_instance);
    EXPECT_FALSE(
        prev_instance->IsRelatedSiteInstance(updated_curr_instance.get()));
  }
}

// Verifies that a renderer-initiated navigation from a site in the default
// StoragePartition to one that ContentBrowserClient places in a non-default
// StoragePartition will swap to a new BrowsingInstance.
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                       NavigationToDifferentPartitionSwapsBrowsingInstance) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Set up a ContentBrowserClient that maps b.com to a non-default partition.
  CustomStoragePartitionBrowserClient modified_client(GURL("http://b.com/"));

  // Load a page on a.com and verify that it uses the default partition.
  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), a_url));
  RenderFrameHostImpl* rfh =
      static_cast<WebContentsImpl*>(shell()->web_contents())
          ->GetPrimaryMainFrame();
  SiteInstanceImpl* a_site_instance = rfh->GetSiteInstance();
  EXPECT_TRUE(a_site_instance->GetSecurityPrincipal()
                  .GetStoragePartitionConfig()
                  .is_default());

  // Make sure proactive BrowserInstance swapping doesn't interfere.
  rfh->DisableProactiveBrowsingInstanceSwapForTesting();

  // Do a renderer-initiated navigation to a page on b.com and verify that we
  // swapped BrowsingInstances.
  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
  rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
            ->GetPrimaryMainFrame();
  SiteInstanceImpl* b_site_instance = rfh->GetSiteInstance();
  EXPECT_FALSE(b_site_instance->GetSecurityPrincipal()
                   .GetStoragePartitionConfig()
                   .is_default());
  EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance));
}

// Verifies that iframes inherit their StoragePartition, even if
// ContentBrowserClient would normally place the iframe's URL in a dedicated
// StoragePartition.
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                       SubframeInheritsStoragePartition) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Set up a ContentBrowserClient that maps b.com to a non-default partition.
  CustomStoragePartitionBrowserClient modified_client(GURL("http://b.com/"));

  // Load a page on b.com to verify that it uses the correct partition.
  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), b_url));
  RenderFrameHostImpl* rfh =
      static_cast<WebContentsImpl*>(shell()->web_contents())
          ->GetPrimaryMainFrame();
  EXPECT_FALSE(rfh->GetSiteInstance()
                   ->GetSecurityPrincipal()
                   .GetStoragePartitionConfig()
                   .is_default());

  // Load a page on a.com that iframes a b.com page.
  GURL a_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  EXPECT_TRUE(NavigateToURL(shell(), a_url));
  rfh = static_cast<WebContentsImpl*>(shell()->web_contents())
            ->GetPrimaryMainFrame();
  SiteInstanceImpl* a_site_instance = rfh->GetSiteInstance();
  if (AreStrictSiteInstancesEnabled()) {
    EXPECT_EQ("http://a.com/",
              a_site_instance->GetSecurityPrincipal().GetDeprecatedSiteURL());
  } else {
    EXPECT_TRUE(a_site_instance->IsDefaultSiteInstance());
  }
  EXPECT_TRUE(a_site_instance->GetSecurityPrincipal()
                  .GetStoragePartitionConfig()
                  .is_default());

  // Verify that the iframe uses the default StoragePartition.
  EXPECT_EQ(1UL, rfh->child_count());
  SiteInstanceImpl* b_site_instance = static_cast<SiteInstanceImpl*>(
      rfh->child_at(0)->current_frame_host()->GetSiteInstance());
  if (AreStrictSiteInstancesEnabled()) {
    EXPECT_EQ("http://b.com/",
              b_site_instance->GetSecurityPrincipal().GetDeprecatedSiteURL());
  } else {
    EXPECT_TRUE(b_site_instance->IsDefaultSiteInstance());
  }
  EXPECT_TRUE(b_site_instance->GetSecurityPrincipal()
                  .GetStoragePartitionConfig()
                  .is_default());
}

// 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, unless we do a proactive BrowsingInstance swap due to
  // back/forward cache.
  EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
  if (IsBackForwardCacheEnabled()) {
    site_instance = shell()->web_contents()->GetSiteInstance();
  } else {
    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->GetSecurityPrincipal().GetDeprecatedSiteURL(),
              shell()
                  ->web_contents()
                  ->GetSiteInstance()
                  ->GetSecurityPrincipal()
                  .GetDeprecatedSiteURL());
  }
}

// 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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->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->GetOrCreateProcessForTesting()
                  ->GetDeprecatedID(),
              error_site_instance->GetProcess()->GetDeprecatedID());
    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()->GetPrimaryMainFrame()->GetSiteInstance();
  EXPECT_FALSE(HasErrorPageProcessLock(success_site_instance.get()));

  {
    NavigationHandleObserver observer(shell()->web_contents(), error_url);
    TestFrameNavigationObserver frame_observer(shell()->web_contents());
    EXPECT_TRUE(ExecJs(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()->GetPrimaryMainFrame()->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->GetOrCreateProcessForTesting()
                  ->GetDeprecatedID(),
              error_site_instance->GetProcess()->GetDeprecatedID());
    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->GetPrimaryFrameTree().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(ExecJs(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())
                            ->GetPrimaryFrameTree()
                            .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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->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));
  }

  // Create 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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->GetSiteInstance();
  url::Origin expected_origin =
      shell()->web_contents()->GetPrimaryMainFrame()->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);
  {
    TestNavigationObserverInternal 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_MAIN_FRAME_EXISTING_ENTRY,
              reload_observer.last_navigation_type());
  }
  EXPECT_EQ(3, nav_controller.GetEntryCount());
  EXPECT_EQ(1, nav_controller.GetLastCommittedEntryIndex());
  ChildProcessId process_id =
      shell()->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID();
  EXPECT_TRUE(HasErrorPageProcessLock(
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));

  // Reload while it will still fail to ensure it stays in the same process.
  {
    TestNavigationObserverInternal 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_MAIN_FRAME_EXISTING_ENTRY,
              reload_observer.last_navigation_type());
  }
  EXPECT_EQ(
      process_id,
      shell()->web_contents()->GetPrimaryMainFrame()->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();
  {
    TestNavigationObserverInternal 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_MAIN_FRAME_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()->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_FALSE(HasErrorPageProcessLock(
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_EQ(
      expected_origin,
      shell()->web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());

  // Test the same scenario as above, but this time initiated by the
  // renderer process.
  url_interceptor = SetupRequestFailForURL(error_url);
  {
    TestNavigationObserverInternal 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_MAIN_FRAME_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()->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_TRUE(HasErrorPageProcessLock(
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));

  url_interceptor.reset();
  {
    TestNavigationObserverInternal reload_observer(shell()->web_contents());
    EXPECT_TRUE(ExecJs(shell(), "location.reload();"));
    reload_observer.Wait();
    EXPECT_TRUE(reload_observer.last_navigation_succeeded());
    EXPECT_EQ(NavigationType::NAVIGATION_TYPE_MAIN_FRAME_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()->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_FALSE(HasErrorPageProcessLock(
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_EQ(
      expected_origin,
      shell()->web_contents()->GetPrimaryMainFrame()->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 sneaking in extra two
  // navigations.
  EXPECT_EQ(3, nav_controller.GetEntryCount());

  // Grab child's site URL.
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();
  FrameTreeNode* child = root->child_at(0);
  GURL child_site_url = child->current_frame_host()
                            ->GetSiteInstance()
                            ->GetSecurityPrincipal()
                            .GetDeprecatedSiteURL();

  // 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()
                                  ->GetSecurityPrincipal()
                                  .GetDeprecatedSiteURL());
  }

  // 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()
                                  ->GetSecurityPrincipal()
                                  .GetDeprecatedSiteURL());
  }
}

// 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(),
                           std::vector<std::string>{"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 sneaking in extra
  // two navigations.
  EXPECT_EQ(3, nav_controller.GetEntryCount());

  // Grab child's site URL.
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();
  FrameTreeNode* child1 = root->child_at(0);
  FrameTreeNode* child2 = root->child_at(1);
  std::string_view a_site_host = root->current_frame_host()
                                     ->GetSiteInstance()
                                     ->GetSecurityPrincipal()
                                     .GetHost();
  EXPECT_EQ("a.com", a_site_host);
  std::string_view b_site_host = child2->current_frame_host()
                                     ->GetSiteInstance()
                                     ->GetSecurityPrincipal()
                                     .GetHost();
  EXPECT_EQ("b.com", b_site_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. The precursor of that
    // origin depends on whether subframe error page isolation is enabled.
    const url::Origin& child1_origin =
        child1->current_frame_host()->GetLastCommittedOrigin();
    EXPECT_TRUE(child1_origin.opaque());
    if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(
            /*in_main_frame=*/false)) {
      // When error page isolation is on, the error commits in its own isolated
      // process. In this case, the error origin's precursor is currently
      // derived from the failing URL (on c.com) for both main frame and
      // subframe cases, which is safe, since there's no attacker code running
      // in that process.
      EXPECT_TRUE(IsOriginOpaqueAndCompatibleWithURL(child1, test_url));
    } else {
      // When subframe error page isolation is off, the error stays in the
      // current process, so when B2 navigates B1 to C, it stays in B's process.
      // The origin's precursor should be empty, and more specifically, it
      // should not be C, to guard against compromised renderers gaining access
      // to cross-site precursors (crbug.com/502348223).
      EXPECT_TRUE(
          child1_origin.GetTupleOrPrecursorTupleIfOpaque().GetURL().is_empty());
    }
    // 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();

    std::string_view c_site_host =
        child1_site_instance->GetSecurityPrincipal().GetHost();
    if (AreAllSitesIsolatedForTesting()) {
      EXPECT_EQ("c.com", c_site_host);
      EXPECT_EQ(test_url.host(), c_site_host);
    } else if (ShouldUseDefaultSiteInstanceGroup()) {
      EXPECT_EQ(
          child1_site_instance->group(),
          child1_site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
    } else {
      EXPECT_TRUE(child1_site_instance->IsDefaultSiteInstance());
    }
    EXPECT_NE(a_site_host, c_site_host);
    EXPECT_NE(b_site_host, c_site_host);
  }
}

// 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...
    TestNavigationObserverInternal 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_MAIN_FRAME_NEW_ENTRY,
                reload_observer.last_navigation_type());
    } else {
      EXPECT_EQ(NavigationType::NAVIGATION_TYPE_MAIN_FRAME_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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_TRUE(HasErrorPageProcessLock(
      shell()->web_contents()->GetPrimaryMainFrame()->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));
  SiteInstanceImpl* site_instance = static_cast<SiteInstanceImpl*>(
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance());
  EXPECT_FALSE(HasErrorPageSiteInfo(site_instance));

  // Verify that we get the default SiteInstance or SiteInstanceGroup because
  // the original URL does not require a dedicated process.
  if (!AreAllSitesIsolatedForTesting()) {
    if (ShouldUseDefaultSiteInstanceGroup()) {
      EXPECT_EQ(site_instance->group(),
                site_instance->DefaultSiteInstanceGroupForBrowsingInstance());
    } else {
      EXPECT_TRUE(site_instance->IsDefaultSiteInstance());
    }
  }

  EXPECT_EQ(
      success_site_instance->GetSecurityPrincipal().GetDeprecatedSiteURL(),
      site_instance->GetSecurityPrincipal().GetDeprecatedSiteURL());
  EXPECT_NE(success_site_instance, site_instance);

  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()->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_TRUE(HasErrorPageProcessLock(
      shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_EQ(4, nav_controller.GetEntryCount());
  {
    TestNavigationObserver observer(shell()->web_contents());
    EXPECT_TRUE(ExecJs(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()->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_TRUE(HasErrorPageProcessLock(
      web_contents->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_EQ(2, nav_controller.GetEntryCount());
  EXPECT_TRUE(IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));

  // Terminate the renderer process.
  {
    RenderProcessHostWatcher termination_observer(
        web_contents->GetPrimaryMainFrame()->GetProcess(),
        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    web_contents->GetPrimaryMainFrame()->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());

  EXPECT_EQ(error_url.spec(), EvalJs(shell(), "location.href;"));
}

// 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_REQUIRES_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->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_TRUE(HasErrorPageProcessLock(
      web_contents->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_TRUE(HasErrorPageProcessLock(
      web_contents->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_FALSE(HasErrorPageProcessLock(
      web_contents->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->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()->GetDeprecatedID()));
  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()->GetPrimaryMainFrame()->GetSiteInstance();
    EXPECT_TRUE(HasErrorPageSiteInfo(error_site_instance.get()));
    EXPECT_FALSE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(
        error_site_instance->GetProcess()->GetDeprecatedID()));
  }

  // 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()->GetPrimaryMainFrame()->GetSiteInstance();
  EXPECT_EQ(webui_url,
            webui_site_instance->GetSecurityPrincipal().GetDeprecatedSiteURL());
  EXPECT_TRUE(ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(
      webui_site_instance->GetProcess()->GetDeprecatedID()));

  // 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 ContentBrowserTestContentBrowserClient {
 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(
      const PageEffectiveURLContentBrowserClient&) = delete;
  PageEffectiveURLContentBrowserClient& operator=(
      const PageEffectiveURLContentBrowserClient&) = delete;

  ~PageEffectiveURLContentBrowserClient() override = default;

 private:
  std::optional<GURL> GetEffectiveURL(BrowserContext* browser_context,
                                      const GURL& url) override {
    if (url.EqualsIgnoringRef(url_to_modify_)) {
      return url_to_return_;
    }
    return std::nullopt;
  }

  GURL url_to_modify_;
  GURL url_to_return_;
};

// 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);

  // 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->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->GetSiteInstance());
}

// 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 ContentBrowserTestContentBrowserClient {
 public:
  BrowsingInstanceSwapContentBrowserClient() = default;

  BrowsingInstanceSwapContentBrowserClient(
      const BrowsingInstanceSwapContentBrowserClient&) = delete;
  BrowsingInstanceSwapContentBrowserClient& operator=(
      const BrowsingInstanceSwapContentBrowserClient&) = delete;

  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;
  }
};

// Test to verify that reloading of an error page which resulted from a
// navigation to an URL which requires a BrowsingInstance swap, correctly
// 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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->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()));
  }

  // 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()->GetPrimaryMainFrame()->GetSiteInstance());
    EXPECT_TRUE(
        IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
  }
  {
    TestNavigationObserver reload_observer(shell()->web_contents());
    EXPECT_TRUE(ExecJs(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()->GetPrimaryMainFrame()->GetSiteInstance());
    EXPECT_TRUE(
        IsMainFrameOriginOpaqueAndCompatibleWithURL(shell(), error_url));
  }

  // Install a client forcing every navigation to swap BrowsingInstances.
  BrowsingInstanceSwapContentBrowserClient content_browser_client;

  // 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(ExecJs(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()->GetPrimaryMainFrame()->GetSiteInstance()));
    EXPECT_FALSE(success_site_instance->IsRelatedSiteInstance(
        shell()->web_contents()->GetPrimaryMainFrame()->GetSiteInstance()));
  }
}

// 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;

  RenderFrameHostManagerUnloadBrowserTest(
      const RenderFrameHostManagerUnloadBrowserTest&) = delete;
  RenderFrameHostManagerUnloadBrowserTest& operator=(
      const RenderFrameHostManagerUnloadBrowserTest&) = delete;

  // Starts monitoring requests made to the embedded_test_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 == "beforeunload") {
      return blink::mojom::SuddenTerminationDisablerType::kBeforeUnloadHandler;
    }
    if (event_name == "pagehide") {
      return blink::mojom::SuddenTerminationDisablerType::kPageHideHandler;
    }
    if (event_name == "visibilitychange") {
      return blink::mojom::SuddenTerminationDisablerType::
          kVisibilityChangeHandler;
    }
    NOTREACHED();
  }

  // Returns the list of event targets that the given `event_name` should be
  // registered on.
  // Since the `visibilitychange` event is fired at the document and it
  // may bubble up to the window, we should test the cases where the event
  // listener is registered on both the document and the window.
  std::vector<std::string> EventTargetsForEvent(const std::string& event_name) {
    if (event_name == "unload" || event_name == "beforeunload" ||
        event_name == "pagehide") {
      return {"window"};
    }
    if (event_name == "visibilitychange") {
      return {"window", "document"};
    }
    NOTREACHED();
  }

  // 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& event_target,
                             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());
    EXPECT_TRUE(ExecJs(
        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::Seconds(30));
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    RenderFrameHostManagerTest::SetUpCommandLine(command_line);
    feature_list_.InitAndDisableFeature(network::features::kDeprecateUnload);
  }

 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();
  }

 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_);
  base::test::ScopedFeatureList feature_list_;
};

// 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) {
  StartEmbeddedServer();
  // 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) {
    for (const std::string& unload_event_target :
         EventTargetsForEvent(unload_event_name)) {
      EXPECT_TRUE(NavigateToURL(shell(), main_url));
      FrameTreeNode* root =
          static_cast<WebContentsImpl*>(shell()->web_contents())
              ->GetPrimaryFrameTree()
              .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, unload_event_target,
          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 pagehide 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) {
  StartEmbeddedServer();
  // See BackForwardCache::DisableForTestingReason for explanation.
  DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);

  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())
                            ->GetPrimaryFrameTree()
                            .root();
  RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();

  // Add a subframe pagehide handler to do a termination ping by loading an
  // image.
  GURL ping_url(embedded_test_server()->GetURL("b.com", "/blank.jpg"));
  AddUnloadEventHandler(
      child_rfh, "pagehide", "window",
      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 pagehide handler and can do a sendBeacon
// termination ping.
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerUnloadBrowserTest,
                       SubframeTerminationPingWhenWindowCloses) {
  StartEmbeddedServer();
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .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->GetPrimaryFrameTree().root();
  RenderFrameHostImpl* child_rfh =
      popup_root->child_at(0)->current_frame_host();

  // In the popup, add a subframe pagehide handler to do a termination ping via
  // sendBeacon.
  GURL ping_url(embedded_test_server()->GetURL("c.com", "/empty.html"));
  AddUnloadEventHandler(
      child_rfh, "pagehide", "window",
      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 a pagehide handler which never finishes,
// the iframe's process eventually exits.
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerUnloadBrowserTest,
                       SubframeProcessGoesAwayAfterUnloadTimeout) {
  StartEmbeddedServer();
  // See BackForwardCache::DisableForTestingReason for explanation.
  DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);

  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())
                            ->GetPrimaryFrameTree()
                            .root();
  RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();

  // Add a pagehide handler which never finishes to b.com subframe.
  AddUnloadEventHandler(child_rfh, "pagehide", "window", "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 a pagehide handler navigates cross-process,
// its pagehide handler is able to send a postMessage to the parent frame.
// See https://crbug.com/857274.
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerUnloadBrowserTest,
                       PostMessageToParentWhenSubframeNavigates) {
  StartEmbeddedServer();
  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())
                            ->GetPrimaryFrameTree()
                            .root();
  FrameTreeNode* child = root->child_at(0);

  // Add an onmessage listener in the main frame.
  EXPECT_TRUE(ExecJs(root, R"(
    function waitForMessage() {
      return new Promise(resolve => {
        window.addEventListener('message', function(e) {
            resolve(e.data);
        });
      });
    })"));

  // Add a pagehide handler in the child frame to send a postMessage to the
  // parent frame.
  AddUnloadEventHandler(child->current_frame_host(), "pagehide", "window",
                        "parent.postMessage('foo', '*')");
  child->current_frame_host()->DisableUnloadTimerForTesting();

  // Navigate the subframe cross-site to c.com and wait for the message.
  GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
  EXPECT_EQ("foo",
            EvalJs(root, base::StringPrintf(
                             "var p = waitForMessage();"
                             "document.querySelector('iframe').src = '%s';"
                             "p;",
                             c_url.spec().c_str())));

  // Now repeat the test with a remote-to-local navigation that brings the
  // subframe back to a.com.
  AddUnloadEventHandler(child->current_frame_host(), "pagehide", "window",
                        "parent.postMessage('bar', '*')");
  child->current_frame_host()->DisableUnloadTimerForTesting();
  GURL a_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
  EXPECT_EQ("bar",
            EvalJs(root, base::StringPrintf(
                             "var p = waitForMessage();"
                             "document.querySelector('iframe').src = '%s';"
                             "p;",
                             a_url.spec().c_str())));
}

// 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) {
  StartEmbeddedServer();
  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())
                            ->GetPrimaryFrameTree()
                            .root();
  RenderFrameHostImpl* rfh = root->current_frame_host();

  // Set up a pagehide handler which never finishes to force |rfh| to stay
  // around in pending delete state and never receive the
  // mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame.
  EXPECT_TRUE(ExecJs(rfh, "window.onpagehide = 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());
}

// RenderFrameHost should have correct sudden termination disabler
// state after the event listeners are registered and removed.
// Regression test for crbug.com/1341417.
IN_PROC_BROWSER_TEST_P(
    RenderFrameHostManagerUnloadBrowserTest,
    AddAndRemoveEventListenersAffectingSuddenTerminationDisablerState) {
  StartEmbeddedServer();
  const std::string sudden_termination_disabler_event_names[] = {
      "unload", "beforeunload", "pagehide", "visibilitychange"};

  // Initialize the RenderFrameHost.
  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())
                            ->GetPrimaryFrameTree()
                            .root();
  RenderFrameHostImpl* rfh = root->current_frame_host();

  // Register a callback function so we can remove the event listener later.
  EXPECT_TRUE(ExecJs(rfh, "var callback = (e) => {};\n"));

  for (const std::string& event_name :
       sudden_termination_disabler_event_names) {
    for (const std::string& event_target : EventTargetsForEvent(event_name)) {
      // The sudden termination disabler state is initially set to false.
      EXPECT_FALSE(rfh->GetSuddenTerminationDisablerState(
          DisablerTypeForEvent(event_name)));
      // Now add an event listener for the event_name.
      EXPECT_TRUE(ExecJs(
          rfh, base::StringPrintf("%s.addEventListener('%s', callback);",
                                  event_target.c_str(), event_name.c_str())));
      // The sudden termination disabler state should be true now.
      EXPECT_TRUE(rfh->GetSuddenTerminationDisablerState(
          DisablerTypeForEvent(event_name)));
      // Remove the registered event listener.
      EXPECT_TRUE(ExecJs(
          rfh, base::StringPrintf("%s.removeEventListener('%s', callback);",
                                  event_target.c_str(), event_name.c_str())));
      // The sudden termination disabler state should be false now.
      EXPECT_FALSE(rfh->GetSuddenTerminationDisablerState(
          DisablerTypeForEvent(event_name)));

      // Add the event listener back for the event_name.
      EXPECT_TRUE(ExecJs(
          rfh, base::StringPrintf("%s.addEventListener('%s', callback);",
                                  event_target.c_str(), event_name.c_str())));
      // The sudden termination disabler state should be true again.
      EXPECT_TRUE(rfh->GetSuddenTerminationDisablerState(
          DisablerTypeForEvent(event_name)));
      // Calling `document.open()` should trigger `RemoveAllEventListeners()`
      // in both the document DOM node and the DOM window.
      EXPECT_TRUE(ExecJs(rfh, "document.open();"));
      // The sudden termination disabler state should be false now.
      EXPECT_FALSE(rfh->GetSuddenTerminationDisablerState(
          DisablerTypeForEvent(event_name)));
    }
  }
}

// Tests that a renderer cannot change the opener of a frame across different
// BrowsingInstances (e.g., when trying to reconnect to a named window that
// has navigated to a WebUI URL).
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                       CannotChangeOpenerAcrossBrowsingInstances) {
  StartEmbeddedServer();

  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 webui_url = GetWebUIURL(kChromeUIGpuHost);

  EXPECT_TRUE(NavigateToURL(shell(), url_a1));
  WebContentsImpl* web_contents_1 =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  FrameTreeNode* ftn_1 = web_contents_1->GetPrimaryFrameTree().root();
  RenderFrameHostImpl* rfh_1 = ftn_1->current_frame_host();

  // Use window.open() to open a popup from A1 to A2 named "foo", and another
  // unnamed popup from A1 to A3.
  Shell* tab_2 = OpenPopup(rfh_1, url_a2, "foo");
  Shell* tab_3 = OpenPopup(rfh_1, url_a3, "");

  WebContentsImpl* web_contents_2 =
      static_cast<WebContentsImpl*>(tab_2->web_contents());
  FrameTreeNode* ftn_2 = web_contents_2->GetPrimaryFrameTree().root();
  WebContentsImpl* web_contents_3 =
      static_cast<WebContentsImpl*>(tab_3->web_contents());
  FrameTreeNode* ftn_3 = web_contents_3->GetPrimaryFrameTree().root();

  // Verify the initial opener relationship.
  EXPECT_EQ(ftn_1, ftn_2->opener());

  // Verify all tabs are in the same BrowsingInstance.
  EXPECT_TRUE(rfh_1->GetSiteInstance()->IsRelatedSiteInstance(
      web_contents_2->GetPrimaryMainFrame()->GetSiteInstance()));
  EXPECT_TRUE(rfh_1->GetSiteInstance()->IsRelatedSiteInstance(
      web_contents_3->GetPrimaryMainFrame()->GetSiteInstance()));

  // From Tab 3, discover Tab 2 by name and change its opener to Tab 3. This
  // should work, as they're in the same BrowsingInstance.
  EXPECT_TRUE(ExecJs(tab_3, "!!window.open('', 'foo');"));
  EXPECT_EQ(ftn_3, ftn_2->opener());

  // Navigate Tab 2 to a WebUI URL. This forces a BrowsingInstance swap.
  EXPECT_TRUE(NavigateToURL(tab_2, webui_url));
  EXPECT_FALSE(rfh_1->GetSiteInstance()->IsRelatedSiteInstance(
      web_contents_2->GetPrimaryMainFrame()->GetSiteInstance()));

  // Currently, the WebUI page's opener in the browser process is not severed,
  // unlike COOP cases. (This allows the opener to still function if we were to
  // go back to the previous page.) However, window.opener visible to JS should
  // be null.
  EXPECT_EQ(ftn_3, ftn_2->opener());
  EXPECT_TRUE(ExecJs(tab_2, "window.opener == null"));

  // From Tab 1, attempt to discover Tab 2 by name and change its opener to
  // Tab 1.
  EXPECT_TRUE(ExecJs(ftn_1, "!!window.open('', 'foo');"));

  // Because those two tabs are now in different BrowsingInstances, this attempt
  // to change the opener should be rejected. Verify the opener did not change.
  EXPECT_EQ(ftn_3, ftn_2->opener());
  EXPECT_TRUE(ExecJs(tab_2, "window.opener == null"));
}

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;

  AssertForegroundHelper(const AssertForegroundHelper&) = delete;
  AssertForegroundHelper& operator=(const AssertForegroundHelper&) = delete;

#if BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_IOS_TVOS)
  // 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) {
    ASSERT_EQ(renderer_process.GetPriority(
                  BrowserChildProcessHost::GetPortProvider()),
              base::Process::Priority::kUserBlocking);
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&AssertForegroundHelper::AssertForegroundAndRepost,
                       weak_ptr_factory_.GetWeakPtr(),
                       std::cref(renderer_process)),
        base::Milliseconds(1));
  }
#else   // BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_IOS_TVOS)
  // Same as above without the Mac specific base::PortProvider.
  void AssertForegroundAndRepost(const base::Process& renderer_process) {
    ASSERT_NE(renderer_process.GetPriority(),
              base::Process::Priority::kBestEffort);
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&AssertForegroundHelper::AssertForegroundAndRepost,
                       weak_ptr_factory_.GetWeakPtr(),
                       std::cref(renderer_process)),
        base::Milliseconds(1));
  }
#endif  // BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_IOS_TVOS)

 private:
  base::WeakPtrFactory<AssertForegroundHelper> weak_ptr_factory_{this};
};

}  // 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.
// TODO(crbug.com/40760155): Flaky on Mac.
#if BUILDFLAG(IS_APPLE)
#define MAYBE_ForegroundNavigationIsNeverBackgroundedWithoutSpareProcess \
  DISABLED_ForegroundNavigationIsNeverBackgroundedWithoutSpareProcess
#else
#define MAYBE_ForegroundNavigationIsNeverBackgroundedWithoutSpareProcess \
  ForegroundNavigationIsNeverBackgroundedWithoutSpareProcess
#endif
IN_PROC_BROWSER_TEST_P(
    RenderFrameHostManagerTest,
    MAYBE_ForegroundNavigationIsNeverBackgroundedWithoutSpareProcess) {
  StartEmbeddedServer();
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // 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->GetPrimaryMainFrame()->GetProcess();

  // Discard the spare RenderProcessHost to ensure a new RenderProcessHost
  // is created and has the right prioritization.
  auto& spare_manager = SpareRenderProcessHostManagerImpl::Get();
  spare_manager.CleanupSparesForTesting();
  EXPECT_TRUE(spare_manager.GetSpares().empty());

  // 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);
  navigation_manager.WaitForSpeculativeRenderFrameHostCreation();
  RenderProcessHost* speculative_rph = web_contents->GetPrimaryFrameTree()
                                           .root()
                                           ->render_manager()
                                           ->speculative_frame_host()
                                           ->GetProcess();
  EXPECT_NE(start_rph, speculative_rph);
  EXPECT_FALSE(speculative_rph->IsReady());

#if !BUILDFLAG(IS_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_NE(speculative_rph->GetPriority(),
            base::Process::Priority::kBestEffort);
#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;
  assert_foreground_helper.AssertForegroundAndRepost(process);

  // The process should be foreground priority before commit because it is
  // pending, and foreground after commit because it has a visible widget.
  ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
  EXPECT_NE(start_rph, web_contents->GetPrimaryMainFrame()->GetProcess());
  EXPECT_EQ(speculative_rph, web_contents->GetPrimaryMainFrame()->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() ||
      // If the navigation throttle is required for the Android spare
      // renderer, the spare process cannot be used if the creation of the RFH
      // gets deferred. The throttle requires the process allocation to happen
      // before sending the network request.
      (base::FeatureList::IsEnabled(
           features::kAndroidWarmUpSpareRendererWithTimeout) &&
       features::kAndroidSpareRendererAddNavigationThrottle.Get() &&
       base::FeatureList::IsEnabled(features::kDeferSpeculativeRFHCreation))) {
    return;
  }

  StartEmbeddedServer();
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // Start off navigating to a.com and capture the process used to commit.
  SpareRenderProcessHostStartedObserver spare_started_observer;
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
  // The AndroidWarmUpSpareRendererWithTimeout feature will create a spare
  // renderer after the navigation finishes or with a delay so we need to
  // explicitly wait.
  if (base::FeatureList::IsEnabled(
          features::kAndroidWarmUpSpareRendererWithTimeout)) {
    spare_started_observer.WaitForSpareRenderProcessStarted();
  }
  RenderProcessHost* start_rph =
      web_contents->GetPrimaryMainFrame()->GetProcess();

  // At this time, there should be at least one RenderProcessHost. Capture them
  // for testing expectations later.
  auto& spare_manager = SpareRenderProcessHostManagerImpl::Get();
  for (content::RenderProcessHost* host : spare_manager.GetSpares()) {
    EXPECT_EQ(host->GetPriority(), base::Process::Priority::kBestEffort);
  }
  std::vector<ChildProcessId> spare_rph_ids = spare_manager.GetSpareIds();
  ASSERT_FALSE(spare_rph_ids.empty());

  // 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);
  navigation_manager.WaitForSpeculativeRenderFrameHostCreation();
  RenderProcessHost* speculative_rph = web_contents->GetPrimaryFrameTree()
                                           .root()
                                           ->render_manager()
                                           ->speculative_frame_host()
                                           ->GetProcess();
  EXPECT_NE(start_rph, speculative_rph);

  // In this test case, a spare RenderProcessHost will be used, so verify it
  // and ensure it is ready.
  EXPECT_THAT(spare_rph_ids, testing::Contains(speculative_rph->GetID()));

  // If LoadUrl finished before the task to call
  // RenderProcessHostImpl::OnChannelConnected is run, wait for the task to be
  // run.
  if (!speculative_rph->IsReady()) {
    RenderProcessHostWatcher ready_waiter(
        speculative_rph, RenderProcessHostWatcher::WATCH_FOR_PROCESS_READY);
    ready_waiter.Wait();
  }
  EXPECT_TRUE(speculative_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_NE(speculative_rph->GetPriority(),
            base::Process::Priority::kBestEffort);

  // 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 = speculative_rph->GetProcess();
  EXPECT_TRUE(process.IsValid());
  AssertForegroundHelper assert_foreground_helper;
  assert_foreground_helper.AssertForegroundAndRepost(process);

  // The process should be foreground priority before commit because it is
  // pending, and foreground after commit because it has a visible widget.
  ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
  EXPECT_NE(start_rph, web_contents->GetPrimaryMainFrame()->GetProcess());
  EXPECT_EQ(speculative_rph, web_contents->GetPrimaryMainFrame()->GetProcess());
}

// 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->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->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:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    RenderFrameHostManagerTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(switches::kDisableSiteIsolation);
    command_line->RemoveSwitch(switches::kSitePerProcess);
  }
};

// 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 replacing 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.
// TODO(crbug.com/390571607): Enable this test when default SiteInstanceGroups
// is implemented.
IN_PROC_BROWSER_TEST_P(
    RenderFrameHostManagerDefaultProcessTest,
    DISABLED_NavigationRacesWithSitelessCommitInDefaultProcess) {
  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->GetPrimaryMainFrame()->GetProcess();
  EXPECT_EQ(original_process, web_contents->GetPrimaryMainFrame()
                                  ->GetSiteInstance()
                                  ->GetDefaultProcessForBrowsingInstance());
  // 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->GetPrimaryMainFrame(), 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.  This requires adding the URL's scheme as an empty
  // document scheme.
  url::ScopedSchemeRegistryForTests scheme_registry;
  url::AddEmptyDocumentScheme("siteless");
  GURL siteless_url("siteless://test");
  EXPECT_FALSE(SiteInstance::ShouldAssignSiteForURL(siteless_url));

  // 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(
            ExecJs(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(ExecJs(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->GetPrimaryMainFrame()->GetProcess());
}

// 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->GetPrimaryMainFrame();
  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->GetRoutingID();

  auto proxy_count = [](RenderFrameHostImpl* rfh) {
    return rfh->browsing_context_state()->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<SiteInstanceImpl> site_instance) {
    return rfh->browsing_context_state()
        ->GetRenderFrameProxyHost(site_instance->group())
        ->is_render_frame_proxy_live();
  };

  // Store SiteInstance for later comparison.
  scoped_refptr<SiteInstanceImpl> a_site_instance(a1->GetSiteInstance());
  scoped_refptr<SiteInstanceImpl> b_site_instance(b2->GetSiteInstance());
  scoped_refptr<SiteInstanceImpl> c_site_instance(c5->GetSiteInstance());

  // Check that each of the site instances are in a different group, so proxies
  // exist for the others.
  EXPECT_NE(a_site_instance->group(), b_site_instance->group());
  EXPECT_NE(a_site_instance->group(), c_site_instance->group());
  EXPECT_NE(b_site_instance->group(), c_site_instance->group());

  // 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_for_crash());
  EXPECT_TRUE(b2->must_be_replaced_for_crash());
  EXPECT_TRUE(b3->must_be_replaced_for_crash());
  EXPECT_FALSE(c5->must_be_replaced_for_crash());

  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()->GetRoutingID());
  // 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_for_crash());
  EXPECT_FALSE(b6->must_be_replaced_for_crash());

  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->GetPrimaryFrameTree().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(
      const RenderFrameHostManagerNoSiteIsolationTest&) = delete;
  RenderFrameHostManagerNoSiteIsolationTest& operator=(
      const RenderFrameHostManagerNoSiteIsolationTest&) = delete;

  ~RenderFrameHostManagerNoSiteIsolationTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kDisableSiteIsolation);
  }
};

// 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()->GetPrimaryMainFrame()->GetSiteInstance());
  SiteInstanceImpl* new_instance = static_cast<SiteInstanceImpl*>(
      shell2->web_contents()->GetPrimaryMainFrame()->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,
                       RemoveSubframeInPageHide_SameSite) {
  // TODO(crbug.com/40731502): Remove this early return. This doesn't
  // work for RenderDocumentLevel::kNonLocalRootSubframe or greater because
  // cancelling the navigation when detaching the subtree tries to restore the
  // replaced `blink::RemoteFrame` (which doesn't exist in the same-site
  // RenderDocument case because the replaced object wasn't a
  // `blink::RemoteFrame`, but instead a RenderFrame).
  if (ShouldCreateNewRenderFrameHostOnSameSiteNavigation(
          /*is_main_frame=*/false, /*is_local_root=*/false)) {
    return;
  }
  AssertCanRemoveSubframeInPageHide(/*same_site=*/true);
}

// See RemoveSubframeInUnload_SameSite
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                       RemoveSubframeInPageHide_CrossSite) {
  // TODO(crbug.com/40731502): Remove this early return.
  if (ShouldCreateNewRenderFrameHostOnSameSiteNavigation(
          /*is_main_frame=*/false, /*is_local_root=*/false) &&
      !AreAllSitesIsolatedForTesting()) {
    return;
  }
  AssertCanRemoveSubframeInPageHide(/*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(RenderFrameHostManagerUnloadBrowserTest, 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->GetPrimaryFrameTree().root()));

  // Navigate the subframe, triggering unload.
  FrameTreeNode* subframe = web_contents->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->child_count());
  // TODO(crbug.com/40142480): Remove this. Without this, the crash in
  // the renderer in https://crbug.com/1148793 is usually not caught.
  ASSERT_TRUE(ExecJs(shell(), ""));
}

// See RemoveSubframeInPageHide_SameSite
void RenderFrameHostManagerTest::AssertCanRemoveSubframeInPageHide(
    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 pagehide handler to remove the subframe.
  ASSERT_TRUE(ExecJs(shell(), R"(
    const subframe = document.getElementById("child-0");
    subframe.contentWindow.onpagehide = () => {
      subframe.remove();
    }
  )"));

  // Navigate the subframe, triggering pagehide.
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  FrameTreeNode* subframe = web_contents->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->child_count());
  // TODO(crbug.com/40142480): 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->GetPrimaryMainFrame()->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->GetPrimaryMainFrame()->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());
}

// From https://crbug.com/1503038.
// The RuntimeFeatureStateDocumentData should be re-created when the main frame
// recovers from a crash.
IN_PROC_BROWSER_TEST_P(
    RenderFrameHostManagerTest,
    RuntimeFeatureStateDocumentDataShouldBeRecreatedAfterCrash) {
  StartEmbeddedServer();

  GURL url(embedded_test_server()->GetURL("a.com", "/empty.html"));

  ASSERT_TRUE(NavigateToURL(shell(), url));

  // Crash the frame.
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  RenderFrameHostImpl* rfh = web_contents->GetPrimaryMainFrame();
  RenderProcessHost* process = rfh->GetProcess();
  {
    RenderProcessHostWatcher crash_observer(
        process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    process->Shutdown(0);
    crash_observer.Wait();
  }
  ASSERT_FALSE(rfh->IsRenderFrameLive());

  auto* root = web_contents->GetPrimaryFrameTree().root();
  RenderFrameHostManager* manager = root->render_manager();

  manager->InitializeMainRenderFrameForImmediateUse();

  // Add a new iframe. As part of this iframe's creation
  // RenderFrameHostImpl::SetOriginDependentStateOfNewFrame() will be called
  // which will attempt to copy the parent frame's
  // RuntimeFeatureStateDocumentData.
  std::string script =
      "var new_iframe = document.createElement('iframe');"
      "document.documentElement.appendChild(new_iframe);";

  // If the parent's RuntimeFeatureStateDocumentData exists then this will
  // succeed, otherwise we'll hit a CHECK.
  EXPECT_TRUE(ExecJs(shell(), script));
}

// Tests that disable kDeferSpeculativeRFHCreation. This is to ensure that
// GetFrameHostNavigation is called immediately on NavigationRequest creation
// instead of getting queued in a task, which possibly causes it not to be run
// if the network response is already received and the final RenderFrameHost
// has been picked by another GetFrameHostNavigation call.
class RenderFrameHostManagerDisableDeferSpeculativeRFHCreationTest
    : public RenderFrameHostManagerTest {
 public:
  RenderFrameHostManagerDisableDeferSpeculativeRFHCreationTest() {
    feature_list_.InitAndDisableFeature(features::kDeferSpeculativeRFHCreation);
  }
  ~RenderFrameHostManagerDisableDeferSpeculativeRFHCreationTest() override =
      default;

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_P(
    RenderFrameHostManagerDisableDeferSpeculativeRFHCreationTest,
    WastedSpeculativeRFHMetrics) {
  StartEmbeddedServer();

  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
  GURL url_d(embedded_test_server()->GetURL("d.com", "/title1.html"));
  GURL url_e(embedded_test_server()->GetURL("e.com", "/title1.html"));
  GURL url_c_redirect_to_d(embedded_test_server()->GetURL(
      "c.com", "/server-redirect?" + url_d.spec()));
  GURL url_d_redirect_to_e(embedded_test_server()->GetURL(
      "d.com", "/server-redirect?" + url_e.spec()));
  {
    SCOPED_TRACE("Case 1: From initial blank document to A");
    // 1) Navigate to A, which will reuse the current RFH.
    base::HistogramTester histogram_tester;
    ASSERT_TRUE(NavigateToURL(shell(), url_a));

    // GetFrameHostForNavigation is called twice for the navigation above.
    // The first call was when there's no associated RFH yet, and because the
    // navigation reuses the current RFH (because it's the first navigation), on
    // the second call we didn't waste any speculative RFHs.
    EXPECT_THAT(
        histogram_tester.GetAllSamples(
            "Navigation.All.WastedSpeculativeRFHCase"),
        testing::ElementsAre(
            base::Bucket(WastedSpeculativeRFHCase::kNotWasted_WasUnassociated,
                         1),
            base::Bucket(WastedSpeculativeRFHCase::
                             kNotWasted_WasUsingCurrentRFH_NowKeepCurrentRFH,
                         1)));
  }

  {
    SCOPED_TRACE("Case 2: From A to B");
    // 2) Navigate cross-site to B, which will use a new speculative RFH.  Note
    // that we navigate from the renderer to avoid doing a proactive
    // BrowsingInstance swap which might cause renderer process/RenderFrameHost
    // swaps despite Site Isolation being turned off, making the metrics a bit
    // confusing to explain.
    base::HistogramTester histogram_tester;
    ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_b));

    // GetFrameHostForNavigation is called twice for the navigation above.
    // The first call was when there's no associated RFH yet, then it will
    // create a speculative RFH. Then on the second call we keep using the same
    // speculative RFH. Note that if all of Site Isolation, BFCache,
    // RenderDocument and default SiteInstanceGroup are turned off, we'll just
    // reuse current RFH in both cases.
    EXPECT_THAT(
        histogram_tester.GetAllSamples(
            "Navigation.All.WastedSpeculativeRFHCase"),
        testing::ElementsAre(
            base::Bucket(WastedSpeculativeRFHCase::kNotWasted_WasUnassociated,
                         1),
            base::Bucket(
                (AreStrictSiteInstancesEnabled() ||
                 CanSameSiteMainFrameNavigationsChangeRenderFrameHosts())
                    ? WastedSpeculativeRFHCase::
                          kNotWasted_NowKeepSameSpeculativeRFH
                    : WastedSpeculativeRFHCase::
                          kNotWasted_WasUsingCurrentRFH_NowKeepCurrentRFH,
                1)));
  }

  {
    SCOPED_TRACE("Case 3: From B to C and redirecting to D");
    // 3) Navigate (initially) cross-site to C, but then redirect to another
    // cross-site URL D. Note that we navigate from the renderer to avoid doing
    // a proactive BrowsingInstance swap which might cause renderer process
    // swaps despite Site Isolation being turned off, making the metrics a bit
    // confusing to explain.
    base::HistogramTester histogram_tester;
    ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_c_redirect_to_d, url_d));

    // GetFrameHostForNavigation is called twice for the navigation above.
    bool wasted_speculative_rfh = false;
    if (AreStrictSiteInstancesEnabled() ||
        CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
      // First call created speculative RFH. Second call can reuse the
      // speculative RFH only if site isolation is turned off.
      wasted_speculative_rfh = AreAllSitesIsolatedForTesting();
      EXPECT_THAT(
          histogram_tester.GetAllSamples(
              "Navigation.All.WastedSpeculativeRFHCase"),
          testing::ElementsAre(
              base::Bucket(WastedSpeculativeRFHCase::kNotWasted_WasUnassociated,
                           1),
              base::Bucket(AreStrictSiteInstancesEnabled()
                               ? WastedSpeculativeRFHCase::
                                     kWasted_NowUseNewSpeculativeRFH
                               : WastedSpeculativeRFHCase::
                                     kNotWasted_NowKeepSameSpeculativeRFH,
                           1)));
    } else {
      // No wasted speculative RFH.
      EXPECT_THAT(
          histogram_tester.GetAllSamples(
              "Navigation.All.WastedSpeculativeRFHCase"),
          testing::ElementsAre(
              base::Bucket(WastedSpeculativeRFHCase::kNotWasted_WasUnassociated,
                           1),
              base::Bucket(WastedSpeculativeRFHCase::
                               kNotWasted_WasUsingCurrentRFH_NowKeepCurrentRFH,
                           1)));
    }
    if (wasted_speculative_rfh) {
      // The wasted speculative RFH created a new process for C.
      histogram_tester.ExpectUniqueSample(
          "Navigation.All.WastedSpeculativeRFH."
          "WastedRFHLikelyCreatedNewProcess",
          true, 1);
      // The replacement speculative RFH created a new process for D.
      histogram_tester.ExpectUniqueSample(
          "Navigation.All.WastedSpeculativeRFH.ReplacementRFHCreatedNewProcess",
          true, 1);
      // Process differs between the wasted speculative RFH and the newly picked
      // RFH.
      histogram_tester.ExpectUniqueSample(
          "Navigation.All.WastedSpeculativeRFH.ProcessDiffers", true, 1);
      // No cross-origin isolation difference.
      histogram_tester.ExpectUniqueSample(
          "Navigation.All.WastedSpeculativeRFH.CrossOriginIsolationDiffers",
          false, 1);
    }
  }

  {
    SCOPED_TRACE("Case 4: From D to C and redirecting to D");
    // 4) Navigate (initially) cross-site to C, but then redirect to a
    // same-site-to-current-RFH URL D.  Note that we navigate from the renderer
    // to avoid doing a proactive BrowsingInstance swap which might cause
    // renderer process swaps despite Site Isolation being turned off, making
    // the metrics a bit confusing to explain.
    base::HistogramTester histogram_tester;
    ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_c_redirect_to_d, url_d));

    // GetFrameHostForNavigation is called twice for the navigation above.
    bool wasted_speculative_rfh = false;
    // The first call will create a new process if strict site isolation is
    // enabled, or BFCache-induced proactive BrowsingInstance swap happened.
    // TODO(https://crbug.com/376777350): Reconsider if we really should do
    // process swap on BrowsingInstance swap when site isolation is turned off.
    bool first_call_created_new_process =
        (AreAllSitesIsolatedForTesting() || IsBackForwardCacheEnabled());
    if (AreStrictSiteInstancesEnabled() ||
        CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
      // First call created speculative RFH because of strict SiteInstances,
      // RenderDocument, or proactive swap for BFCache. This speculative RFH
      // will never get used, see cases below.
      if (ShouldCreateNewHostForAllFrames() && first_call_created_new_process) {
        // The navigation needs to commit in a speculative RFH, but the
        // previously created speculative RFH is now incompatible because it
        // was created for C in another process.
        wasted_speculative_rfh = true;
        EXPECT_THAT(
            histogram_tester.GetAllSamples(
                "Navigation.All.WastedSpeculativeRFHCase"),
            testing::ElementsAre(
                base::Bucket(
                    WastedSpeculativeRFHCase::kNotWasted_WasUnassociated, 1),
                base::Bucket(
                    WastedSpeculativeRFHCase::kWasted_NowUseNewSpeculativeRFH,
                    1)));
      } else if (AreStrictSiteInstancesEnabled()) {
        // If we are using default SiteInstanceGroups, the navigation started in
        // a speculative RFH for C. After the redirect to D, it committed in
        // either the current RFH for D (without RenderDocument), or a new
        // speculative RFH for D (with RenderDocument). So, the wasted
        // speculative RFH metric should indicate that there was a change from
        // speculative to current RFH (without RenderDocument), or from one
        // speculative RFH to another (with RenderDocument).
        wasted_speculative_rfh = true;
        EXPECT_THAT(
            histogram_tester.GetAllSamples(
                "Navigation.All.WastedSpeculativeRFHCase"),
            testing::ElementsAre(
                base::Bucket(
                    WastedSpeculativeRFHCase::kNotWasted_WasUnassociated, 1),
                base::Bucket(
                    ShouldCreateNewHostForAllFrames()
                        ? WastedSpeculativeRFHCase::
                              kWasted_NowUseNewSpeculativeRFH
                        : WastedSpeculativeRFHCase::kWasted_NowUseCurrentRFH,
                    1)));
      } else if (ShouldCreateNewHostForAllFrames()) {
        // The navigation needs to commit in a speculative RFH, and it can reuse
        // the previously created speculative RFH.
        EXPECT_THAT(
            histogram_tester.GetAllSamples(
                "Navigation.All.WastedSpeculativeRFHCase"),
            testing::ElementsAre(
                base::Bucket(
                    WastedSpeculativeRFHCase::kNotWasted_WasUnassociated, 1),
                base::Bucket(WastedSpeculativeRFHCase::
                                 kNotWasted_NowKeepSameSpeculativeRFH,
                             1)));
      } else {
        // The navigation ends up reusing the current active RFH instead.
        wasted_speculative_rfh = true;
        EXPECT_THAT(
            histogram_tester.GetAllSamples(
                "Navigation.All.WastedSpeculativeRFHCase"),
            testing::ElementsAre(
                base::Bucket(
                    WastedSpeculativeRFHCase::kNotWasted_WasUnassociated, 1),
                base::Bucket(WastedSpeculativeRFHCase::kWasted_NowUseCurrentRFH,
                             1)));
      }
    } else {
      // No speculative RFH created.
      EXPECT_THAT(
          histogram_tester.GetAllSamples(
              "Navigation.All.WastedSpeculativeRFHCase"),
          testing::ElementsAre(
              base::Bucket(WastedSpeculativeRFHCase::kNotWasted_WasUnassociated,
                           1),
              base::Bucket(WastedSpeculativeRFHCase::
                               kNotWasted_WasUsingCurrentRFH_NowKeepCurrentRFH,
                           1)));
    }

    if (wasted_speculative_rfh) {
      histogram_tester.ExpectUniqueSample(
          "Navigation.All.WastedSpeculativeRFH."
          "WastedRFHLikelyCreatedNewProcess",
          first_call_created_new_process, 1);
      // The replacement RFH does not create a new process for D.
      histogram_tester.ExpectUniqueSample(
          "Navigation.All.WastedSpeculativeRFH.ReplacementRFHCreatedNewProcess",
          false, 1);
      // Process differs between the wasted speculative RFH and the newly picked
      // RFH if the wasted one created a new process. The new RFH must be in the
      // current / D's process.
      histogram_tester.ExpectUniqueSample(
          "Navigation.All.WastedSpeculativeRFH.ProcessDiffers",
          first_call_created_new_process, 1);
      // No cross-origin isolation difference.
      histogram_tester.ExpectUniqueSample(
          "Navigation.All.WastedSpeculativeRFH.CrossOriginIsolationDiffers",
          false, 1);
    }
  }

  {
    SCOPED_TRACE("Case 5: From D to D and redirecting to E");
    // 5) Navigate (initially) same-site to D, but then redirect cross-site to
    // E. Note that we navigate from the renderer to avoid doing a proactive
    // BrowsingInstance swap which might cause renderer process swaps despite
    // Site Isolation being turned off, making the metrics a bit confusing to
    // explain.
    base::HistogramTester histogram_tester;
    ASSERT_TRUE(NavigateToURLFromRenderer(shell(), url_d_redirect_to_e, url_e));

    // GetFrameHostForNavigation is called twice for the navigation above.
    bool wasted_speculative_rfh = false;
    if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
      // First call created speculative RFH. Second call can reuse the
      // speculative RFH only if site isolation is turned off and default
      // SiteInstanceGroups are not enabled.
      wasted_speculative_rfh = AreStrictSiteInstancesEnabled();
      EXPECT_THAT(
          histogram_tester.GetAllSamples(
              "Navigation.All.WastedSpeculativeRFHCase"),
          testing::ElementsAre(
              base::Bucket(WastedSpeculativeRFHCase::kNotWasted_WasUnassociated,
                           1),
              base::Bucket(wasted_speculative_rfh
                               ? WastedSpeculativeRFHCase::
                                     kWasted_NowUseNewSpeculativeRFH
                               : WastedSpeculativeRFHCase::
                                     kNotWasted_NowKeepSameSpeculativeRFH,
                           1)));
    } else {
      // No speculative RFH created for the initial call for D.
      EXPECT_THAT(
          histogram_tester.GetAllSamples(
              "Navigation.All.WastedSpeculativeRFHCase"),
          testing::ElementsAre(
              base::Bucket(WastedSpeculativeRFHCase::kNotWasted_WasUnassociated,
                           1),
              base::Bucket(
                  AreStrictSiteInstancesEnabled()
                      ? WastedSpeculativeRFHCase::
                            kNotWasted_WasUsingCurrentRFH_NowUseSpeculativeRFH
                      : WastedSpeculativeRFHCase::
                            kNotWasted_WasUsingCurrentRFH_NowKeepCurrentRFH,
                  1)));
    }

    if (wasted_speculative_rfh) {
      // The wasted speculative RFH used the process for D.
      histogram_tester.ExpectUniqueSample(
          "Navigation.All.WastedSpeculativeRFH."
          "WastedRFHLikelyCreatedNewProcess",
          false, 1);
      // The replacement speculative RFH created a new process for E, if site
      // isolation is turned on, or BFCache is enabled.
      //
      // TODO(https://crbug.com/376777350): Investigate if we should suppress
      // the process creation for BFCache when site isolation is turned off.
      //
      // TODO(https://crbug.com/419469455): These metrics do not behave properly
      // with default SiteInstanceGroups. They indicate that in this case, a new
      // process is created, whereas in fact it is not created but rather
      // reused.  As a result, ProcessDiffers and ReplacementRFHCreateNewProcess
      // are both incorrect (true instead of false) with default
      // SiteInstanceGroups, which should be fixed.
      if (!ShouldUseDefaultSiteInstanceGroup()) {
        histogram_tester.ExpectUniqueSample(
            "Navigation.All.WastedSpeculativeRFH."
            "ReplacementRFHCreatedNewProcess",
            AreAllSitesIsolatedForTesting() || IsBackForwardCacheEnabled(), 1);
        // Process differs between the wasted RFH and the newly chosen RFH if a
        // new process is created for E.
        histogram_tester.ExpectUniqueSample(
            "Navigation.All.WastedSpeculativeRFH.ProcessDiffers",
            AreAllSitesIsolatedForTesting() || IsBackForwardCacheEnabled(), 1);
      }
      // No cross-origin isolation difference.
      histogram_tester.ExpectUniqueSample(
          "Navigation.All.WastedSpeculativeRFH.CrossOriginIsolationDiffers",
          false, 1);
    }
  }
}

// Tests that enable clearing window.name on cross-site
// cross-BrowsingInstance navigations.
class RenderFrameHostManagerClearWindowNameTest
    : public RenderFrameHostManagerTest {
 public:
  RenderFrameHostManagerClearWindowNameTest() {
    feature_list_.InitAndEnableFeature(
        features::kClearCrossSiteCrossBrowsingContextGroupWindowName);
  }
  ~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::ExecJs(web_contents, "window.name='foo'"));
  auto* frame_a = web_contents->GetPrimaryMainFrame();
  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->GetPrimaryMainFrame();
  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->GetPrimaryMainFrame();
  // 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());
}

class ResumeNavigationWithSpeculativeRFHProcessGoneTest
    : public RenderFrameHostManagerTest {
 public:
  ResumeNavigationWithSpeculativeRFHProcessGoneTest() {
    feature_list_.InitWithFeatures(
        {features::kResumeNavigationWithSpeculativeRFHProcessGone},
        {features::kDeferSpeculativeRFHCreation});
  }
  ~ResumeNavigationWithSpeculativeRFHProcessGoneTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Isolate all sites so that speculative RFH will be created during
    // navigation to a different site.
    IsolateAllSitesForTesting(command_line);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_P(ResumeNavigationWithSpeculativeRFHProcessGoneTest,
                       ProcessForSpeculativeRFHKilled) {
  StartEmbeddedServer();

  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());

  // 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"));
  TestNavigationManager navigation_manager(web_contents, url_b);
  shell()->LoadURL(url_b);
  navigation_manager.WaitForSpeculativeRenderFrameHostCreation();
  RenderFrameHost* rfh = navigation_manager.GetCreatedSpeculativeRFH();
  RenderProcessHostWatcher crash_observer(
      rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  if (!rfh->GetProcess()->IsReady()) {
    // ChildProcessLauncher::Terminate will not kill a process if it is not
    // ready yet so we need to wait for the process to be ready.
    RenderProcessHostWatcher ready_observer(
        rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_READY);
    ready_observer.Wait();
  }
  EXPECT_TRUE(rfh->GetProcess()->Shutdown(0));
  crash_observer.Wait();
  NavigationRequest* navigation_request =
      NavigationRequest::From(navigation_manager.GetNavigationHandle());
  EXPECT_EQ(navigation_request->GetAssociatedRFHType(),
            NavigationRequest::AssociatedRenderFrameHostType::NONE);

  EXPECT_TRUE(navigation_manager.WaitForNavigationFinished());
  EXPECT_TRUE(navigation_manager.was_successful());
  EXPECT_TRUE(navigation_manager.was_committed());
}

// Test that a BrowsingInstance swap occurs when the process flags for a site
// change. Ensure this happens only for main frames and only when there are no
// related WebContents.
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                       SwapBrowsingInstanceOnReloadOnProcessFlagChange) {
  if (!SiteIsolationPolicy::IsSitePerProcessOrStricter()) {
    // TODO(crbug.com/489441400): Enable this test on Android. It is currently
    // skipped because it requires site-per-process, which is not the default
    // on many Android configurations, though the feature itself is functional.
    GTEST_SKIP() << "Requires at least site-per-process";
  }
  StartEmbeddedServer();
  ProcessFlagsContentBrowserClient client;
  // The BFCache may proactively swap, even on same-site navigations, so disable
  // it for this test to ensure we are indeed testing the swap that is caused by
  // changing process flag values.
  DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);

  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  // 1. Navigate to a.com with optimizations enabled.
  client.SetV8OptimizationsDisabledForHost("a.com", false);
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  scoped_refptr<SiteInstanceImpl> instance1 =
      static_cast<SiteInstanceImpl*>(web_contents->GetSiteInstance());
  EXPECT_FALSE(instance1->GetSiteInfo().are_v8_optimizations_disabled());

  // 2. Change flags and reload a.com.
  client.SetV8OptimizationsDisabledForHost("a.com", true);
  web_contents->GetController().Reload(content::ReloadType::NORMAL, false);
  EXPECT_TRUE(WaitForLoadStop(web_contents));
  scoped_refptr<SiteInstanceImpl> instance2 =
      static_cast<SiteInstanceImpl*>(web_contents->GetSiteInstance());
  // Verify v8 optimizations were updated and swap occurred.
  EXPECT_TRUE(instance2->GetSiteInfo().are_v8_optimizations_disabled());
  EXPECT_FALSE(instance1->IsRelatedSiteInstance(instance2.get()));

  // 3. Test subframe: subframes should not swap BrowsingInstances.
  // Start on a.com with an iframe to b.com.
  GURL url_a_with_b(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  client.SetV8OptimizationsDisabledForHost("b.com", false);
  EXPECT_TRUE(NavigateToURL(shell(), url_a_with_b));
  RenderFrameHostImpl* main_rfh = web_contents->GetPrimaryMainFrame();
  RenderFrameHost* subframe_rfh = content::ChildFrameAt(main_rfh, 0);

  // Verify subframe is in the same BrowsingInstance as its parent, and v8
  // optimizations are enabled.
  EXPECT_EQ(main_rfh->GetSiteInstance()->GetBrowsingInstanceId(),
            subframe_rfh->GetSiteInstance()->GetBrowsingInstanceId());
  scoped_refptr<SiteInstanceImpl> instance3 =
      static_cast<SiteInstanceImpl*>(subframe_rfh->GetSiteInstance());
  EXPECT_FALSE(instance3->GetSiteInfo().are_v8_optimizations_disabled());

  // Now change v8 optimizer state for b.com and navigate the subframe.
  client.SetV8OptimizationsDisabledForHost("b.com", true);
  EXPECT_TRUE(NavigateIframeToURL(web_contents, "child-0", url_b));

  // Verify subframe still in same BrowsingInstance despite flag change, and v8
  // optimizations are still enabled.
  subframe_rfh = content::ChildFrameAt(main_rfh, 0);
  EXPECT_TRUE(main_rfh->GetSiteInstance()->IsRelatedSiteInstance(
      subframe_rfh->GetSiteInstance()));
  scoped_refptr<SiteInstanceImpl> instance4 =
      static_cast<SiteInstanceImpl*>(subframe_rfh->GetSiteInstance());
  EXPECT_FALSE(instance4->GetSiteInfo().are_v8_optimizations_disabled());

  // 4. Test RelatedActiveContents: no swap if there are scripting references.
  // Start on a.com with v8 optimizations disabled.
  client.SetV8OptimizationsDisabledForHost("a.com", true);
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  scoped_refptr<SiteInstanceImpl> instance5 =
      static_cast<SiteInstanceImpl*>(web_contents->GetSiteInstance());
  EXPECT_TRUE(instance5->GetSiteInfo().are_v8_optimizations_disabled());

  // Open a popup to create a related ActiveContent.
  EXPECT_TRUE(OpenPopup(shell(), url_a, "popup"));
  EXPECT_EQ(2u, instance5->GetRelatedActiveContentsCount());

  // Change flags and reload main window.
  client.SetV8OptimizationsDisabledForHost("a.com", false);
  web_contents->GetController().Reload(content::ReloadType::NORMAL, false);
  EXPECT_TRUE(WaitForLoadStop(web_contents));

  scoped_refptr<SiteInstanceImpl> instance6 =
      static_cast<SiteInstanceImpl*>(web_contents->GetSiteInstance());

  // Verify no swap occurred and v8 optimizations are still disabled.
  EXPECT_TRUE(instance5->IsRelatedSiteInstance(instance6.get()));
  EXPECT_TRUE(instance6->GetSiteInfo().are_v8_optimizations_disabled());
}

// Test that a BrowsingInstance swap occurs when the jitless setting for a site
// changes.
IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
                       SwapBrowsingInstanceOnReloadOnJitlessStateChange) {
  if (!SiteIsolationPolicy::IsSitePerProcessOrStricter()) {
    // TODO(crbug.com/489441400): Enable this test on Android. It is currently
    // skipped because it requires site-per-process, which is not the default
    // on many Android configurations, though the feature itself is functional.
    GTEST_SKIP() << "Requires at least site-per-process";
  }
  StartEmbeddedServer();
  ProcessFlagsContentBrowserClient client;

  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));

  // 1. Navigate to a.com with optimizations enabled.
  client.SetJitDisabledForHost("a.com", true);
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  scoped_refptr<SiteInstanceImpl> instance1 =
      static_cast<SiteInstanceImpl*>(web_contents->GetSiteInstance());
  EXPECT_TRUE(instance1->GetSiteInfo().is_jit_disabled());

  // 2. Change flags and navigate to a.com again.
  client.SetJitDisabledForHost("a.com", false);
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  scoped_refptr<SiteInstanceImpl> instance2 =
      static_cast<SiteInstanceImpl*>(web_contents->GetSiteInstance());

  // Verify swap occurred.
  EXPECT_FALSE(instance1->IsRelatedSiteInstance(instance2.get()));
  // Verify updated jitless settings were applied correctly.
  EXPECT_FALSE(instance2->GetSiteInfo().is_jit_disabled());
}

class SwapBrowsingInstancesForDifferentProcessFlagsDisabledTest
    : public RenderFrameHostManagerTest {
 public:
  SwapBrowsingInstancesForDifferentProcessFlagsDisabledTest() {
    feature_list_.InitFromCommandLine(
        "", "SwapBrowsingInstancesForDifferentProcessFlags");
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Test that a BrowsingInstance swap does NOT occur when process-level flags
// change if the kSwapBrowsingInstancesForDifferentProcessFlags feature is
// disabled.
IN_PROC_BROWSER_TEST_P(
    SwapBrowsingInstancesForDifferentProcessFlagsDisabledTest,
    BrowsingInstanceNotSwappedOnProcessFlagChange) {
  StartEmbeddedServer();
  ProcessFlagsContentBrowserClient client;

  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));

  // 1. Navigate to a.com with optimizations enabled.
  client.SetV8OptimizationsDisabledForHost("a.com", false);
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  scoped_refptr<SiteInstanceImpl> instance1 =
      static_cast<SiteInstanceImpl*>(web_contents->GetSiteInstance());
  EXPECT_FALSE(instance1->GetSiteInfo().are_v8_optimizations_disabled());

  // 2. Change flags and navigate to a.com again.
  client.SetV8OptimizationsDisabledForHost("a.com", true);
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  scoped_refptr<SiteInstanceImpl> instance2 =
      static_cast<SiteInstanceImpl*>(web_contents->GetSiteInstance());

  // Verify that NO swap occurred because the feature is disabled.
  EXPECT_TRUE(instance1->IsRelatedSiteInstance(instance2.get()));
  // Verify that the toggled flag also did not apply.
  EXPECT_FALSE(instance2->GetSiteInfo().are_v8_optimizations_disabled());
}

// Tests that a renderer process cannot send IPCs through an old
// RenderFrameProxyHost to manipulate a window that has navigated to a different
// BrowsingInstance, even if the new BrowsingInstance happens to share a process
// with the old one (e.g., due to subframe process reuse).
IN_PROC_BROWSER_TEST_P(
    RenderFrameHostManagerTest,
    ProxyIgnoresRequestsFromOldBrowsingInstanceWithProcessReuse) {
  StartEmbeddedServer();
  DisableBackForwardCache(BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);

  // Ensure that all sites in this test 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", "d.com"});

  // Navigate to A1.
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url_a));
  RenderFrameHostImpl* a1_rfh =
      static_cast<WebContentsImpl*>(shell()->web_contents())
          ->GetPrimaryMainFrame();

  // A1 opens A2 in a new window.
  ShellAddedObserver shell2_observer;
  EXPECT_TRUE(ExecJs(shell(), "window.open('/title2.html', 'window2');"));
  Shell* shell2 = shell2_observer.GetShell();
  EXPECT_TRUE(WaitForLoadStop(shell2->web_contents()));

  // A2 navigates to B2 in the same BCG1. Window 2 now has a proxy in A's
  // process.
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURLFromRenderer(shell2, url_b));
  RenderFrameHostImpl* b2_rfh =
      static_cast<WebContentsImpl*>(shell2->web_contents())
          ->GetPrimaryMainFrame();
  RenderFrameProxyHost* window2_proxy_in_bcg1 =
      b2_rfh->browsing_context_state()->GetRenderFrameProxyHost(
          a1_rfh->GetSiteInstance()->group());
  EXPECT_TRUE(window2_proxy_in_bcg1);

  // B2's opener should be A1.
  EXPECT_EQ(a1_rfh->frame_tree_node(), b2_rfh->frame_tree_node()->opener());

  // The user navigates B2 to C2 in BCG2. Use a browser-initiated navigation to
  // force a BrowsingInstance swap. Be careful not to use something like a WebUI
  // for this, since that would prevent a subsequent window.open() from staying
  // in the same BCG2, which is needed in this test.
  GURL url_c(embedded_test_server()->GetURL("c.com", "/title2.html"));
  EXPECT_TRUE(NavigateToURL(shell2, url_c));
  RenderFrameHostImpl* c2_rfh =
      static_cast<WebContentsImpl*>(shell2->web_contents())
          ->GetPrimaryMainFrame();
  EXPECT_FALSE(c2_rfh->GetSiteInstance()->group()->IsRelatedSiteInstanceGroup(
      a1_rfh->GetSiteInstance()->group()));

  // C2's opener stays as A1.
  EXPECT_EQ(a1_rfh->frame_tree_node(), c2_rfh->frame_tree_node()->opener());

  // C2 opens another page D3 in a new window that embeds A3 as a subframe.
  // This will create a proxy for C2 in BCG2 in both D3's and A3's process.
  GURL url_d(embedded_test_server()->GetURL(
      "d.com", "/cross_site_iframe_factory.html?d(a)"));
  ShellAddedObserver shell3_observer;
  EXPECT_TRUE(
      ExecJs(shell2, "window.open('" + url_d.spec() + "', 'window3');"));
  Shell* shell3 = shell3_observer.GetShell();
  EXPECT_TRUE(WaitForLoadStop(shell3->web_contents()));
  RenderFrameHostImpl* d3_rfh =
      static_cast<WebContentsImpl*>(shell3->web_contents())
          ->GetPrimaryMainFrame();
  RenderFrameHostImpl* a3_rfh = d3_rfh->child_at(0)->current_frame_host();

  // Verify A3 shares process with A1 (due to subframe process reuse).
  EXPECT_EQ(a1_rfh->GetProcess(), a3_rfh->GetProcess());

  // A1 and A3 should be in different BCGs.
  EXPECT_FALSE(a1_rfh->GetSiteInstance()->group()->IsRelatedSiteInstanceGroup(
      a3_rfh->GetSiteInstance()->group()));

  // Get the proxy for Window 2 (shell2)'s main frame in A3's
  // SiteInstanceGroup.
  RenderFrameProxyHost* window2_proxy_in_bcg2 =
      c2_rfh->browsing_context_state()->GetRenderFrameProxyHost(
          a3_rfh->GetSiteInstance()->group());
  EXPECT_TRUE(window2_proxy_in_bcg2);

  // Even though they're in the same process and representing the same window 2,
  // these two proxies are different, corresponding to two different
  // SiteInstanceGroups in different BCGs.
  EXPECT_NE(window2_proxy_in_bcg1, window2_proxy_in_bcg2);
  EXPECT_NE(
      window2_proxy_in_bcg1->site_instance_group()->browsing_instance_id(),
      window2_proxy_in_bcg2->site_instance_group()->browsing_instance_id());
  EXPECT_EQ(window2_proxy_in_bcg1->GetProcess(),
            window2_proxy_in_bcg2->GetProcess());
  EXPECT_EQ(window2_proxy_in_bcg1->frame_tree_node(),
            window2_proxy_in_bcg2->frame_tree_node());
  // Check that the window2 proxy in BCG1 can still be found. Note that this
  // lookup will no longer be possible to do via `c2_rfh` if
  // NewBrowsingContextStateOnBrowsingContextGroupSwap is launched (see
  // https://crbug.com/40239885 and https://crbug.com/40205442).
  EXPECT_EQ(window2_proxy_in_bcg1,
            c2_rfh->browsing_context_state()->GetRenderFrameProxyHost(
                a1_rfh->GetSiteInstance()->group()));

  // Verify opener of Window 2 is still A1.
  EXPECT_EQ(a1_rfh->frame_tree_node(), c2_rfh->frame_tree_node()->opener());

  // A3 can update C2's opener to itself.
  EXPECT_TRUE(ExecJs(a3_rfh, "window.open('', 'window2');"));
  EXPECT_EQ(a3_rfh->frame_tree_node(), c2_rfh->frame_tree_node()->opener());

  // In contrast, A1 shouldn't be able to update C2's opener to itself via
  // window.open(), as its proxy to window 2 is in a different BrowsingInstance
  // than C2.
  EXPECT_TRUE(ExecJs(a1_rfh, "window.open('', 'window2');"));
  EXPECT_EQ(a3_rfh->frame_tree_node(), c2_rfh->frame_tree_node()->opener());

  // Also, try to disown openers directly through the appropriate proxies,
  // verifying our assumptions more directly (and simulating what a compromised
  // renderer could do).
  //
  // If A1 tries to clear Window 2's opener via its old proxy, this should be
  // dropped because A1 is in BCG1 and Window 2 is now in BCG2. The opener
  // should not be changed.
  static_cast<blink::mojom::RemoteFrameHost*>(window2_proxy_in_bcg1)
      ->DidChangeOpener(std::nullopt);
  EXPECT_EQ(a3_rfh->frame_tree_node(), c2_rfh->frame_tree_node()->opener());

  // If A3 clears Window 2's opener via its proxy, this should succeed because
  // A3 and Window 2 are in the same BCG.
  static_cast<blink::mojom::RemoteFrameHost*>(window2_proxy_in_bcg2)
      ->DidChangeOpener(std::nullopt);
  EXPECT_EQ(nullptr, c2_rfh->frame_tree_node()->opener());
}

INSTANTIATE_TEST_SUITE_P(All,
                         RenderFrameHostManagerTest,
                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(
    All,
    SwapBrowsingInstancesForDifferentProcessFlagsDisabledTest,
    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,
    RenderFrameHostManagerDisableDeferSpeculativeRFHCreationTest,
    testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
                         RenderFrameHostManagerClearWindowNameTest,
                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
INSTANTIATE_TEST_SUITE_P(All,
                         ResumeNavigationWithSpeculativeRFHProcessGoneTest,
                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
}  // namespace content
