910e62b5创建于 1月15日历史提交
// Copyright 2014 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/test/content_browser_test_utils_internal.h"

#include <stddef.h>

#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>

#include "base/containers/stack.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/escape.h"
#include "base/strings/stringprintf.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/browser/renderer_host/delegated_frame_host.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_widget_host_factory.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/frame_messages.mojom.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_javascript_dialog_manager.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "third_party/blink/public/common/frame/frame_visual_properties.h"

namespace content {

bool NavigateFrameToURL(FrameTreeNode* node, const GURL& url) {
  TestFrameNavigationObserver observer(node);
  NavigationController::LoadURLParams params(url);
  params.transition_type = ui::PAGE_TRANSITION_LINK;
  params.frame_tree_node_id = node->frame_tree_node_id();
  FrameTree& frame_tree = node->frame_tree();

  node->navigator().controller().LoadURLWithParams(params);
  observer.Wait();

  if (!observer.last_navigation_succeeded()) {
    DLOG(WARNING) << "Navigation did not succeed: " << url;
    return false;
  }

  // It's possible for JS handlers triggered during the navigation to remove
  // the node, so retrieve it by ID again to check if that occurred.
  node = frame_tree.FindByID(params.frame_tree_node_id);

  if (node && url != node->current_url()) {
    DLOG(WARNING) << "Expected URL " << url << " but observed "
                  << node->current_url();
    return false;
  }
  return true;
}

void SetShouldProceedOnBeforeUnload(Shell* shell, bool proceed, bool success) {
  ShellJavaScriptDialogManager* manager =
      static_cast<ShellJavaScriptDialogManager*>(
          shell->GetJavaScriptDialogManager(shell->web_contents()));
  manager->set_should_proceed_on_beforeunload(proceed, success);
}

RenderFrameHost* ConvertToRenderFrameHost(FrameTreeNode* frame_tree_node) {
  return frame_tree_node->current_frame_host();
}

bool NavigateToURLInSameBrowsingInstance(Shell* window, const GURL& url) {
  TestNavigationObserver observer(window->web_contents());
  // Using a PAGE_TRANSITION_LINK transition with a browser-initiated
  // navigation forces it to stay in the current BrowsingInstance, as normally
  // that transition is used by renderer-initiated navigations.
  window->LoadURLForFrame(url, std::string(),
                          ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK));
  observer.Wait();

  if (!IsLastCommittedEntryOfPageType(window->web_contents(),
                                      PAGE_TYPE_NORMAL)) {
    NavigationEntry* last_entry =
        window->web_contents()->GetController().GetLastCommittedEntry();
    DLOG(WARNING) << "last_entry->GetPageType() = "
                  << (last_entry ? last_entry->GetPageType() : -1);
    return false;
  }

  if (window->web_contents()->GetLastCommittedURL() != url) {
    DLOG(WARNING) << "window->web_contents()->GetLastCommittedURL() = "
                  << window->web_contents()->GetLastCommittedURL()
                  << "; url = " << url;
    return false;
  }

  return true;
}

bool IsExpectedSubframeErrorTransition(SiteInstance* start_site_instance,
                                       SiteInstance* end_site_instance) {
  bool site_instances_are_equal = (start_site_instance == end_site_instance);

  // AgentClusterKey mismatch will trigger a SiteInstance switch.
  if (static_cast<SiteInstanceImpl*>(start_site_instance)
              ->GetSiteInfo()
              .agent_cluster_key() !=
          static_cast<SiteInstanceImpl*>(end_site_instance)
              ->GetSiteInfo()
              .agent_cluster_key() &&
      !site_instances_are_equal) {
    return true;
  }

  bool is_error_page_site_instance =
      (static_cast<SiteInstanceImpl*>(end_site_instance)
           ->GetSiteInfo()
           .is_error_page());

  if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(
          /*in_main_frame=*/false)) {
    return site_instances_are_equal && !is_error_page_site_instance;
  } else {
    return !site_instances_are_equal && is_error_page_site_instance;
  }
}

RenderFrameHost* CreateSubframe(WebContentsImpl* web_contents,
                                std::string frame_id,
                                const GURL& url,
                                bool wait_for_navigation) {
  return CreateSubframe(
      web_contents->GetPrimaryFrameTree().root()->current_frame_host(),
      frame_id, url, wait_for_navigation, {});
}

RenderFrameHost* CreateSubframe(RenderFrameHost* parent,
                                std::string frame_id,
                                const GURL& url,
                                bool wait_for_navigation) {
  return CreateSubframe(parent, frame_id, url, wait_for_navigation, {});
}

