// Copyright 2020 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/renderer/agent_scheduling_group.h"

#include <utility>

#include "arkweb/chromium_ext/base/process/process_handle_posix_ex.h"
#include "base/containers/map_util.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/sequence_bound.h"
#include "base/timer/elapsed_timer.h"
#include "base/types/pass_key.h"
#include "content/common/agent_scheduling_group.mojom.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/renderer/content_renderer_client.h"
#include "content/renderer/render_frame_impl.h"
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/renderer_navigation_metrics_manager.h"
#include "ipc/ipc_channel_factory.h"
#include "ipc/ipc_listener.h"
#include "ipc/ipc_sync_channel.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
#include "third_party/blink/public/mojom/page/page.mojom.h"
#include "third_party/blink/public/mojom/page/prerender_page_param.mojom.h"
#include "third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom.h"
#include "third_party/blink/public/mojom/worker/worklet_global_scope_creation_params.mojom.h"
#include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h"
#include "third_party/blink/public/web/web_remote_frame.h"
#include "third_party/blink/public/web/web_shared_storage_worklet_thread.h"
#include "third_party/blink/public/web/web_view.h"
#include "third_party/blink/public/web/web_view_client.h"

namespace content {

using ::IPC::ChannelFactory;
using ::IPC::Listener;
using ::IPC::SyncChannel;
using ::mojo::AssociatedReceiver;
using ::mojo::AssociatedRemote;
using ::mojo::PendingAssociatedReceiver;
using ::mojo::PendingAssociatedRemote;
using ::mojo::PendingReceiver;
using ::mojo::PendingRemote;
using ::mojo::Receiver;
using ::mojo::Remote;

using PassKey = ::base::PassKey<AgentSchedulingGroup>;

namespace {

RenderThreadImpl& ToImpl(RenderThread& render_thread) {
  DCHECK(RenderThreadImpl::current());
  return static_cast<RenderThreadImpl&>(render_thread);
}

static features::MBIMode GetMBIMode() {
  return base::FeatureList::IsEnabled(features::kMBIMode)
             ? features::kMBIModeParam.Get()
             : features::MBIMode::kLegacy;
}

// Creates a main WebRemoteFrame for `web_view`.
void CreateRemoteMainFrame(
    const blink::RemoteFrameToken& frame_token,
    mojo::PendingAssociatedRemote<blink::mojom::RemoteFrameHost>
        remote_frame_host,
    mojo::PendingAssociatedReceiver<blink::mojom::RemoteFrame>
        remote_frame_receiver,
    mojo::PendingAssociatedRemote<blink::mojom::RemoteMainFrameHost>
        remote_main_frame_host,
    mojo::PendingAssociatedReceiver<blink::mojom::RemoteMainFrame>
        remote_main_frame_receiver,
    base::UnguessableToken& devtools_main_frame_token,
    blink::mojom::FrameReplicationStatePtr replication_state,
    blink::WebFrame* opener_frame,
    blink::WebView* web_view) {
  blink::WebRemoteFrame::CreateMainFrame(
      web_view, frame_token, /*is_loading=*/false, devtools_main_frame_token,
      opener_frame, std::move(remote_frame_host),
      std::move(remote_frame_receiver), std::move(replication_state));
  // Root frame proxy has no ancestors to point to their RenderWidget.

  // The WebRemoteFrame created here was already attached to the Page as its
  // main frame, so we can call WebView's DidAttachRemoteMainFrame().
  web_view->DidAttachRemoteMainFrame(std::move(remote_main_frame_host),
                                     std::move(remote_main_frame_receiver));
}

// Blink inappropriately makes decisions if there is a WebViewClient set,
// so currently we need to always create a WebViewClient.
class SelfOwnedWebViewClient : public blink::WebViewClient {
 public:
  void OnDestruct() override { delete this; }
};

}  // namespace

AgentSchedulingGroup::ReceiverData::ReceiverData(
    const std::string& name,
    mojo::PendingAssociatedReceiver<blink::mojom::AssociatedInterface> receiver)
    : name(std::move(name)), receiver(std::move(receiver)) {}

AgentSchedulingGroup::ReceiverData::ReceiverData(ReceiverData&& other)
    : name(std::move(other.name)), receiver(std::move(other.receiver)) {}

AgentSchedulingGroup::ReceiverData::~ReceiverData() = default;

// AgentSchedulingGroup:
AgentSchedulingGroup::AgentSchedulingGroup(
    RenderThread& render_thread,
    mojo::PendingReceiver<IPC::mojom::ChannelBootstrap> bootstrap)
    : agent_group_scheduler_(
          blink::scheduler::WebThreadScheduler::MainThreadScheduler()
              .CreateWebAgentGroupScheduler()),
      render_thread_(render_thread),
      // `receiver_` will be bound by `OnAssociatedInterfaceRequest()`.
      receiver_(this) {
  DCHECK(agent_group_scheduler_);
  DCHECK_NE(GetMBIMode(), features::MBIMode::kLegacy);

  channel_ = SyncChannel::Create(
      /*listener=*/this, /*ipc_task_runner=*/render_thread_->GetIOTaskRunner(),
      /*listener_task_runner=*/agent_group_scheduler_->DefaultTaskRunner(),
      render_thread_->GetShutdownEvent());

  channel_->SetUrgentMessageObserver(agent_group_scheduler_.get());

  // TODO(crbug.com/40142495): Add necessary filters.
  // Currently, the renderer process has these filters:
  // 1. `UnfreezableMessageFilter` - in the process of being removed,
  // 2. `AutomationMessageFilter` - needs to be handled somehow.

  channel_->Init(
      ChannelFactory::CreateClientFactory(
          bootstrap.PassPipe(),
          /*ipc_task_runner=*/render_thread_->GetIOTaskRunner(),
          /*proxy_task_runner=*/agent_group_scheduler_->DefaultTaskRunner()),
      /*create_pipe_now=*/true);
}

AgentSchedulingGroup::AgentSchedulingGroup(
    RenderThread& render_thread,
    PendingAssociatedReceiver<mojom::AgentSchedulingGroup> receiver)
    : agent_group_scheduler_(
          blink::scheduler::WebThreadScheduler::MainThreadScheduler()
              .CreateWebAgentGroupScheduler()),
      render_thread_(render_thread),
      receiver_(this,
                std::move(receiver),
                agent_group_scheduler_->DefaultTaskRunner()) {
  DCHECK(agent_group_scheduler_);
  DCHECK_EQ(GetMBIMode(), features::MBIMode::kLegacy);
}

AgentSchedulingGroup::~AgentSchedulingGroup() = default;

void AgentSchedulingGroup::OnBadMessageReceived() {
  // Not strictly required, since we don't currently do anything with bad
  // messages in the renderer, but if we ever do then this will "just work".
  return ToImpl(*render_thread_).OnBadMessageReceived();
}

void AgentSchedulingGroup::OnAssociatedInterfaceRequest(
    const std::string& interface_name,
    mojo::ScopedInterfaceEndpointHandle handle) {
  // The ASG's channel should only be used to bootstrap the ASG mojo interface.
  DCHECK_EQ(interface_name, mojom::AgentSchedulingGroup::Name_);
  DCHECK(!receiver_.is_bound());

  PendingAssociatedReceiver<mojom::AgentSchedulingGroup> pending_receiver(
      std::move(handle));
  receiver_.Bind(std::move(pending_receiver),
                 agent_group_scheduler_->DefaultTaskRunner());
}

void AgentSchedulingGroup::AddFrameRoute(
    const blink::LocalFrameToken& frame_token,
    RenderFrameImpl* render_frame,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
  DCHECK(!base::Contains(listener_map_, frame_token));
  listener_map_.insert({frame_token, render_frame});

  // See warning in `GetAssociatedInterface`.
  // Replay any `GetAssociatedInterface` calls for this route.
  auto range = pending_receivers_.equal_range(frame_token);
  for (auto iter = range.first; iter != range.second; ++iter) {
    ReceiverData& data = iter->second;
    render_frame->OnAssociatedInterfaceRequest(data.name,
                                               data.receiver.PassHandle());
  }
  pending_receivers_.erase(range.first, range.second);
}

void AgentSchedulingGroup::RemoveFrameRoute(
    const blink::LocalFrameToken& frame_token) {
  DCHECK(base::Contains(listener_map_, frame_token));
  listener_map_.erase(frame_token);
}

void AgentSchedulingGroup::DidUnloadRenderFrame(
    const blink::LocalFrameToken& frame_token) {
  host_remote_->DidUnloadRenderFrame(frame_token);
}

void AgentSchedulingGroup::CreateView(mojom::CreateViewParamsPtr params) {
  LOG(DEBUG) << "AgentSchedulingGroup::CreateView";
#if BUILDFLAG(ARKWEB_RENDER_PROCESS_MODE)
  host_remote_->ReportCreateView(base::GetCurrentRealPid());
#endif
  base::ElapsedTimer timer;
  RenderThreadImpl& renderer = ToImpl(*render_thread_);
  renderer.SetScrollAnimatorEnabled(
      params->web_preferences.enable_scroll_animator, PassKey());
#if BUILDFLAG(ARKWEB_SYNC_RENDER)
  renderer.SetDrawMode(
      params->web_preferences.draw_mode, PassKey());
#endif

  const auto navigation_metrics_token = params->navigation_metrics_token;
  CreateWebView(std::move(params),
                /*was_created_by_renderer=*/false,
                /*base_url=*/blink::WebURL());

  RendererNavigationMetricsManager::Instance().AddCreateViewEvent(
      navigation_metrics_token, timer.start_time(), timer.Elapsed());
  // Add any new code above the AddCreateViewEvent call.
}

blink::WebView* AgentSchedulingGroup::CreateWebView(
    mojom::CreateViewParamsPtr params,
    bool was_created_by_renderer,
    const blink::WebURL& base_url) {
  TRACE_EVENT0("navigation", "AgentSchedulingGroup::CreateWebView");
  base::ScopedUmaHistogramTimer histogram_timer(
      "Navigation.AgentSchedulingGroup.CreateWebView");
  DCHECK(RenderThread::IsMainThread());

  blink::WebFrame* opener_frame = nullptr;
  if (params->opener_frame_token)
    opener_frame =
        blink::WebFrame::FromFrameToken(params->opener_frame_token.value());

  blink::WebView* web_view = blink::WebView::Create(
      new SelfOwnedWebViewClient(), params->hidden,
      std::move(params->prerender_param),
      params->type == mojom::ViewWidgetType::kFencedFrame
          ? std::make_optional(params->fenced_frame_mode)
          : std::nullopt,
      /*compositing_enabled=*/true, params->never_composited,
      opener_frame ? opener_frame->View() : nullptr,
      std::move(params->blink_page_broadcast), agent_group_scheduler(),
      params->session_storage_namespace_id, params->base_background_color,
      params->browsing_context_group_token, &params->color_provider_colors,
      params->history_index, params->history_length);

  web_view->SetRendererPreferences(params->renderer_preferences);
  web_view->SetWebPreferences(params->web_preferences);
  web_view->SetPageAttributionSupport(params->attribution_support);

  const bool is_for_nested_main_frame =
      params->type != mojom::ViewWidgetType::kTopLevel;

  switch (params->main_frame->which()) {
    case mojom::CreateMainFrameUnion::Tag::kRemoteParams: {
      auto& remote_params = params->main_frame->get_remote_params();
      CreateRemoteMainFrame(
          remote_params->token,
          std::move(remote_params->frame_interfaces->frame_host),
          std::move(remote_params->frame_interfaces->frame_receiver),
          std::move(remote_params->main_frame_interfaces->main_frame_host),
          std::move(remote_params->main_frame_interfaces->main_frame),
          params->devtools_main_frame_token,
          std::move(params->replication_state), opener_frame, web_view);
      break;
    }
    case mojom::CreateMainFrameUnion::Tag::kLocalParams: {
      RenderFrameImpl::CreateMainFrame(
          *this, web_view, opener_frame, is_for_nested_main_frame,
          /*is_for_scalable_page=*/params->type !=
              mojom::ViewWidgetType::kFencedFrame,
          std::move(params->replication_state),
          params->devtools_main_frame_token,
          std::move(params->main_frame->get_local_params()),
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
          params->is_offscreen,
#endif
          base_url);
      break;
    }
    case mojom::CreateMainFrameUnion::Tag::kProvisionalLocalParams: {
      // Create a provisional local main frame and a placeholder RemoteFrame as
      // the main frame for the new WebView. This is used in two instances:
      //
      // 1. A main frame navigation with RenderDocument that commits in the same
      //    renderer process as the current RenderFrame, which will result in a
      //    local -> local frame swap if the navigation commits. Note that the
      //    current RenderFrame and the new RenderFrame are in separate
      //    blink::WebViews, so a local -> local main frame swap must also
      //    handle swapping the state on the blink::WebView/blink::Page.
      //
      //  2. A main frame navigation in a prerendered page. Unlike local ->
      //     local frame swaps for RenderDocument, there is never a current
      //     RenderFrame, so there is no additional complexity to swap the state
      //     on the blink::WebView/blink::Page.
      //
      // Note: a potential remote -> local swap does not go through this path at
      // all; instead, the browser creates a new view with a RemoteFrame as the
      // main frame, and then creates a provisional main LocalFrame with a
      // second IPC.
      //
      // Additional background for local -> local main frame swap:
      // The placeholder RemoteFrame is needed because:
      // - a blink::WebView/blink::Page must have a main frame
      // - but the provisional LocalFrame must not be set as the main frame of
      //   the page yet, as it is still provisional
      //
      // Unlike a potential remote -> local swap, the main RemoteFrame does not
      // have a corresponding RenderFrameProxyHost on the browser side. This is
      // because the potential navigation is within the same SiteInstanceGroup,
      // and a single frame tree node should not have both a RenderFrameHost and
      // a RenderFrameProxyHost.
      //
      // If the potential remote -> local swap is cancelled, the provisional
      // LocalFrame is deleted first, with a separate IPC to delete the
      // blink::WebView (and its corresponding placeholder RemoteFrame).
      //
      // Finally, if the remote -> local swap commits, the frame swapping logic
      // has additional logic to swap a similar placeholder RemoteFrame in the
      // previous blink::WebView to unload the previous RenderFrame.
      //
      // Note: we create the placeholder RemoteFrame with no opener, even if we
      // have an opener_frame. This ensures that the placeholder RemoteFrame is
      // not retained beyond the navigation by the opener's OpenedFrameTracker.

      // Create the placeholder RemoteFrame.
      blink::RemoteFrameToken remote_frame_token;
      CreateRemoteMainFrame(
          remote_frame_token, mojo::NullAssociatedRemote(),
          mojo::NullAssociatedReceiver(), mojo::NullAssociatedRemote(),
          mojo::NullAssociatedReceiver(), params->devtools_main_frame_token,
          params->replication_state.Clone(), nullptr, web_view);

      auto& provisional_local_params =
          params->main_frame->get_provisional_local_params();
      auto& local_params = provisional_local_params->local_params;

      // Create the provisional main LocalFrame.
      // TODO(dcheng): RenderFrameImpl::CreateFrame() should probably be split
      // for clarity, but this is left as an exercise for another day.
      RenderFrameImpl::CreateFrame(
          *this, local_params->frame_token, local_params->routing_id,
          std::move(local_params->frame),
          std::move(local_params->interface_broker),
          std::move(local_params->associated_interface_provider_remote),
          provisional_local_params->previous_frame_token ? web_view : nullptr,
          provisional_local_params->previous_frame_token
              ? provisional_local_params->previous_frame_token
              : remote_frame_token,
          params->opener_frame_token,
          /*parent_frame_token=*/std::nullopt,
          /*previous_sibling_frame_token=*/std::nullopt,
          params->devtools_main_frame_token, params->navigation_metrics_token,
          blink::mojom::TreeScopeType::kDocument,
          std::move(params->replication_state),
          std::move(local_params->widget_params),
          /*frame_owner_properties=*/nullptr,
          local_params->is_on_initial_empty_document,
          local_params->document_token,
          std::move(local_params->policy_container), is_for_nested_main_frame);
      break;
    }
  }

  // TODO(davidben): Move this state from Blink into content.
  if (params->window_was_opened_by_another_window)
    web_view->SetOpenedByDOM();

  GetContentClient()->renderer()->WebViewCreated(
      web_view, was_created_by_renderer,
      params->outermost_origin ? &params->outermost_origin.value() : nullptr);
  return web_view;
}

void AgentSchedulingGroup::CreateFrame(mojom::CreateFrameParamsPtr params) {
  TRACE_EVENT0("navigation", "AgentSchedulingGroup::CreateFrame");
  base::ScopedUmaHistogramTimer histogram_timer(
      "Navigation.AgentSchedulingGroup.CreateFrame");
  RenderFrameImpl::CreateFrame(
      *this, params->frame_token, params->routing_id, std::move(params->frame),
      std::move(params->interface_broker),
      std::move(params->associated_interface_provider_remote),
      /*web_view=*/nullptr, params->previous_frame_token,
      params->opener_frame_token, params->parent_frame_token,
      params->previous_sibling_frame_token, params->devtools_frame_token,
      params->navigation_metrics_token, params->tree_scope_type,
      std::move(params->replication_state), std::move(params->widget_params),
      std::move(params->frame_owner_properties),
      params->is_on_initial_empty_document, params->document_token,
      std::move(params->policy_container), params->is_for_nested_main_frame);
}

void AgentSchedulingGroup::CreateSharedStorageWorkletService(
    mojo::PendingReceiver<blink::mojom::SharedStorageWorkletService> receiver,
    blink::mojom::WorkletGlobalScopeCreationParamsPtr
        global_scope_creation_params) {
  blink::WebSharedStorageWorkletThread::Start(
      agent_group_scheduler_->DefaultTaskRunner(), std::move(receiver),
      std::move(global_scope_creation_params));
}

void AgentSchedulingGroup::BindAssociatedInterfaces(
    mojo::PendingAssociatedRemote<mojom::AgentSchedulingGroupHost> remote_host,
    mojo::PendingAssociatedReceiver<mojom::RouteProvider>
        route_provider_receiever) {
  host_remote_.Bind(std::move(remote_host),
                    agent_group_scheduler_->DefaultTaskRunner());
  route_provider_receiver_.Bind(std::move(route_provider_receiever),
                                agent_group_scheduler_->DefaultTaskRunner());
}

void AgentSchedulingGroup::GetRoute(
    const blink::LocalFrameToken& frame_token,
    mojo::PendingAssociatedReceiver<blink::mojom::AssociatedInterfaceProvider>
        receiver) {
  DCHECK(receiver.is_valid());
  associated_interface_provider_receivers_.Add(
      this, std::move(receiver), frame_token,
      agent_group_scheduler_->DefaultTaskRunner());
}

void AgentSchedulingGroup::GetAssociatedInterface(
    const std::string& name,
    mojo::PendingAssociatedReceiver<blink::mojom::AssociatedInterface>
        receiver) {
  const auto& frame_token =
      associated_interface_provider_receivers_.current_context();

  if (auto* listener = GetListener(frame_token)) {
    listener->OnAssociatedInterfaceRequest(name, receiver.PassHandle());
  } else {
    // THIS IS UNSAFE!
    // Associated receivers must be bound immediately or they could drop
    // messages. This is needed short term so the browser side Remote isn't
    // broken even after the corresponding `AddRoute` happens. Browser should
    // avoid calling this before the corresponding `AddRoute`, but this is a
    // short term workaround until that happens.
    pending_receivers_.emplace(frame_token,
                               ReceiverData(name, std::move(receiver)));
  }
}

RenderFrameImpl* AgentSchedulingGroup::GetListener(
    const blink::LocalFrameToken& frame_token) {
  return base::FindPtrOrNull(listener_map_, frame_token);
}

}  // namespace content