// Copyright 2012 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/public/test/mock_render_process_host.h"

#include <algorithm>
#include <tuple>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/no_destructor.h"
#include "base/notimplemented.h"
#include "base/process/process_handle.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/child_process_host_impl.h"
#include "content/browser/file_system_access/file_system_access_error.h"
#include "content/browser/process_lock.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/render_widget_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/common/renderer.mojom.h"
#include "content/public/browser/android/child_process_importance.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/render_process_host_priority_client.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/site_instance.h"
#include "content/test/fake_network_url_loader_factory.h"
#include "gpu/config/gpu_finch_features.h"
#include "media/media_buildflags.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"

namespace content {

namespace {

StoragePartitionConfig GetOrCreateStoragePartitionConfig(
    BrowserContext* browser_context,
    SiteInstance* site_instance) {
  if (site_instance) {
    SiteInstanceImpl* site_instance_impl =
        static_cast<SiteInstanceImpl*>(site_instance);
    return site_instance_impl->GetSecurityPrincipal()
        .GetStoragePartitionConfig();
  }
  return StoragePartitionConfig::CreateDefault(browser_context);
}

}  // namespace

MockRenderProcessHost::MockRenderProcessHost(BrowserContext* browser_context,
                                             bool is_for_guests_only)
    : MockRenderProcessHost(
          browser_context,
          StoragePartitionConfig::CreateDefault(browser_context),
          is_for_guests_only) {}

MockRenderProcessHost::MockRenderProcessHost(
    BrowserContext* browser_context,
    const StoragePartitionConfig& storage_partition_config,
    bool is_for_guests_only)
    : bad_msg_count_(0),
      id_(ChildProcessHostImpl::GenerateChildProcessUniqueId()),
      has_connection_(false),
      browser_context_(browser_context),
      storage_partition_config_(storage_partition_config),
      prev_routing_id_(0),
      shutdown_requested_(false),
      fast_shutdown_started_(false),
      deletion_callback_called_(false),
      is_for_guests_only_(is_for_guests_only),
      priority_(base::Process::Priority::kUserBlocking),
      is_unused_(true),
      pending_view_count_(0),
      worker_ref_count_(0),
      pending_reuse_ref_count_(0),
      foreground_service_worker_count_(0) {
  // Child process security operations can't be unit tested unless we add
  // ourselves as an existing child process.
  ChildProcessSecurityPolicyImpl::GetInstance()->Add(GetID(), browser_context);

  RenderProcessHostImpl::RegisterHost(GetID(), this);
}

MockRenderProcessHost::~MockRenderProcessHost() {
  ChildProcessSecurityPolicyImpl::GetInstance()->Remove(GetID());
  // In unit tests, Cleanup() might not have been called.
  if (!deletion_callback_called_) {
    for (auto& observer : observers_)
      observer.RenderProcessHostDestroyed(this);
    RenderProcessHostImpl::UnregisterHost(GetID());
  }
  ClearAllUserData();
}

void MockRenderProcessHost::SimulateCrash() {
  SimulateRenderProcessExit(base::TERMINATION_STATUS_PROCESS_CRASHED, 0);
}

void MockRenderProcessHost::SimulateRenderProcessExit(
    base::TerminationStatus status,
    int exit_code) {
  has_connection_ = false;
  ChildProcessTerminationInfo termination_info;
  termination_info.status = status;
  termination_info.exit_code = exit_code;
#if BUILDFLAG(IS_ANDROID)
  termination_info.renderer_has_visible_clients = VisibleClientCount() > 0;
#endif

  within_process_died_observer_ = true;
  for (auto& observer : observers_)
    observer.RenderProcessExited(this, termination_info);
  within_process_died_observer_ = false;

  if (delayed_cleanup_) {
    Cleanup();
  }
}

void MockRenderProcessHost::SimulateReady() {
  is_ready_ = true;
  for (auto& observer : observers_)
    observer.RenderProcessReady(this);
}

bool MockRenderProcessHost::Init() {
  has_connection_ = true;
  return true;
}

void MockRenderProcessHost::EnableSendQueue() {}

int MockRenderProcessHost::GetNextRoutingID() {
  return ++prev_routing_id_;
}

void MockRenderProcessHost::AddRoute(int32_t routing_id,
                                     IPC::Listener* listener) {
  listeners_.AddWithID(listener, routing_id);
}

void MockRenderProcessHost::RemoveRoute(int32_t routing_id) {
  DCHECK(listeners_.Lookup(routing_id) != nullptr);
  listeners_.Remove(routing_id);
  Cleanup();
}

void MockRenderProcessHost::AddObserver(RenderProcessHostObserver* observer) {
  observers_.AddObserver(observer);
}

void MockRenderProcessHost::RemoveObserver(
    RenderProcessHostObserver* observer) {
  observers_.RemoveObserver(observer);
}

void MockRenderProcessHost::ShutdownForBadMessage(
    CrashReportMode crash_report_mode) {
  ++bad_msg_count_;
  shutdown_requested_ = true;
}

void MockRenderProcessHost::UpdateClientPriority(
    RenderProcessHostPriorityClient* client) {}

int MockRenderProcessHost::VisibleClientCount() {
  int count = 0;
  for (RenderProcessHostPriorityClient* client : priority_clients_) {
    const RenderProcessHostPriorityClient::Priority priority =
        client->GetPriority();
    if (!priority.is_hidden) {
      count++;
    }
  }
  return count;
}

unsigned int MockRenderProcessHost::GetFrameDepth() {
  NOTIMPLEMENTED();
  return 0u;
}

bool MockRenderProcessHost::GetIntersectsViewport() {
  NOTIMPLEMENTED();
  return true;
}

bool MockRenderProcessHost::IsForTopChromeWebUI() const {
  return is_for_top_chrome_web_ui_;
}

bool MockRenderProcessHost::ShouldSendGpuChannelEarly() const {
  return base::FeatureList::IsEnabled(features::kSendGPUChannelEarly) &&
         (!features::kSendGPUChannelEarlyTopChromeOnly.Get() ||
          IsForTopChromeWebUI());
}

bool MockRenderProcessHost::IsForGuestsOnly() {
  return is_for_guests_only_;
}

bool MockRenderProcessHost::IsPrivileged() {
  return GetProcessLock().is_privileged();
}

bool MockRenderProcessHost::IsJitDisabled() {
  return false;
}

bool MockRenderProcessHost::AreV8OptimizationsDisabled() {
  return are_v8_optimizations_disabled_;
}

void MockRenderProcessHost::SetAreV8OptimizationsDisabled(bool disabled) {
  are_v8_optimizations_disabled_ = disabled;
}

bool MockRenderProcessHost::DisallowV8FeatureFlagOverrides() {
  return false;
}

bool MockRenderProcessHost::IsPdf() {
  return is_pdf_;
}

void MockRenderProcessHost::SetIsPdf(bool is_pdf) {
  is_pdf_ = is_pdf;
}

void MockRenderProcessHost::OnMediaStreamAdded() {}

void MockRenderProcessHost::OnMediaStreamRemoved() {}

void MockRenderProcessHost::OnForegroundServiceWorkerAdded() {
  foreground_service_worker_count_ += 1;
}

void MockRenderProcessHost::OnForegroundServiceWorkerRemoved() {
  DCHECK_GT(foreground_service_worker_count_, 0);
  foreground_service_worker_count_ -= 1;
}

void MockRenderProcessHost::OnBoostForLoadingAdded() {}

void MockRenderProcessHost::OnBoostForLoadingRemoved() {}

void MockRenderProcessHost::OnImmersiveXrSessionStarted() {
  has_immersive_xr_session_ = true;
}

void MockRenderProcessHost::OnImmersiveXrSessionStopped() {
  has_immersive_xr_session_ = false;
}

bool MockRenderProcessHost::HasImmersiveXrSessionForTesting() const {
  return has_immersive_xr_session_;
}

StoragePartition* MockRenderProcessHost::GetStoragePartition() {
  return browser_context_->GetStoragePartition(storage_partition_config_);
}

void MockRenderProcessHost::AddWord(const std::u16string& word) {}

bool MockRenderProcessHost::Shutdown(int exit_code) {
  shutdown_requested_ = true;
  return true;
}

bool MockRenderProcessHost::ShutdownRequested() {
  return shutdown_requested_;
}

bool MockRenderProcessHost::FastShutdownIfPossible(
    size_t page_count,
    bool skip_unload_handlers,
    bool ignore_workers,
    bool ignore_keep_alive,
    bool ignore_pending_reuse,
    bool use_outermost_main_frame_check) {
  if (GetActiveViewCount() != page_count)
    return false;
  // We aren't actually going to do anything, but set |fast_shutdown_started_|
  // to true so that tests know we've been called.
  fast_shutdown_started_ = true;
  return true;
}

bool MockRenderProcessHost::FastShutdownStarted() {
  return fast_shutdown_started_;
}

const base::Process& MockRenderProcessHost::GetProcess() {
  if (process_still_launching_) {
    // Matches RenderProcessHostImpl::GetProcess() while the launcher is still
    // starting: a sentinel for "no process".
    static const base::NoDestructor<base::Process> null_process;
    return *null_process;
  }

  static const base::Process current_process(base::Process::Current());
  return current_process;
}

bool MockRenderProcessHost::IsReady() {
  return is_ready_;
}

ChildProcessId MockRenderProcessHost::GetID() const {
  return id_;
}

int MockRenderProcessHost::GetDeprecatedID() const {
  return id_.GetUnsafeValue();
}

base::SafeRef<RenderProcessHost> MockRenderProcessHost::GetSafeRef() const {
  return weak_ptr_factory_.GetSafeRef();
}

bool MockRenderProcessHost::IsInitializedAndNotDead() {
  return has_connection_;
}

bool MockRenderProcessHost::IsDeletingSoon() {
  return deletion_callback_called_;
}

void MockRenderProcessHost::SetBlocked(bool blocked) {}

bool MockRenderProcessHost::IsBlocked() {
  return false;
}

base::CallbackListSubscription
MockRenderProcessHost::RegisterBlockStateChangedCallback(
    const BlockStateChangedCallback& cb) {
  return {};
}

void MockRenderProcessHost::Cleanup() {
  if (within_process_died_observer_) {
    delayed_cleanup_ = true;
    return;
  }
  delayed_cleanup_ = false;

  if (pending_reuse_ref_count_ > 0) {
    return;
  }

  if (listeners_.IsEmpty() && !deletion_callback_called_ &&
      !pending_view_count_) {
    if (IsInitializedAndNotDead()) {
      ChildProcessTerminationInfo termination_info;
      termination_info.status = base::TERMINATION_STATUS_NORMAL_TERMINATION;
      termination_info.exit_code = 0;
#if BUILDFLAG(IS_ANDROID)
      termination_info.renderer_has_visible_clients = VisibleClientCount() > 0;
#endif
      for (auto& observer : observers_)
        observer.RenderProcessExited(this, termination_info);
    }

    for (auto& observer : observers_)
      observer.RenderProcessHostDestroyed(this);
    RenderProcessHostImpl::UnregisterHost(GetID());
    has_connection_ = false;
    deletion_callback_called_ = true;
  }
}

void MockRenderProcessHost::AddPendingView() {
  ++pending_view_count_;
}

void MockRenderProcessHost::RemovePendingView() {
  CHECK(pending_view_count_);
  --pending_view_count_;
  Cleanup();
}

void MockRenderProcessHost::AddPriorityClient(
    RenderProcessHostPriorityClient* priority_client) {
  priority_clients_.insert(priority_client);
}

void MockRenderProcessHost::RemovePriorityClient(
    RenderProcessHostPriorityClient* priority_client) {
  priority_clients_.erase(priority_client);
}

void MockRenderProcessHost::SetPriorityOverride(
    base::Process::Priority priority) {}

bool MockRenderProcessHost::HasPriorityOverride() {
  return false;
}

void MockRenderProcessHost::ClearPriorityOverride() {}

#if BUILDFLAG(IS_ANDROID)
void MockRenderProcessHost::GraduateSpareToNormalRendererPriority() {}

bool MockRenderProcessHost::
    ShouldThrottleNavigationForSpareRendererGraduation() {
  return false;
}

ChildProcessImportance MockRenderProcessHost::GetEffectiveImportance() {
  NOTIMPLEMENTED();
  return ChildProcessImportance::NORMAL;
}

base::android::ChildBindingState
MockRenderProcessHost::GetEffectiveChildBindingState() {
  NOTIMPLEMENTED();
  return base::android::ChildBindingState::UNBOUND;
}

void MockRenderProcessHost::DumpProcessStack() {}
#endif

void MockRenderProcessHost::SetSuddenTerminationAllowed(bool allowed) {}

BrowserContext* MockRenderProcessHost::GetBrowserContext() {
  return browser_context_;
}

bool MockRenderProcessHost::InSameStoragePartition(
    StoragePartition* partition) {
  return GetStoragePartition() == partition;
}

IPC::ChannelProxy* MockRenderProcessHost::GetChannel() {
  return nullptr;
}

base::TimeDelta MockRenderProcessHost::GetChildProcessIdleTime() {
  return base::Milliseconds(0);
}

void MockRenderProcessHost::BindReceiver(
    mojo::GenericPendingReceiver receiver) {
  auto it = binder_overrides_.find(*receiver.interface_name());
  if (it != binder_overrides_.end())
    it->second.Run(receiver.PassPipe());
}

std::unique_ptr<base::PersistentMemoryAllocator>
MockRenderProcessHost::TakeMetricsAllocator() {
  return nullptr;
}

const base::TimeTicks& MockRenderProcessHost::GetLastInitTime() {
  static base::TimeTicks dummy_time = base::TimeTicks::Now();
  return dummy_time;
}

base::TimeTicks MockRenderProcessHost::GetProcessLaunchedTime() const {
  return process_launched_time_;
}

base::Process::Priority MockRenderProcessHost::GetPriority() const {
  return priority_;
}

std::string MockRenderProcessHost::GetKeepAliveDurations() const {
  return std::string("MockRenderProcessHost: durations not tracked.");
}

size_t MockRenderProcessHost::GetShutdownDelayRefCount() const {
  return 0;
}

int MockRenderProcessHost::GetRenderFrameHostCount() const {
  return render_frame_host_id_set_.size();
}

void MockRenderProcessHost::RegisterRenderFrameHost(
    const GlobalRenderFrameHostId& render_frame_host_id,
    bool is_outermost_main_frame) {
  render_frame_host_id_set_.insert(render_frame_host_id);
}

void MockRenderProcessHost::UnregisterRenderFrameHost(
    const GlobalRenderFrameHostId& render_frame_host_id,
    bool is_outermost_main_frame) {
  render_frame_host_id_set_.erase(render_frame_host_id);
}

void MockRenderProcessHost::ForEachRenderFrameHost(
    base::FunctionRef<void(RenderFrameHost*)> on_render_frame_host) {
  // TODO(crbug.com/40487508): Clean up MockRenderProcessHost usage and merge
  // this implementation with RenderProcessHostImpl::ForEachRenderFrameHost().
  for (auto rfh_id : render_frame_host_id_set_) {
    RenderFrameHostImpl* rfh = RenderFrameHostImpl::FromID(rfh_id);
    // Note that some RenderFrameHosts in the set may not be found by FromID if
    // we get here during their destructor (e.g., while deleting their subframe
    // RenderFrameHosts).
    if (!rfh) {
      continue;
    }

    // Speculative RFHs are not exposed to //content embedders, so we have to
    // explicitly check them here to avoid leaks.
    if (rfh->lifecycle_state() ==
        RenderFrameHostImpl::LifecycleStateImpl::kSpeculative) {
      continue;
    }
    on_render_frame_host(rfh);
  }
}

void MockRenderProcessHost::IncrementWorkerRefCount() {
  ++worker_ref_count_;
}

void MockRenderProcessHost::DecrementWorkerRefCount() {
  DCHECK_GT(worker_ref_count_, 0);
  --worker_ref_count_;
}

void MockRenderProcessHost::IncrementPendingReuseRefCount() {
  ++pending_reuse_ref_count_;
}

void MockRenderProcessHost::DecrementPendingReuseRefCount() {
  DCHECK_GT(pending_reuse_ref_count_, 0);
  --pending_reuse_ref_count_;
}

int MockRenderProcessHost::GetPendingReuseRefCountForTesting() const {
  return pending_reuse_ref_count_;
}

size_t MockRenderProcessHost::GetWorkerRefCount() const {
  return worker_ref_count_;
}

void MockRenderProcessHost::DisableRefCounts() {
  worker_ref_count_ = 0;
  pending_reuse_ref_count_ = 0;

  // RenderProcessHost::DisableRefCounts() virtual method gets called as part of
  // BrowserContext::NotifyWillBeDestroyed(...).  Normally
  // MockRenderProcessHost::DisableRefCounts() doesn't call Cleanup, because the
  // MockRenderProcessHost might be owned by a test.  However, when the
  // MockRenderProcessHost is the spare RenderProcessHost, we know that it is
  // owned by the SpareRenderProcessHostManager and we need to delete the spare
  // to avoid reports/DCHECKs about memory leaks.
  if (IsSpare()) {
    Cleanup();
  }
}

bool MockRenderProcessHost::AreRefCountsDisabled() {
  return false;
}

mojom::Renderer* MockRenderProcessHost::GetRendererInterface() {
  if (!renderer_interface_) {
    renderer_interface_ =
        std::make_unique<mojo::AssociatedRemote<mojom::Renderer>>();
    std::ignore =
        renderer_interface_->BindNewEndpointAndPassDedicatedReceiver();
  }
  return renderer_interface_->get();
}

bool MockRenderProcessHost::MayReuseHost() {
  return true;
}

bool MockRenderProcessHost::IsUnused() {
  return is_unused_;
}

void MockRenderProcessHost::SetIsUsed() {
  is_unused_ = false;
}

bool MockRenderProcessHost::HostHasNotBeenUsed() {
  return IsUnused() && listeners_.IsEmpty();
}

bool MockRenderProcessHost::IsSpare() const {
  return std::ranges::contains(
      SpareRenderProcessHostManagerImpl::Get().GetSpares(), this);
}

void MockRenderProcessHost::SetProcessLock(
    const IsolationContext& isolation_context,
    const ProcessLock& process_lock) {
  ChildProcessSecurityPolicyImpl::GetInstance()->LockProcess(
      isolation_context, GetID(), !IsUnused(), process_lock);
  if (process_lock.IsASiteOrOrigin())
    is_renderer_locked_to_site_ = true;
}

ProcessLock MockRenderProcessHost::GetProcessLock() const {
  return ChildProcessSecurityPolicyImpl::GetInstance()->GetProcessLock(GetID());
}

bool MockRenderProcessHost::IsProcessLockedToSiteForTesting() {
  return GetProcessLock().IsLockedToSite();
}

void MockRenderProcessHost::BindCacheStorage(
    const network::CrossOriginEmbedderPolicy&,
    mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>,
    const network::DocumentIsolationPolicy&,
    mojo::PendingRemote<network::mojom::DocumentIsolationPolicyReporter>,
    const storage::BucketLocator& bucket_locator,
    mojo::PendingReceiver<blink::mojom::CacheStorage> receiver) {
  cache_storage_receiver_ = std::move(receiver);
}

void MockRenderProcessHost::BindIndexedDB(
    const blink::StorageKey& storage_key,
    BucketContext& bucket_context,
    mojo::PendingReceiver<blink::mojom::IDBFactory> receiver) {
  idb_factory_receiver_ = std::move(receiver);
}

void MockRenderProcessHost::GetSandboxedFileSystemForBucket(
    const storage::BucketLocator& bucket,
    const std::vector<std::string>& directory_path_components,
    blink::mojom::FileSystemAccessManager::GetSandboxedFileSystemCallback
        callback) {
  std::move(callback).Run(file_system_access_error::Ok(), {});
}

std::string
MockRenderProcessHost::GetInfoForBrowserContextDestructionCrashReporting() {
  return std::string();
}

void MockRenderProcessHost::WriteIntoTrace(
    perfetto::TracedProto<TraceProto> proto) const {
  proto->set_id(GetID().value());
}

#if BUILDFLAG(IS_CHROMEOS)
void MockRenderProcessHost::ReinitializeLogging(
    uint32_t logging_dest,
    base::ScopedFD log_file_descriptor) {
  NOTIMPLEMENTED();
}
#endif  // BUILDFLAG(IS_CHROMEOS)

uint64_t MockRenderProcessHost::GetPrivateMemoryFootprint() {
  return 0;
}

RenderProcessHost::FilterURLResult MockRenderProcessHost::FilterURL(
    bool empty_allowed,
    GURL* url) {
  return RenderProcessHostImpl::FilterURL(this, empty_allowed, url);
}

void MockRenderProcessHost::EnableAudioDebugRecordings(
    const base::FilePath& file) {}

void MockRenderProcessHost::DisableAudioDebugRecordings() {}

RenderProcessHost::WebRtcStopRtpDumpCallback
MockRenderProcessHost::StartRtpDump(bool incoming,
                                    bool outgoing,
                                    WebRtcRtpPacketCallback packet_callback) {
  return base::NullCallback();
}

void MockRenderProcessHost::OnChannelConnected(int32_t peer_pid) {}

void MockRenderProcessHost::OverrideBinderForTesting(
    const std::string& interface_name,
    const InterfaceBinder& binder) {
  binder_overrides_[interface_name] = binder;
}

void MockRenderProcessHost::OverrideRendererInterfaceForTesting(
    std::unique_ptr<mojo::AssociatedRemote<mojom::Renderer>>
        renderer_interface) {
  renderer_interface_ = std::move(renderer_interface);
}

MockRenderProcessHostFactory::MockRenderProcessHostFactory() = default;

MockRenderProcessHostFactory::~MockRenderProcessHostFactory() = default;

RenderProcessHost* MockRenderProcessHostFactory::CreateRenderProcessHost(
    BrowserContext* browser_context,
    SiteInstance* site_instance) {
  processes_.push_back(BuildRenderProcessHost(browser_context, site_instance));
  return processes_.back().get();
}

void MockRenderProcessHostFactory::Remove(MockRenderProcessHost* host) const {
  for (auto it = processes_.begin(); it != processes_.end(); ++it) {
    if (it->get() == host) {
      processes_.erase(it);
      break;
    }
  }
}

std::unique_ptr<MockRenderProcessHost>
MockRenderProcessHostFactory::BuildRenderProcessHost(
    BrowserContext* browser_context,
    SiteInstance* site_instance) {
  const bool is_for_guests_only =
      site_instance && site_instance->GetSecurityPrincipal().IsGuest();
  StoragePartitionConfig storage_partition_config =
      GetOrCreateStoragePartitionConfig(browser_context, site_instance);
  return std::make_unique<MockRenderProcessHost>(
      browser_context, storage_partition_config, is_for_guests_only);
}

base::ScopedClosureRunner MockRenderProcessHost::DelayProcessShutdown(
    const base::TimeDelta& subframe_shutdown_timeout,
    const base::TimeDelta& unload_handler_timeout,
    const SiteInfo& site_info) {
  return base::ScopedClosureRunner();
}

void MockRenderProcessHost::StopTrackingProcessForShutdownDelay() {}

bool MockRenderProcessHost::IsOnlyHostingPrerenderedFramesOrEmpty() {
  return false;
}

}  // namespace content