RenderFrameHost* CreateSubframe(RenderFrameHost* parent,
                                std::string frame_id,
                                const GURL& url,
                                bool wait_for_navigation,
                                ExtraParams extra_params) {
  WebContents* web_contents = WebContents::FromRenderFrameHost(parent);
  RenderFrameHostCreatedObserver subframe_created_observer(web_contents);
  TestNavigationObserver subframe_nav_observer(web_contents);

  EXPECT_TRUE(
      ExecJs(parent, JsReplace(R"(
    var iframe = document.createElement('iframe');
    iframe.id = $1; //frame_id
    if ($2) {
      iframe.src = $2; // url
    }
    if ($3) {
      iframe.sandbox = $3; // extra_params.sandbox_flags
    }
    document.body.appendChild(iframe);
  )",
                               frame_id, url, extra_params.sandbox_flags)));

  subframe_created_observer.Wait();
  if (wait_for_navigation)
    subframe_nav_observer.Wait();
  FrameTreeNode* root =
      static_cast<RenderFrameHostImpl*>(parent)->frame_tree_node();
  return root->child_at(root->child_count() - 1)->current_frame_host();
}

std::vector<RenderFrameHostImpl*> CollectAllRenderFrameHosts(
    RenderFrameHostImpl* starting_rfh) {
  std::vector<RenderFrameHostImpl*> visited_frames;
  starting_rfh->ForEachRenderFrameHostImpl(
      [&](RenderFrameHostImpl* rfh) { visited_frames.push_back(rfh); });
  return visited_frames;
}

std::vector<RenderFrameHostImpl*>
CollectAllRenderFrameHostsIncludingSpeculative(
    RenderFrameHostImpl* starting_rfh) {
  std::vector<RenderFrameHostImpl*> visited_frames;
  starting_rfh->ForEachRenderFrameHostImplIncludingSpeculative(
      [&](RenderFrameHostImpl* rfh) { visited_frames.push_back(rfh); });
  return visited_frames;
}

std::vector<RenderFrameHostImpl*> CollectAllRenderFrameHosts(
    WebContentsImpl* web_contents) {
  std::vector<RenderFrameHostImpl*> visited_frames;
  web_contents->ForEachRenderFrameHostImpl(
      [&](RenderFrameHostImpl* rfh) { visited_frames.push_back(rfh); });
  return visited_frames;
}

std::vector<RenderFrameHostImpl*>
CollectAllRenderFrameHostsIncludingSpeculative(WebContentsImpl* web_contents) {
  std::vector<RenderFrameHostImpl*> visited_frames;
  web_contents->ForEachRenderFrameHostImplIncludingSpeculative(
      [&](RenderFrameHostImpl* rfh) { visited_frames.push_back(rfh); });
  return visited_frames;
}

Shell* OpenBlankWindow(WebContentsImpl* web_contents) {
  FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
  ShellAddedObserver new_shell_observer;
  EXPECT_TRUE(ExecJs(root, "last_opened_window = window.open()"));
  Shell* new_shell = new_shell_observer.GetShell();
  EXPECT_NE(new_shell->web_contents(), web_contents);
  EXPECT_TRUE(new_shell->web_contents()
                  ->GetController()
                  .GetLastCommittedEntry()
                  ->IsInitialEntry());
  EXPECT_EQ(1, new_shell->web_contents()->GetController().GetEntryCount());
  return new_shell;
}

Shell* OpenWindow(WebContentsImpl* web_contents, const GURL& url) {
  FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
  ShellAddedObserver new_shell_observer;
  EXPECT_TRUE(
      ExecJs(root, JsReplace("last_opened_window = window.open($1)", url)));
  Shell* new_shell = new_shell_observer.GetShell();
  EXPECT_NE(new_shell->web_contents(), web_contents);
  return new_shell;
}

FrameTreeVisualizer::FrameTreeVisualizer() = default;

FrameTreeVisualizer::~FrameTreeVisualizer() = default;

