// 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 <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/ranges/algorithm.h"
#include "base/strings/stringprintf.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.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/site_instance_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/features.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 "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);
  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) {
  RenderFrameHostCreatedObserver subframe_created_observer(web_contents);
  TestNavigationObserver subframe_nav_observer(web_contents);
  if (url.is_empty()) {
    EXPECT_TRUE(ExecJs(web_contents, JsReplace(R"(
          var iframe = document.createElement('iframe');
          iframe.id = $1;
          document.body.appendChild(iframe);
      )",
                                               frame_id)));
  } else {
    EXPECT_TRUE(ExecJs(web_contents, JsReplace(R"(
          var iframe = document.createElement('iframe');
          iframe.id = $1;
          iframe.src = $2;
          document.body.appendChild(iframe);
      )",
                                               frame_id, url)));
  }
  subframe_created_observer.Wait();
  if (wait_for_navigation)
    subframe_nav_observer.Wait();
  FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
  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->ForEachRenderFrameHost(
      [&](RenderFrameHostImpl* rfh) { visited_frames.push_back(rfh); });
  return visited_frames;
}

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

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

std::vector<RenderFrameHostImpl*>
CollectAllRenderFrameHostsIncludingSpeculative(WebContentsImpl* web_contents) {
  std::vector<RenderFrameHostImpl*> visited_frames;
  web_contents->ForEachRenderFrameHostIncludingSpeculative(
      [&](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 proxies 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 SiteInstance ID to avoid unordered_map ordering.
    std::vector<SiteInstance*> site_instances;
    for (const auto& proxy_pair :
         node->render_manager()->GetAllProxyHostsForTesting()) {
      site_instances.push_back(proxy_pair.second->GetSiteInstance());
    }
    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(
            GetName(proxy_pair.second->GetSiteInstance()));
      }
      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 = site_instance->GetSiteURL().spec();
    base::StringAppendF(&result, "\n%s%s = %s", prefix,
                        legend_entry.first.c_str(), description.c_str());
    // Highlight some exceptionable conditions.
    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 =
      base::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 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()));
    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(
    int 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") {}

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

ShowPopupWidgetWaiter::ShowPopupWidgetWaiter(WebContentsImpl* web_contents,
                                             RenderFrameHostImpl* frame_host)
    : frame_host_(frame_host) {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
  web_contents_ = web_contents;
  web_contents_->set_show_popup_menu_callback_for_testing(base::BindOnce(
      &ShowPopupWidgetWaiter::ShowPopupMenu, base::Unretained(this)));
#endif
  frame_host_->SetCreateNewPopupCallbackForTesting(base::BindRepeating(
      &ShowPopupWidgetWaiter::DidCreatePopupWidget, base::Unretained(this)));
}

ShowPopupWidgetWaiter::~ShowPopupWidgetWaiter() {
  if (auto* rwhi = RenderWidgetHostImpl::FromID(process_id_, routing_id_)) {
    std::ignore =
        rwhi->popup_widget_host_receiver_for_testing().SwapImplForTesting(rwhi);
  }
  if (frame_host_)
    frame_host_->SetCreateNewPopupCallbackForTesting(base::NullCallback());
}

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

void ShowPopupWidgetWaiter::Stop() {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
  web_contents_->set_show_popup_menu_callback_for_testing(base::NullCallback());
#endif
  frame_host_->SetCreateNewPopupCallbackForTesting(base::NullCallback());
  frame_host_ = nullptr;
}

blink::mojom::PopupWidgetHost* ShowPopupWidgetWaiter::GetForwardingInterface() {
  DCHECK_NE(MSG_ROUTING_NONE, routing_id_);
  return RenderWidgetHostImpl::FromID(process_id_, routing_id_);
}

void ShowPopupWidgetWaiter::ShowPopup(const gfx::Rect& initial_rect,
                                      const gfx::Rect& initial_anchor_rect,
                                      ShowPopupCallback callback) {
  GetForwardingInterface()->ShowPopup(initial_rect, initial_anchor_rect,
                                      std::move(callback));
  initial_rect_ = initial_rect;
  run_loop_.Quit();
}

void ShowPopupWidgetWaiter::DidCreatePopupWidget(
    RenderWidgetHostImpl* render_widget_host) {
  process_id_ = render_widget_host->GetProcess()->GetID();
  routing_id_ = render_widget_host->GetRoutingID();
  // Swapped back in destructor from process_id_ and routing_id_ lookup.
  std::ignore = render_widget_host->popup_widget_host_receiver_for_testing()
                    .SwapImplForTesting(this);
}

#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
void ShowPopupWidgetWaiter::ShowPopupMenu(const gfx::Rect& bounds) {
  initial_rect_ = bounds;
  run_loop_.Quit();
}
#endif

DropMessageFilter::DropMessageFilter(uint32_t message_class,
                                     uint32_t drop_message_id)
    : BrowserMessageFilter(message_class), drop_message_id_(drop_message_id) {}

DropMessageFilter::~DropMessageFilter() = default;

bool DropMessageFilter::OnMessageReceived(const IPC::Message& message) {
  return message.type() == drop_message_id_;
}

ObserveMessageFilter::ObserveMessageFilter(uint32_t message_class,
                                           uint32_t watch_message_id)
    : BrowserMessageFilter(message_class),
      watch_message_id_(watch_message_id) {}

ObserveMessageFilter::~ObserveMessageFilter() = default;

void ObserveMessageFilter::Wait() {
  base::RunLoop loop;
  quit_closure_ = loop.QuitClosure();
  loop.Run();
}

bool ObserveMessageFilter::OnMessageReceived(const IPC::Message& message) {
  if (message.type() == watch_message_id_) {
    // Exit the Wait() method if it's being used, but in a fresh stack once the
    // message is actually handled.
    if (quit_closure_ && !received_) {
      base::ThreadPool::PostTask(
          FROM_HERE, base::BindOnce(&ObserveMessageFilter::QuitWait, this));
    }
    received_ = true;
  }
  return false;
}

void ObserveMessageFilter::QuitWait() {
  std::move(quit_closure_).Run();
}

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

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

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

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

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

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

ResumeCommitClosureSetObserver::ResumeCommitClosureSetObserver(
    base::SafeRef<NavigationHandle> original_navigation,
    mojom::DidCommitProvisionalLoadParamsPtr original_params,
    mojom::DidCommitProvisionalLoadInterfaceParamsPtr original_interface_params)
    : original_navigation_(std::move(original_navigation)),
      original_params_(std::move(original_params)),
      original_interface_params_(std::move(original_interface_params)) {}

ResumeCommitClosureSetObserver::~ResumeCommitClosureSetObserver() {
  NavigationRequest* original_request =
      NavigationRequest::From(&*original_navigation_);
  // Post a task just to avoid any potential weirdness with reentrancy.
  base::SequencedTaskRunner::GetCurrentDefault()->PostNonNestableTask(
      FROM_HERE,
      base::BindOnce(&RenderFrameHostImpl::DidCommitNavigation,
                     base::Unretained(original_request->GetRenderFrameHost()),
                     original_request, std::move(original_params_),
                     std::move(original_interface_params_)));
}

// Installs an observer that waits for `resume_commit_closure_` to be set on
// the next `NavigationRequest` seen by `DidStartNavigation()`.
class BeginNavigationInCommitCallbackInterceptor::ObserverInstaller
    : public WebContentsObserver {
 public:
  explicit ObserverInstaller(
      WebContents* web_contents,
      base::SafeRef<NavigationHandle> original_navigation,
      mojom::DidCommitProvisionalLoadParamsPtr original_params,
      mojom::DidCommitProvisionalLoadInterfaceParamsPtr
          original_interface_params)
      : WebContentsObserver(web_contents),
        original_navigation_(std::move(original_navigation)),
        original_params_(std::move(original_params)),
        original_interface_params_(std::move(original_interface_params)) {}

  // WebContentsObserver overrides:
  void DidStartNavigation(NavigationHandle* handle) override {
    NavigationRequest::From(handle)->set_resume_commit_closure(
        base::BindLambdaForTesting(
            [observer = std::make_unique<ResumeCommitClosureSetObserver>(
                 std::move(original_navigation_), std::move(original_params_),
                 std::move(original_interface_params_))] {}));
  }

  base::SafeRef<NavigationHandle> original_navigation_;
  mojom::DidCommitProvisionalLoadParamsPtr original_params_;
  mojom::DidCommitProvisionalLoadInterfaceParamsPtr original_interface_params_;
};

BeginNavigationInCommitCallbackInterceptor::
    BeginNavigationInCommitCallbackInterceptor(FrameTreeNode* frame_tree_node,
                                               const GURL& url)
    : frame_tree_node_(frame_tree_node), url_(url) {}

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

  absl::optional<ObserverInstaller> observer_installer;
  if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
    // If navigation queueing is enabled, the navigation to `url_` will not
    // commit until the commit suspended by this interceptor completes:
    // instead, it will be queued/suspended and the navigation code will
    // internally set a closure to resume committing the navigation when
    // the suspended `request` completes.
    //
    // `BeginNavigateToURLFromRenderer()` waits for the new navigation from
    // the renderer to start, so arm a hook to install an (indirect) observer
    // that:
    // - waits for the navigation code to install a resume commit closure
    // - resumes the suspended `request` to test that the queued navigation
    //   eventually completes.
    observer_installer.emplace(
        WebContents::FromRenderFrameHost(request->GetRenderFrameHost()),
        request->GetSafeRef(), std::move(*params),
        std::move(*interface_params));
  }

  // At this point, the renderer has already committed the RenderFrame, but
  // on the browser side, the RenderFrameHost is still speculative. Begin
  // another navigation.
  //
  // If navigation queueing is disabled, this should result in the speculative
  // RFH being discarded with a pending commit in flight.
  EXPECT_TRUE(BeginNavigateToURLFromRenderer(frame_tree_node_.get(), url_));

  // Ignore the commit message.
  return false;
}

}  // namespace content