std::string FrameTreeVisualizer::DepictFrameTree(FrameTreeNode* root) {
  // Tracks the sites actually used in this depiction.
  std::map<std::string, SiteInstance*> legend;

  // Traversal 1: Assign names to current frames. This ensures that the first
  // call to the pretty-printer will result in a naming of the site instances
  // that feels natural and stable.
  base::stack<FrameTreeNode*> to_explore;
  for (to_explore.push(root); !to_explore.empty();) {
    FrameTreeNode* node = to_explore.top();
    to_explore.pop();
    for (size_t i = node->child_count(); i-- != 0;) {
      to_explore.push(node->child_at(i));
    }

    RenderFrameHost* current = node->render_manager()->current_frame_host();
    legend[GetName(current->GetSiteInstance())] = current->GetSiteInstance();
  }

  // Traversal 2: Assign names to the pending/speculative frames. For stability
  // of assigned names it's important to do this before trying to name the
  // proxies, which have a less well defined order.
  for (to_explore.push(root); !to_explore.empty();) {
    FrameTreeNode* node = to_explore.top();
    to_explore.pop();
    for (size_t i = node->child_count(); i-- != 0;) {
      to_explore.push(node->child_at(i));
    }

    RenderFrameHost* spec = node->render_manager()->speculative_frame_host();
    if (spec)
      legend[GetName(spec->GetSiteInstance())] = spec->GetSiteInstance();
  }

  // Traversal 3: Assign names to the SiteInstances within each group's proxies
  // (which are associated with SiteInstanceGroups instead of SiteInstances) and
  // add them to |legend| too. Typically, only openers should have their names
  // assigned this way.
  for (to_explore.push(root); !to_explore.empty();) {
    FrameTreeNode* node = to_explore.top();
    to_explore.pop();
    for (size_t i = node->child_count(); i-- != 0;) {
      to_explore.push(node->child_at(i));
    }

    // Sort the proxies by SiteInstanceGroup ID to avoid unordered_map ordering.
    std::vector<SiteInstance*> site_instances;
    for (const auto& proxy_pair :
         node->render_manager()->GetAllProxyHostsForTesting()) {
      SiteInstanceGroup* group = proxy_pair.second->site_instance_group();
      for (raw_ptr<SiteInstanceImpl> instance :
           group->site_instances_for_testing()) {
        site_instances.push_back(instance);
      }
    }
    std::sort(site_instances.begin(), site_instances.end(),
              [](SiteInstance* lhs, SiteInstance* rhs) {
                return lhs->GetId() < rhs->GetId();
              });

    for (SiteInstance* site_instance : site_instances)
      legend[GetName(site_instance)] = site_instance;
  }

  // Traversal 4: Now that all names are assigned, make a big loop to pretty-
  // print the tree. Each iteration produces exactly one line of format.
  std::string result;
  for (to_explore.push(root); !to_explore.empty();) {
    FrameTreeNode* node = to_explore.top();
    to_explore.pop();
    for (size_t i = node->child_count(); i-- != 0;) {
      to_explore.push(node->child_at(i));
    }

    // Draw the feeler line tree graphics by walking up to the root. A feeler
    // line is needed for each ancestor that is the last child of its parent.
    // This creates the ASCII art that looks like:
    //    Foo
    //      |--Foo
    //      |--Foo
    //      |    |--Foo
    //      |    +--Foo
    //      |         +--Foo
    //      +--Foo
    //           +--Foo
    //
    // TODO(nick): Make this more elegant.
    std::string line;
    if (node != root) {
      if (node->parent()->child_at(node->parent()->child_count() - 1) != node)
        line = "  |--";
      else
        line = "  +--";
      for (RenderFrameHostImpl* up = node->parent();
           up != root->current_frame_host(); up = up->GetParent()) {
        if (up->GetParent()
                ->child_at(up->GetParent()->child_count() - 1)
                ->current_frame_host() != up)
          line = "  |  " + line;
        else
          line = "     " + line;
      }
    }

    // Prefix one extra space of padding for two reasons. First, this helps the
    // diagram aligns nicely with the legend. Second, this makes it easier to
    // read the diffs that gtest spits out on EXPECT_EQ failure.
    line = " " + line;

    // Summarize the FrameTreeNode's state. Always show the site of the current
    // RenderFrameHost, and show any exceptional state of the node, like a
    // pending or speculative RenderFrameHost.
    RenderFrameHost* current = node->render_manager()->current_frame_host();
    RenderFrameHost* spec = node->render_manager()->speculative_frame_host();
    base::StringAppendF(&line, "Site %s",
                        GetName(current->GetSiteInstance()).c_str());
    if (spec) {
      base::StringAppendF(&line, " (%s speculative)",
                          GetName(spec->GetSiteInstance()).c_str());
    }

    // Show the SiteInstances of the RenderFrameProxyHosts of this node.
    const auto& proxy_host_map =
        node->render_manager()->GetAllProxyHostsForTesting();
    if (!proxy_host_map.empty()) {
      // Show a dashed line of variable length before the proxy list. Always at
      // least two dashes.
      line.append(" --");

      // To make proxy lists align vertically for the first three tree levels,
      // pad with dashes up to a first tab stop at column 19 (which works out to
      // text editor column 28 in the typical diagram fed to EXPECT_EQ as a
      // string literal). Lining the lists up vertically makes differences in
      // the proxy sets easier to spot visually. We choose not to use the
      // *actual* tree height here, because that would make the diagram's
      // appearance less stable as the tree's shape evolves.
      while (line.length() < 20) {
        line.append("-");
      }
      line.append(" proxies for");

      // Sort these alphabetically, to avoid hash_map ordering dependency.
      std::vector<std::string> sorted_proxy_hosts;
      for (const auto& proxy_pair : proxy_host_map) {
        sorted_proxy_hosts.push_back(
            GetGroupName(proxy_pair.second->site_instance_group()));
      }
      std::sort(sorted_proxy_hosts.begin(), sorted_proxy_hosts.end());
      for (std::string& proxy_name : sorted_proxy_hosts) {
        base::StringAppendF(&line, " %s", proxy_name.c_str());
      }
    }
    if (node != root)
      result.append("\n");
    result.append(line);
  }

  // Finally, show a legend with details of the site instances.
  const char* prefix = "Where ";
  for (auto& legend_entry : legend) {
    SiteInstanceImpl* site_instance =
        static_cast<SiteInstanceImpl*>(legend_entry.second);
    std::string description =
        GetUrlWithoutPort(site_instance->GetSiteURL()).spec();

    // data: URLs have site URLs of the form data:nonce, where the nonce is an
    // UnguessableToken. Make these deterministic for testing by using the
    // abbreviated letter for the site in the nonce. For example,
    // "data:nonce_A".
    if (site_instance->GetSiteURL().SchemeIs(url::kDataScheme)) {
      description =
          base::StringPrintf("data:nonce_%s", legend_entry.first.c_str());
    }

    base::StringAppendF(&result, "\n%s%s = %s", prefix,
                        legend_entry.first.c_str(), description.c_str());
    // Highlight some exceptionable conditions.
    if (site_instance->GetSiteInfo().is_sandboxed()) {
      result.append(" (sandboxed)");
    }
    if (site_instance->group()->active_frame_count() == 0)
      result.append(" (active_frame_count == 0)");
    if (!site_instance->GetProcess()->IsInitializedAndNotDead())
      result.append(" (no process)");
    prefix = "      ";
  }
  return result;
}

std::string FrameTreeVisualizer::GetName(SiteInstance* site_instance) {
  // Indices into the vector correspond to letters of the alphabet.
  size_t index =
      std::ranges::find(seen_site_instance_ids_, site_instance->GetId()) -
      seen_site_instance_ids_.begin();
  if (index == seen_site_instance_ids_.size())
    seen_site_instance_ids_.push_back(site_instance->GetId());

  // Whosoever writes a test using >=26 site instances shall be a lucky ducky.
  if (index < 25)
    return base::StringPrintf("%c", 'A' + static_cast<char>(index));
  else
    return base::StringPrintf("Z%d", static_cast<int>(index - 25));
}

std::string FrameTreeVisualizer::GetGroupName(SiteInstanceGroup* group) {
  // If there's only one SiteInstance in `group`, get the name of the
  // SiteInstance directly. This preserves test expectations for DepictFrameTree
  // uses that predate SiteInstanceGroup.
  if (group->site_instances_for_testing().size() == 1) {
    return GetName(*group->site_instances_for_testing().begin());
  }

  // Alphabetically sort the SiteInstances within the group.
  std::vector<std::string> sorted_instance_names;
  for (auto& site_instance : group->site_instances_for_testing()) {
    sorted_instance_names.push_back(GetName(site_instance));
  }
  std::sort(sorted_instance_names.begin(), sorted_instance_names.end());

  // Name the group using set notation.
  CHECK(sorted_instance_names.size() >= 1u);
  std::string result = "{";
  for (auto& site_instance_name : sorted_instance_names) {
    base::StringAppendF(&result, "%s,", site_instance_name.c_str());
  }
  result.resize(result.length() - 1);
  result.append("}");

  return result;
}

GURL FrameTreeVisualizer::GetUrlWithoutPort(const GURL& url) {
  GURL::Replacements replacements;
  replacements.ClearPort();
  return url.ReplaceComponents(replacements);
}

std::string DepictFrameTree(FrameTreeNode& root) {
  return FrameTreeVisualizer().DepictFrameTree(&root);
}

Shell* OpenPopup(const ToRenderFrameHost& opener,
                 const GURL& url,
                 const std::string& name) {
  return OpenPopup(opener, url, name, "", true);
}

Shell* OpenPopup(const ToRenderFrameHost& opener,
                 const GURL& url,
                 const std::string& name,
                 const std::string& features,
                 bool expect_return_from_window_open) {
  TestNavigationObserver observer(url);
  observer.StartWatchingNewWebContents();

  ShellAddedObserver new_shell_observer;
  std::string popup_script = "!!window.open('" + url.spec() + "', '" + name +
                             "', '" + features + "');";
  bool did_create_popup = EvalJs(opener, popup_script).ExtractBool();

  if (!(did_create_popup || !expect_return_from_window_open)) {
    return nullptr;
  }

  observer.Wait();

  Shell* new_shell = new_shell_observer.GetShell();
  EXPECT_EQ(
      url,
      new_shell->web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
  return new_shell_observer.GetShell();
}

FileChooserDelegate::FileChooserDelegate(std::vector<base::FilePath> files,
                                         const base::FilePath& base_dir,
                                         base::OnceClosure callback)
    : files_(std::move(files)),
      base_dir_(base_dir),
      callback_(std::move(callback)) {}

FileChooserDelegate::FileChooserDelegate(const base::FilePath& file,
                                         base::OnceClosure callback)
    : FileChooserDelegate(std::vector<base::FilePath>(1, file),
                          base::FilePath(),
                          std::move(callback)) {}

FileChooserDelegate::~FileChooserDelegate() = default;

void FileChooserDelegate::RunFileChooser(
    RenderFrameHost* render_frame_host,
    scoped_refptr<content::FileSelectListener> listener,
    const blink::mojom::FileChooserParams& params) {
  // |base_dir_| should be set for and only for |kUploadFolder| mode.
  DCHECK(base_dir_.empty() ==
         (params.mode != blink::mojom::FileChooserParams::Mode::kUploadFolder));
  // Send the selected files to the renderer process.
  std::vector<blink::mojom::FileChooserFileInfoPtr> files;
  for (const auto& file : files_) {
    auto file_info = blink::mojom::FileChooserFileInfo::NewNativeFile(
        blink::mojom::NativeFileInfo::New(file, std::u16string(),
                                          std::vector<std::u16string>()));
    files.push_back(std::move(file_info));
  }
  listener->FileSelected(std::move(files), base_dir_, params.mode);

  params_ = params.Clone();
  if (callback_)
    std::move(callback_).Run();
}

FrameTestNavigationManager::FrameTestNavigationManager(
    FrameTreeNodeId filtering_frame_tree_node_id,
    WebContents* web_contents,
    const GURL& url)
    : TestNavigationManager(web_contents, url),
      filtering_frame_tree_node_id_(filtering_frame_tree_node_id) {}

bool FrameTestNavigationManager::ShouldMonitorNavigation(
    NavigationHandle* handle) {
  return TestNavigationManager::ShouldMonitorNavigation(handle) &&
         handle->GetFrameTreeNodeId() == filtering_frame_tree_node_id_;
}

UrlCommitObserver::UrlCommitObserver(FrameTreeNode* frame_tree_node,
                                     const GURL& url)
    : content::WebContentsObserver(WebContents::FromRenderFrameHost(
          frame_tree_node->current_frame_host())),
      frame_tree_node_id_(frame_tree_node->frame_tree_node_id()),
      url_(url) {}

UrlCommitObserver::~UrlCommitObserver() {}

void UrlCommitObserver::Wait() {
  run_loop_.Run();
}

void UrlCommitObserver::DidFinishNavigation(
    NavigationHandle* navigation_handle) {
  if (navigation_handle->HasCommitted() &&
      !navigation_handle->IsErrorPage() &&
      navigation_handle->GetURL() == url_ &&
      navigation_handle->GetFrameTreeNodeId() == frame_tree_node_id_) {
    run_loop_.Quit();
  }
}

RenderProcessHostBadIpcMessageWaiter::RenderProcessHostBadIpcMessageWaiter(
    RenderProcessHost* render_process_host)
    : internal_waiter_(render_process_host,
                       "Stability.BadMessageTerminated.Content") {}

std::optional<bad_message::BadMessageReason>
RenderProcessHostBadIpcMessageWaiter::Wait() {
  std::optional<int> internal_result = internal_waiter_.Wait();
  if (!internal_result.has_value())
    return std::nullopt;
  return static_cast<bad_message::BadMessageReason>(internal_result.value());
}

UnresponsiveRendererObserver::UnresponsiveRendererObserver(
    WebContents* web_contents)
    : WebContentsObserver(web_contents) {}

UnresponsiveRendererObserver::~UnresponsiveRendererObserver() = default;

RenderProcessHost* UnresponsiveRendererObserver::Wait(base::TimeDelta timeout) {
  if (!captured_render_process_host_) {
    base::OneShotTimer timer;
    timer.Start(FROM_HERE, timeout, run_loop_.QuitClosure());
    run_loop_.Run();
    timer.Stop();
  }
  return captured_render_process_host_;
}

void UnresponsiveRendererObserver::OnRendererUnresponsive(
    RenderProcessHost* render_process_host) {
  captured_render_process_host_ = render_process_host;
  run_loop_.Quit();
}

BeforeUnloadBlockingDelegate::BeforeUnloadBlockingDelegate(
    WebContentsImpl* web_contents)
    : web_contents_(web_contents) {
  web_contents_->SetDelegate(this);
}

BeforeUnloadBlockingDelegate::~BeforeUnloadBlockingDelegate() {
  if (!callback_.is_null())
    std::move(callback_).Run(true, std::u16string());

  web_contents_->SetDelegate(nullptr);
}

void BeforeUnloadBlockingDelegate::Wait() {
  run_loop_->Run();
  run_loop_ = std::make_unique<base::RunLoop>();
}

JavaScriptDialogManager*
BeforeUnloadBlockingDelegate::GetJavaScriptDialogManager(WebContents* source) {
  return this;
}

bool BeforeUnloadBlockingDelegate::IsBackForwardCacheSupported(
    WebContents& web_contents) {
  return true;
}

void BeforeUnloadBlockingDelegate::RunJavaScriptDialog(
    WebContents* web_contents,
    RenderFrameHost* render_frame_host,
    JavaScriptDialogType dialog_type,
    const std::u16string& message_text,
    const std::u16string& default_prompt_text,
    DialogClosedCallback callback,
    bool* did_suppress_message) {
  NOTREACHED();
}

void BeforeUnloadBlockingDelegate::RunBeforeUnloadDialog(
    WebContents* web_contents,
    RenderFrameHost* render_frame_host,
    bool is_reload,
    DialogClosedCallback callback) {
  callback_ = std::move(callback);
  run_loop_->Quit();
}

bool BeforeUnloadBlockingDelegate::HandleJavaScriptDialog(
    WebContents* web_contents,
    bool accept,
    const std::u16string* prompt_override) {
  NOTREACHED();
}

FrameNavigateParamsCapturer::FrameNavigateParamsCapturer(WebContents* contents)
    : WebContentsObserver(contents) {}

FrameNavigateParamsCapturer::FrameNavigateParamsCapturer(FrameTreeNode* node)
    : WebContentsObserver(
          WebContents::FromRenderFrameHost(node->current_frame_host())),
      frame_tree_node_id_(node->frame_tree_node_id()) {}

FrameNavigateParamsCapturer::~FrameNavigateParamsCapturer() = default;

void FrameNavigateParamsCapturer::DidFinishNavigation(
    NavigationHandle* navigation_handle) {
  if (!navigation_handle->HasCommitted() ||
      (frame_tree_node_id_.has_value() &&
       navigation_handle->GetFrameTreeNodeId() !=
           frame_tree_node_id_.value()) ||
      navigations_remaining_ == 0) {
    return;
  }

  --navigations_remaining_;
  transitions_.push_back(navigation_handle->GetPageTransition());
  urls_.push_back(navigation_handle->GetURL());
  navigation_types_.push_back(
      NavigationRequest::From(navigation_handle)->navigation_type());
  is_same_documents_.push_back(navigation_handle->IsSameDocument());
  did_replace_entries_.push_back(navigation_handle->DidReplaceEntry());
  is_renderer_initiateds_.push_back(navigation_handle->IsRendererInitiated());
  has_user_gestures_.push_back(navigation_handle->HasUserGesture());
  is_overriding_user_agents_.push_back(
      NavigationRequest::From(navigation_handle)->is_overriding_user_agent());
  is_error_pages_.push_back(navigation_handle->IsErrorPage());
  if (!navigations_remaining_ &&
      (!web_contents()->IsLoading() || !wait_for_load_))
    loop_.Quit();
}

void FrameNavigateParamsCapturer::Wait() {
  loop_.Run();
}

void FrameNavigateParamsCapturer::DidStopLoading() {
  if (!navigations_remaining_)
    loop_.Quit();
}

RenderFrameHostCreatedObserver::RenderFrameHostCreatedObserver(
    WebContents* web_contents)
    : WebContentsObserver(web_contents) {}

RenderFrameHostCreatedObserver::RenderFrameHostCreatedObserver(
    WebContents* web_contents,
    int expected_frame_count)
    : WebContentsObserver(web_contents),
      expected_frame_count_(expected_frame_count) {}

RenderFrameHostCreatedObserver::RenderFrameHostCreatedObserver(
    WebContents* web_contents,
    OnRenderFrameHostCreatedCallback on_rfh_created)
    : WebContentsObserver(web_contents),
      on_rfh_created_(std::move(on_rfh_created)) {}

RenderFrameHostCreatedObserver::~RenderFrameHostCreatedObserver() = default;

RenderFrameHost* RenderFrameHostCreatedObserver::Wait() {
  if (frames_created_ < expected_frame_count_)
    run_loop_.Run();

  return last_rfh_;
}

void RenderFrameHostCreatedObserver::RenderFrameCreated(
    RenderFrameHost* render_frame_host) {
  frames_created_++;
  last_rfh_ = render_frame_host;
  if (on_rfh_created_)
    on_rfh_created_.Run(render_frame_host);
  if (frames_created_ == expected_frame_count_)
    run_loop_.Quit();
}

BackForwardCache::DisabledReason RenderFrameHostDisabledForTestingReason() {
  static const BackForwardCache::DisabledReason reason =
      BackForwardCache::DisabledReason(
          BackForwardCache::DisabledSource::kTesting, 0, "disabled for testing",
          /*context=*/"", "disabled");
  return reason;
}

void DisableBFCacheForRFHForTesting(
    content::RenderFrameHost* render_frame_host) {
  content::BackForwardCache::DisableForRenderFrameHost(
      render_frame_host, RenderFrameHostDisabledForTestingReason());
}

void DisableBFCacheForRFHForTesting(content::GlobalRenderFrameHostId id) {
  content::BackForwardCache::DisableForRenderFrameHost(
      id, RenderFrameHostDisabledForTestingReason());
}

void UserAgentInjector::DidStartNavigation(
    NavigationHandle* navigation_handle) {
  web_contents()->SetUserAgentOverride(user_agent_override_, false);
  navigation_handle->SetIsOverridingUserAgent(is_overriding_user_agent_);
}

RenderFrameHostImplWrapper::RenderFrameHostImplWrapper(RenderFrameHost* rfh)
    : RenderFrameHostWrapper(rfh) {}

RenderFrameHostImpl* RenderFrameHostImplWrapper::get() const {
  return static_cast<RenderFrameHostImpl*>(RenderFrameHostWrapper::get());
}

RenderFrameHostImpl& RenderFrameHostImplWrapper::operator*() const {
  DCHECK(get());
  return *get();
}

RenderFrameHostImpl* RenderFrameHostImplWrapper::operator->() const {
  DCHECK(get());
  return get();
}

InactiveRenderFrameHostDeletionObserver::
    InactiveRenderFrameHostDeletionObserver(WebContents* content)
    : WebContentsObserver(content) {}

InactiveRenderFrameHostDeletionObserver::
    ~InactiveRenderFrameHostDeletionObserver() = default;

void InactiveRenderFrameHostDeletionObserver::Wait() {
  // Some RenderFrameHost may remain in the BackForwardCache and or as
  // prerendered pages. Trigger deletion for them asynchronously.
  static_cast<WebContentsImpl*>(web_contents())
      ->GetController()
      .GetBackForwardCache()
      .Flush();
  static_cast<WebContentsImpl*>(web_contents())
      ->GetPrerenderHostRegistry()
      ->CancelAllHostsForTesting();

  for (RenderFrameHost* rfh : CollectAllRenderFrameHosts(web_contents())) {
    // Keep track of all currently inactive RenderFrameHosts so that we can wait
    // for all of them to be deleted.
    if (!rfh->IsActive() && rfh->IsRenderFrameLive())
      inactive_rfhs_.insert(rfh);
  }
  loop_ = std::make_unique<base::RunLoop>();
  CheckCondition();
  loop_->Run();
}

void InactiveRenderFrameHostDeletionObserver::RenderFrameDeleted(
    RenderFrameHost* rfh) {
  if (inactive_rfhs_.count(rfh) == 0)
    return;
  inactive_rfhs_.erase(rfh);
  CheckCondition();
}

void InactiveRenderFrameHostDeletionObserver::CheckCondition() {
  if (loop_ && inactive_rfhs_.empty())
    loop_->Quit();
}

void TestNavigationObserverInternal::OnDidFinishNavigation(
    NavigationHandle* navigation_handle) {
  last_navigation_type_ =
      navigation_handle->HasCommitted()
          ? static_cast<NavigationRequest*>(navigation_handle)
                ->navigation_type()
          : NAVIGATION_TYPE_UNKNOWN;
  TestNavigationObserver::OnDidFinishNavigation(navigation_handle);
}

RenderFrameHostImpl* DescendantRenderFrameHostAtInternal(
    RenderFrameHostImpl* rfh,
    std::string path,
    std::vector<size_t>& descendant_indices) {
  if (descendant_indices.size() == 0)
    return rfh;
  size_t index = descendant_indices[0];
  descendant_indices.erase(descendant_indices.begin());
  CHECK_LT(index, rfh->child_count()) << path;
  FrameTreeNode* node = rfh->child_at(index);
  path = base::StringPrintf("%s[%zu]", path.c_str(), index);
  return DescendantRenderFrameHostAtInternal(node->current_frame_host(), path,
                                             descendant_indices);
}

RenderFrameHostImpl* DescendantRenderFrameHostImplAt(
    const ToRenderFrameHost& adapter,
    std::vector<size_t> descendant_indices) {
  return DescendantRenderFrameHostAtInternal(
      static_cast<RenderFrameHostImpl*>(adapter.render_frame_host()), "rfh",
      descendant_indices);
}

EffectiveURLContentBrowserTestContentBrowserClient::
    EffectiveURLContentBrowserTestContentBrowserClient(
        bool requires_dedicated_process)
    : helper_(requires_dedicated_process) {}

EffectiveURLContentBrowserTestContentBrowserClient::
    EffectiveURLContentBrowserTestContentBrowserClient(
        const GURL& url_to_modify,
        const GURL& url_to_return,
        bool requires_dedicated_process)
    : helper_(requires_dedicated_process) {
  AddTranslation(url_to_modify, url_to_return);
}

EffectiveURLContentBrowserTestContentBrowserClient::
    ~EffectiveURLContentBrowserTestContentBrowserClient() = default;

void EffectiveURLContentBrowserTestContentBrowserClient::AddTranslation(
    const GURL& url_to_modify,
    const GURL& url_to_return) {
  helper_.AddTranslation(url_to_modify, url_to_return);
}

std::optional<GURL>
EffectiveURLContentBrowserTestContentBrowserClient::GetEffectiveURL(
    BrowserContext* browser_context,
    const GURL& url) {
  return helper_.GetEffectiveURL(url);
}

bool EffectiveURLContentBrowserTestContentBrowserClient::
    DoesSiteRequireDedicatedProcess(BrowserContext* browser_context,
                                    const GURL& effective_site_url) {
  return helper_.DoesSiteRequireDedicatedProcess(browser_context,
                                                 effective_site_url);
}

CustomStoragePartitionBrowserClient::CustomStoragePartitionBrowserClient(
    const GURL& site_to_isolate)
    : site_to_isolate_(site_to_isolate) {}

StoragePartitionConfig
CustomStoragePartitionBrowserClient::GetStoragePartitionConfigForSite(
    BrowserContext* browser_context,
    const GURL& site) {
  // Override for |site_to_isolate_|.
  if (site == site_to_isolate_) {
    return StoragePartitionConfig::Create(
        browser_context, "blah_isolated_storage", "blah_isolated_storage",
        false /* in_memory */);
  }

  return StoragePartitionConfig::CreateDefault(browser_context);
}

CommitNavigationPauser::CommitNavigationPauser(RenderFrameHostImpl* rfh) {
  rfh->SetCommitCallbackInterceptorForTesting(this);
}

CommitNavigationPauser::~CommitNavigationPauser() = default;

void CommitNavigationPauser::WaitForCommitAndPause() {
  loop_.Run();
}

void CommitNavigationPauser::ResumePausedCommit() {
  // The caller is responsible for ensuring the paused request is still alive
  // and not discarded.
  DCHECK(paused_request_);
  paused_request_->GetRenderFrameHost()->DidCommitNavigation(
      paused_request_.get(), std::move(paused_params_),
      std::move(paused_interface_params_));
}

bool CommitNavigationPauser::WillProcessDidCommitNavigation(
    NavigationRequest* request,
    mojom::DidCommitProvisionalLoadParamsPtr* params,
    mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params) {
  request->GetRenderFrameHost()->SetCommitCallbackInterceptorForTesting(
      nullptr);

  paused_request_ = request->GetWeakPtr();
  paused_params_ = std::move(*params);
  paused_interface_params_ = std::move(*interface_params);

  loop_.Quit();

  // Ignore the commit message.
  return false;
}

namespace {

// Helper to return a 200 OK non-cacheable response for a first request, and
// redirect the second request to the URL indicated in the query param.
std::unique_ptr<net::test_server::HttpResponse>
RedirectToTargetOnSecondNavigation(
    unsigned int& navigation_counter,
    const net::test_server::HttpRequest& request) {
  ++navigation_counter;
  if (navigation_counter == 1) {
    auto http_response =
        std::make_unique<net::test_server::BasicHttpResponse>();
    http_response->set_code(net::HttpStatusCode::HTTP_OK);
    http_response->AddCustomHeader("Cache-Control",
                                   "no-store, must-revalidate");
    return http_response;
  }

  std::string url_from_query =
      base::UnescapeBinaryURLComponent(request.GetURL().query());
  auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_code(net::HttpStatusCode::HTTP_FOUND);
  http_response->AddCustomHeader("Location", url_from_query);
  return http_response;
}

}  // namespace

void AddRedirectOnSecondNavigationHandler(net::EmbeddedTestServer* server) {
  unsigned int navigation_counter = 0;
  server->RegisterDefaultHandler(base::BindRepeating(
      &net::test_server::HandlePrefixedRequest,
      "/redirect-on-second-navigation",
      base::BindRepeating(&RedirectToTargetOnSecondNavigation,
                          base::OwnedRef(navigation_counter))));
}

LoadingStartObserver::LoadingStartObserver(WebContents* web_contents,
                                           Callback callback)
    : WebContentsObserver(web_contents), callback_(std::move(callback)) {}

LoadingStartObserver::~LoadingStartObserver() = default;

void LoadingStartObserver::DidStartLoading() {
  callback_.Run();
}

LoadingStopObserver::LoadingStopObserver(WebContents* web_contents,
                                         Callback callback)
    : WebContentsObserver(web_contents), callback_(std::move(callback)) {}

LoadingStopObserver::~LoadingStopObserver() = default;

void LoadingStopObserver::DidStopLoading() {
  callback_.Run();
}

LoadFinishObserver::LoadFinishObserver(WebContents* web_contents,
                                       Callback callback)
    : WebContentsObserver(web_contents), callback_(std::move(callback)) {}

LoadFinishObserver::~LoadFinishObserver() = default;

void LoadFinishObserver::DidFinishLoad(RenderFrameHost* render_frame_host,
                                       const GURL& validated_url) {
  callback_.Run(render_frame_host, validated_url);
}

}  // namespace content