chromium/content/browser/android/synchronous_compositor_host.cc

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

#include "content/browser/android/synchronous_compositor_host.h"

#include <algorithm>
#include <atomic>
#include <cmath>
#include <memory>
#include <utility>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/writable_shared_memory_region.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/traced_value.h"
#include "content/browser/android/synchronous_compositor_sync_call_bridge.h"
#include "content/browser/bad_message.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/renderer_host/compositor_dependencies_android.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/android/sync_compositor_statics.h"
#include "content/common/features.h"
#include "content/public/browser/android/synchronous_compositor_client.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/common/content_switches.h"
#include "gpu/ipc/client/gpu_channel_host.h"
#include "ipc/ipc_sender.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/events/blink/did_overscroll_params.h"
#include "ui/gfx/geometry/skia_conversions.h"

namespace content {

namespace {

bool g_viz_established = false;

// TODO(vasilyt): Create BrowserCompositor for webview and move it there?
void EstablishVizConnection(
    scoped_refptr<gpu::GpuChannelHost> gpu_channel_host) {
  content::CompositorDependenciesAndroid::Get()
      .TryEstablishVizConnectionIfNeeded();
  g_viz_established = true;
}

void EstablishGpuChannelToEstablishVizConnection() {
  if (g_viz_established)
    return;

  content::BrowserMainLoop::GetInstance()
      ->gpu_channel_establish_factory()
      ->EstablishGpuChannel(base::BindOnce(&EstablishVizConnection));
}

}  // namespace

// This class runs on the IO thread and is destroyed when the renderer
// side closes the mojo channel.
class SynchronousCompositorControlHost
    : public blink::mojom::SynchronousCompositorControlHost {
 public:
  SynchronousCompositorControlHost(
      scoped_refptr<SynchronousCompositorSyncCallBridge> bridge,
      int process_id)
      : bridge_(std::move(bridge)), process_id_(process_id) {}

  ~SynchronousCompositorControlHost() override {
    bridge_->RemoteClosedOnIOThread();
  }

  static void Create(
      mojo::PendingReceiver<blink::mojom::SynchronousCompositorControlHost>
          receiver,
      scoped_refptr<SynchronousCompositorSyncCallBridge> bridge,
      int process_id) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindOnce(&CreateOnIOThread, std::move(receiver),
                                  std::move(bridge), process_id));
  }

  static void CreateOnIOThread(
      mojo::PendingReceiver<blink::mojom::SynchronousCompositorControlHost>
          receiver,
      scoped_refptr<SynchronousCompositorSyncCallBridge> bridge,
      int process_id) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    auto host_control_receiver = mojo::MakeSelfOwnedReceiver(
        std::make_unique<SynchronousCompositorControlHost>(bridge, process_id),
        std::move(receiver));
    bridge->SetHostControlReceiverOnIOThread(host_control_receiver);
  }

  // SynchronousCompositorControlHost overrides.
  void ReturnFrame(
      uint32_t layer_tree_frame_sink_id,
      uint32_t metadata_version,
      const std::optional<viz::LocalSurfaceId>& local_surface_id,
      std::optional<viz::CompositorFrame> frame,
      std::optional<viz::HitTestRegionList> hit_test_region_list) override {
    if (frame && (!local_surface_id || !local_surface_id->is_valid())) {
      bad_message::ReceivedBadMessage(
          process_id_, bad_message::SYNC_COMPOSITOR_NO_LOCAL_SURFACE_ID);
      return;
    }
    if (!bridge_->ReceiveFrameOnIOThread(
            layer_tree_frame_sink_id, metadata_version, local_surface_id,
            std::move(frame), std::move(hit_test_region_list))) {
      bad_message::ReceivedBadMessage(
          process_id_, bad_message::SYNC_COMPOSITOR_NO_FUTURE_FRAME);
    }
  }

  void BeginFrameResponse(
      blink::mojom::SyncCompositorCommonRendererParamsPtr params) override {
    if (!bridge_->BeginFrameResponseOnIOThread(std::move(params))) {
      bad_message::ReceivedBadMessage(
          process_id_, bad_message::SYNC_COMPOSITOR_NO_BEGIN_FRAME);
    }
  }

 private:
  scoped_refptr<SynchronousCompositorSyncCallBridge> bridge_;
  const int process_id_;
};

// static
std::unique_ptr<SynchronousCompositorHost> SynchronousCompositorHost::Create(
    RenderWidgetHostViewAndroid* rwhva,
    const viz::FrameSinkId& frame_sink_id,
    viz::HostFrameSinkManager* host_frame_sink_manager) {
  if (!rwhva->synchronous_compositor_client())
    return nullptr;  // Not using sync compositing.

  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  bool use_in_proc_software_draw =
      command_line->HasSwitch(switches::kSingleProcess);
  return base::WrapUnique(new SynchronousCompositorHost(
      rwhva, frame_sink_id, host_frame_sink_manager,
      use_in_proc_software_draw));
}

SynchronousCompositorHost::SynchronousCompositorHost(
    RenderWidgetHostViewAndroid* rwhva,
    const viz::FrameSinkId& frame_sink_id,
    viz::HostFrameSinkManager* host_frame_sink_manager,
    bool use_in_proc_software_draw)
    : rwhva_(rwhva),
      client_(rwhva->synchronous_compositor_client()),
      frame_sink_id_(frame_sink_id),
      host_frame_sink_manager_(host_frame_sink_manager),
      use_in_process_zero_copy_software_draw_(use_in_proc_software_draw),
      bytes_limit_(0u),
      renderer_param_version_(0u),
      need_invalidate_count_(0u),
      invalidate_needs_draw_(false),
      did_activate_pending_tree_count_(0u) {
  EstablishGpuChannelToEstablishVizConnection();
  client_->DidInitializeCompositor(this, frame_sink_id_);
  host_frame_sink_manager_->CreateHitTestQueryForSynchronousCompositor(
      frame_sink_id_);
  bridge_ = new SynchronousCompositorSyncCallBridge(this);
}

SynchronousCompositorHost::~SynchronousCompositorHost() {
  if (outstanding_begin_frame_requests_ && begin_frame_source_)
    begin_frame_source_->RemoveObserver(this);
  host_frame_sink_manager_->EraseHitTestQueryForSynchronousCompositor(
      frame_sink_id_);
  client_->DidDestroyCompositor(this, frame_sink_id_);
  bridge_->HostDestroyedOnUIThread();
}

void SynchronousCompositorHost::InitMojo() {
  mojo::PendingRemote<blink::mojom::SynchronousCompositorControlHost>
      host_control;

  SynchronousCompositorControlHost::Create(
      host_control.InitWithNewPipeAndPassReceiver(), bridge_,
      rwhva_->GetRenderWidgetHost()->GetProcess()->GetID());
  rwhva_->host()->GetWidgetInputHandler()->AttachSynchronousCompositor(
      std::move(host_control), host_receiver_.BindNewEndpointAndPassRemote(),
      sync_compositor_.BindNewEndpointAndPassReceiver());
}

bool SynchronousCompositorHost::IsReadyForSynchronousCall() {
  bool res = bridge_->IsRemoteReadyOnUIThread();
  DCHECK(!res || GetSynchronousCompositor());
  return res;
}

void SynchronousCompositorHost::OnCompositorVisible() {
  CompositorDependenciesAndroid::Get().OnSynchronousCompositorVisible();
}

void SynchronousCompositorHost::OnCompositorHidden() {
  CompositorDependenciesAndroid::Get().OnSynchronousCompositorHidden();
}

scoped_refptr<SynchronousCompositor::FrameFuture>
SynchronousCompositorHost::DemandDrawHwAsync(
    const gfx::Size& viewport_size,
    const gfx::Rect& viewport_rect_for_tile_priority,
    const gfx::Transform& transform_for_tile_priority) {
  velocity_in_pixels_per_second_ = 0.f;
  draw_hw_called_ = true;
  invalidate_needs_draw_ = false;
  num_invalidates_since_last_draw_ = 0u;
  scoped_refptr<FrameFuture> frame_future = new FrameFuture();
  if (!allow_async_draw_) {
    allow_async_draw_ = allow_async_draw_ || IsReadyForSynchronousCall();
    auto frame_ptr = std::make_unique<Frame>();
    *frame_ptr = DemandDrawHw(viewport_size, viewport_rect_for_tile_priority,
                              transform_for_tile_priority);
    frame_future->SetFrame(std::move(frame_ptr));
    return frame_future;
  }

  blink::mojom::SyncCompositorDemandDrawHwParamsPtr params =
      blink::mojom::SyncCompositorDemandDrawHwParams::New(
          viewport_size, viewport_rect_for_tile_priority,
          transform_for_tile_priority,
          /*need_new_local_surface_id=*/was_evicted_);

  was_evicted_ = false;

  blink::mojom::SynchronousCompositor* compositor = GetSynchronousCompositor();
  if (!bridge_->SetFrameFutureOnUIThread(frame_future)) {
    frame_future->SetFrame(nullptr);
  } else {
    DCHECK(compositor);
    compositor->DemandDrawHwAsync(std::move(params));
  }
  return frame_future;
}

SynchronousCompositor::Frame SynchronousCompositorHost::DemandDrawHw(
    const gfx::Size& viewport_size,
    const gfx::Rect& viewport_rect_for_tile_priority,
    const gfx::Transform& transform_for_tile_priority) {
  blink::mojom::SyncCompositorDemandDrawHwParamsPtr params =
      blink::mojom::SyncCompositorDemandDrawHwParams::New(
          viewport_size, viewport_rect_for_tile_priority,
          transform_for_tile_priority,
          /*need_new_local_surface_id=*/was_evicted_);

  was_evicted_ = false;

  uint32_t layer_tree_frame_sink_id;
  uint32_t metadata_version = 0u;
  std::optional<viz::LocalSurfaceId> local_surface_id;
  std::optional<viz::CompositorFrame> compositor_frame;
  std::optional<viz::HitTestRegionList> hit_test_region_list;
  blink::mojom::SyncCompositorCommonRendererParamsPtr common_renderer_params;

  {
    mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call;
    base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope
        allow_base_sync_primitives;
    if (!IsReadyForSynchronousCall() ||
        !GetSynchronousCompositor()->DemandDrawHw(
            std::move(params), &common_renderer_params,
            &layer_tree_frame_sink_id, &metadata_version, &local_surface_id,
            &compositor_frame, &hit_test_region_list)) {
      return SynchronousCompositor::Frame();
    }
  }

  UpdateState(std::move(common_renderer_params));

  if (compositor_frame) {
    if (!local_surface_id || !local_surface_id->is_valid()) {
      bad_message::ReceivedBadMessage(
          rwhva_->GetRenderWidgetHost()->GetProcess()->GetID(),
          bad_message::SYNC_COMPOSITOR_NO_LOCAL_SURFACE_ID);
      return SynchronousCompositor::Frame();
    }
  } else {
    return SynchronousCompositor::Frame();
  }

  SynchronousCompositor::Frame frame;
  frame.frame = std::make_unique<viz::CompositorFrame>();
  frame.layer_tree_frame_sink_id = layer_tree_frame_sink_id;
  if (local_surface_id)
    frame.local_surface_id = local_surface_id.value();
  *frame.frame = std::move(*compositor_frame);
  frame.hit_test_region_list = std::move(hit_test_region_list);
  UpdateFrameMetaData(metadata_version, frame.frame->metadata.Clone(),
                      std::move(local_surface_id));
  return frame;
}

void SynchronousCompositorHost::UpdateFrameMetaData(
    uint32_t version,
    viz::CompositorFrameMetadata frame_metadata,
    std::optional<viz::LocalSurfaceId> new_local_surface_id) {
  // Ignore if |frame_metadata_version_| is newer than |version|. This
  // comparison takes into account when the unsigned int wraps.
  if ((frame_metadata_version_ - version) < 0x80000000) {
    return;
  }
  frame_metadata_version_ = version;
  if (new_local_surface_id)
    local_surface_id_ = new_local_surface_id.value();
}

namespace {

class ScopedSetSkCanvas {
 public:
  explicit ScopedSetSkCanvas(SkCanvas* canvas) {
    SynchronousCompositorSetSkCanvas(canvas);
  }

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

  ~ScopedSetSkCanvas() {
    SynchronousCompositorSetSkCanvas(nullptr);
  }
};

}

bool SynchronousCompositorHost::DemandDrawSwInProc(SkCanvas* canvas) {
  mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call;
  base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope
      allow_base_sync_primitives;
  blink::mojom::SyncCompositorCommonRendererParamsPtr common_renderer_params;
  std::optional<viz::CompositorFrameMetadata> metadata;
  ScopedSetSkCanvas set_sk_canvas(canvas);
  blink::mojom::SyncCompositorDemandDrawSwParamsPtr params =
      blink::mojom::SyncCompositorDemandDrawSwParams::New();  // Unused.
  uint32_t metadata_version = 0u;
  invalidate_needs_draw_ = false;
  if (!IsReadyForSynchronousCall() ||
      !GetSynchronousCompositor()->DemandDrawSw(std::move(params),
                                                &common_renderer_params,
                                                &metadata_version, &metadata))
    return false;
  if (!metadata)
    return false;
  UpdateState(std::move(common_renderer_params));
  UpdateFrameMetaData(metadata_version, std::move(*metadata), std::nullopt);
  return true;
}

class SynchronousCompositorHost::ScopedSendZeroMemory {
 public:
  ScopedSendZeroMemory(SynchronousCompositorHost* host) : host_(host) {}

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

  ~ScopedSendZeroMemory() { host_->SendZeroMemory(); }

 private:
  const raw_ptr<SynchronousCompositorHost> host_;
};

struct SynchronousCompositorHost::SharedMemoryWithSize {
  base::WritableSharedMemoryMapping shared_memory;
  const size_t stride;
  const size_t buffer_size;

  SharedMemoryWithSize(size_t stride, size_t buffer_size)
      : stride(stride), buffer_size(buffer_size) {}

  SharedMemoryWithSize(const SharedMemoryWithSize&) = delete;
  SharedMemoryWithSize& operator=(const SharedMemoryWithSize&) = delete;
};

bool SynchronousCompositorHost::DemandDrawSw(SkCanvas* canvas,
                                             bool software_canvas) {
  velocity_in_pixels_per_second_ = 0.f;
  num_invalidates_since_last_draw_ = 0u;
  if (use_in_process_zero_copy_software_draw_)
    return DemandDrawSwInProc(canvas);

  blink::mojom::SyncCompositorDemandDrawSwParamsPtr params =
      blink::mojom::SyncCompositorDemandDrawSwParams::New();
  params->size = gfx::Size(canvas->getBaseLayerSize().width(),
                           canvas->getBaseLayerSize().height());
  SkIRect canvas_clip = canvas->getDeviceClipBounds();
  params->clip = gfx::SkIRectToRect(canvas_clip);
  params->transform = gfx::SkMatrixToTransform(canvas->getTotalMatrix());
  if (params->size.IsEmpty())
    return true;

  SkImageInfo info =
      SkImageInfo::MakeN32Premul(params->size.width(), params->size.height());
  DCHECK_EQ(kRGBA_8888_SkColorType, info.colorType());
  size_t stride = info.minRowBytes();
  size_t buffer_size = info.computeByteSize(stride);
  if (SkImageInfo::ByteSizeOverflowed(buffer_size))
    return false;

  SetSoftwareDrawSharedMemoryIfNeeded(stride, buffer_size);
  if (!software_draw_shm_)
    return false;

  std::optional<viz::CompositorFrameMetadata> metadata;
  uint32_t metadata_version = 0u;
  blink::mojom::SyncCompositorCommonRendererParamsPtr common_renderer_params;
  {
    mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call;
    base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope
        allow_base_sync_primitives;
    if (!IsReadyForSynchronousCall() ||
        !GetSynchronousCompositor()->DemandDrawSw(
            std::move(params), &common_renderer_params, &metadata_version,
            &metadata)) {
      return false;
    }
  }
  ScopedSendZeroMemory send_zero_memory(this);
  if (!metadata)
    return false;

  UpdateState(std::move(common_renderer_params));
  UpdateFrameMetaData(metadata_version, std::move(*metadata), std::nullopt);

  SkBitmap bitmap;
  base::span<uint8_t> mem(software_draw_shm_->shared_memory);
  CHECK_GE(mem.size(), info.computeByteSize(stride));
  SkPixmap pixmap(info, mem.data(), stride);

  bool pixels_released = false;
  {
    TRACE_EVENT0("browser", "DrawBitmap");
    canvas->save();
    canvas->resetMatrix();
    sk_sp<SkImage> image;
    // Software canvas will draw immediately, so it's safe to avoid this copy.
    if (software_canvas) {
      auto mark_bool = [](const void* pixels, void* context) {
        *static_cast<bool*>(context) = true;
      };
      image = SkImages::RasterFromPixmap(pixmap, mark_bool, &pixels_released);
    } else {
      image = SkImages::RasterFromPixmapCopy(pixmap);
    }
    canvas->drawImage(image, 0, 0);
    canvas->restore();
  }

  if (software_canvas) {
    // It could lead to UAF if this CHECK fails, hence it's a release CHECK.
    CHECK(pixels_released);
  }

  return true;
}

void SynchronousCompositorHost::SetSoftwareDrawSharedMemoryIfNeeded(
    size_t stride,
    size_t buffer_size) {
  if (software_draw_shm_ && software_draw_shm_->stride == stride &&
      software_draw_shm_->buffer_size == buffer_size)
    return;
  software_draw_shm_.reset();
  auto software_draw_shm =
      std::make_unique<SharedMemoryWithSize>(stride, buffer_size);
  base::WritableSharedMemoryRegion shm_region;
  {
    TRACE_EVENT1("browser", "AllocateSharedMemory", "buffer_size", buffer_size);
    shm_region = base::WritableSharedMemoryRegion::Create(buffer_size);
    if (!shm_region.IsValid())
      return;

    software_draw_shm->shared_memory = shm_region.Map();
    if (!software_draw_shm->shared_memory.IsValid())
      return;
  }

  bool success = false;
  blink::mojom::SyncCompositorCommonRendererParamsPtr common_renderer_params;
  {
    mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call;
    base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope
        allow_base_sync_primitives;
    if (!IsReadyForSynchronousCall() ||
        !GetSynchronousCompositor()->SetSharedMemory(
            std::move(shm_region), &success, &common_renderer_params) ||
        !success) {
      return;
    }
  }
  software_draw_shm_ = std::move(software_draw_shm);
  UpdateState(std::move(common_renderer_params));
}

void SynchronousCompositorHost::SendZeroMemory() {
  // No need to check return value.
  if (blink::mojom::SynchronousCompositor* compositor =
          GetSynchronousCompositor())
    compositor->ZeroSharedMemory();
}

void SynchronousCompositorHost::ReturnResources(
    uint32_t layer_tree_frame_sink_id,
    std::vector<viz::ReturnedResource> resources) {
  DCHECK(!resources.empty());
  if (blink::mojom::SynchronousCompositor* compositor =
          GetSynchronousCompositor())
    compositor->ReclaimResources(layer_tree_frame_sink_id,
                                 std::move(resources));
}

void SynchronousCompositorHost::OnCompositorFrameTransitionDirectiveProcessed(
    uint32_t layer_tree_frame_sink_id,
    uint32_t sequence_id) {
  if (blink::mojom::SynchronousCompositor* compositor =
          GetSynchronousCompositor()) {
    compositor->OnCompositorFrameTransitionDirectiveProcessed(
        layer_tree_frame_sink_id, sequence_id);
  }
}

void SynchronousCompositorHost::DidPresentCompositorFrames(
    viz::FrameTimingDetailsMap timing_details,
    uint32_t frame_token) {
  timing_details_.insert(timing_details.begin(), timing_details.end());
  if (!timing_details_.empty())
    AddBeginFrameRequest(BEGIN_FRAME);
}

void SynchronousCompositorHost::SetMemoryPolicy(size_t bytes_limit) {
  if (bytes_limit_ == bytes_limit)
    return;

  bytes_limit_ = bytes_limit;
  if (blink::mojom::SynchronousCompositor* compositor =
          GetSynchronousCompositor())
    compositor->SetMemoryPolicy(bytes_limit_);
}

float SynchronousCompositorHost::GetVelocityInPixelsPerSecond() {
  return velocity_in_pixels_per_second_;
}

void SynchronousCompositorHost::DidChangeRootLayerScrollOffset(
    const gfx::PointF& root_offset) {
  if (root_scroll_offset_ == root_offset)
    return;
  root_scroll_offset_ = root_offset;
  if (blink::mojom::SynchronousCompositor* compositor =
          GetSynchronousCompositor())
    compositor->SetScroll(root_scroll_offset_);
}

void SynchronousCompositorHost::SynchronouslyZoomBy(float zoom_delta,
                                                    const gfx::Point& anchor) {
  blink::mojom::SyncCompositorCommonRendererParamsPtr common_renderer_params;
  {
    mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call;
    base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope
        allow_base_sync_primitives;
    if (!IsReadyForSynchronousCall() ||
        !GetSynchronousCompositor()->ZoomBy(zoom_delta, anchor,
                                            &common_renderer_params)) {
      return;
    }
  }
  UpdateState(std::move(common_renderer_params));
}

void SynchronousCompositorHost::OnComputeScroll(
    base::TimeTicks animation_time) {
  on_compute_scroll_called_ = true;
}

ui::ViewAndroid::CopyViewCallback
SynchronousCompositorHost::GetCopyViewCallback() {
  // Unretained is safe since callback is helped by ViewAndroid which has same
  // lifetime as this, and client outlives this.
  return base::BindRepeating(&SynchronousCompositorClient::CopyOutput,
                             base::Unretained(client_), this);
}

void SynchronousCompositorHost::DidOverscroll(
    const ui::DidOverscrollParams& over_scroll_params) {
  client_->DidOverscroll(this, over_scroll_params.accumulated_overscroll,
                         over_scroll_params.latest_overscroll_delta,
                         over_scroll_params.current_fling_velocity);
}

void SynchronousCompositorHost::SetNeedsBeginFrames(bool needs_begin_frames) {
  TRACE_EVENT1("cc", "SynchronousCompositorHost::SetNeedsBeginFrames",
               "needs_begin_frames", needs_begin_frames);
  if (needs_begin_frames)
    AddBeginFrameRequest(PERSISTENT_BEGIN_FRAME);
  else
    ClearBeginFrameRequest(PERSISTENT_BEGIN_FRAME);
}

void SynchronousCompositorHost::SetThreadIds(
    const std::vector<int32_t>& thread_ids) {
  client_->SetThreadIds(thread_ids);
}

void SynchronousCompositorHost::LayerTreeFrameSinkCreated() {
  bridge_->RemoteReady();

  // New LayerTreeFrameSink is not aware of state from Browser side. So need to
  // re-send all browser side state here.
  blink::mojom::SynchronousCompositor* compositor = GetSynchronousCompositor();
  DCHECK(compositor);
  compositor->SetMemoryPolicy(bytes_limit_);

  SendBeginFramePaused();
}

void SynchronousCompositorHost::UpdateState(
    blink::mojom::SyncCompositorCommonRendererParamsPtr params) {
  // Ignore if |renderer_param_version_| is newer than |params.version|. This
  // comparison takes into account when the unsigned int wraps.
  if ((renderer_param_version_ - params->version) < 0x80000000) {
    return;
  }
  renderer_param_version_ = params->version;
  root_scroll_offset_ = params->total_scroll_offset;
  max_scroll_offset_ = params->max_scroll_offset;
  scrollable_size_ = params->scrollable_size;
  page_scale_factor_ = params->page_scale_factor;
  min_page_scale_factor_ = params->min_page_scale_factor;
  max_page_scale_factor_ = params->max_page_scale_factor;
  invalidate_needs_draw_ |= params->invalidate_needs_draw;

  if (need_invalidate_count_ != params->need_invalidate_count) {
    need_invalidate_count_ = params->need_invalidate_count;
    if (invalidate_needs_draw_) {
      num_invalidates_since_last_draw_++;
      client_->PostInvalidate(this);
    } else {
      GetSynchronousCompositor()->WillSkipDraw();
    }
  }

  if (did_activate_pending_tree_count_ !=
      params->did_activate_pending_tree_count) {
    did_activate_pending_tree_count_ = params->did_activate_pending_tree_count;
    client_->DidUpdateContent(this);
  }

  UpdateRootLayerStateOnClient();
}

void SynchronousCompositorHost::DidBecomeActive() {
  UpdateRootLayerStateOnClient();
}

void SynchronousCompositorHost::UpdateRootLayerStateOnClient() {
  // Ensure only valid values from compositor are sent to client.
  // Compositor has page_scale_factor set to 0 before initialization, so check
  // for that case here.
  if (page_scale_factor_) {
    client_->UpdateRootLayerState(
        this, root_scroll_offset_, max_scroll_offset_, scrollable_size_,
        page_scale_factor_, min_page_scale_factor_, max_page_scale_factor_);
  }
}

RenderProcessHost* SynchronousCompositorHost::GetRenderProcessHost() {
  return rwhva_->GetRenderWidgetHost()->GetProcess();
}

blink::mojom::SynchronousCompositor*
SynchronousCompositorHost::GetSynchronousCompositor() {
  if (!sync_compositor_)
    return nullptr;
  return sync_compositor_.get();
}

void SynchronousCompositorHost::OnBeginFrame(const viz::BeginFrameArgs& args) {
  TRACE_EVENT0("cc,benchmark", "SynchronousCompositorHost::OnBeginFrame");

  // In sync mode, we disregard missed frame args to ensure that
  // SynchronousCompositorBrowserFilter::SyncStateAfterVSync will be called
  // during WindowAndroid::WindowBeginFrameSource::OnVSync() observer iteration.
  if (args.type == viz::BeginFrameArgs::MISSED)
    return;

  // We need to check this before |outstanding_begin_frame_requests_| will be
  // changed by ClearBeginFrameRequest below.
  bool needs_begin_frame =
      (outstanding_begin_frame_requests_ & BEGIN_FRAME) ||
      (outstanding_begin_frame_requests_ & PERSISTENT_BEGIN_FRAME);

  last_begin_frame_time_delta_ =
      args.frame_time - last_begin_frame_args_.frame_time;

  // Update |last_begin_frame_args_| before handling
  // |outstanding_begin_frame_requests_| to prevent the BeginFrameSource from
  // sending the same MISSED args in infinite recursion.
  last_begin_frame_args_ = args;

  ClearBeginFrameRequest(BEGIN_FRAME);

  if (on_compute_scroll_called_ ||
      !rwhva_->GetViewRenderInputRouter()->is_currently_scrolling_viewport()) {
    rwhva_->host()->ProgressFlingIfNeeded(args.frame_time);
  } else if (base::FeatureList::IsEnabled(
                 features::kWebViewSuppressTapDuringFling)) {
    // Normally, `OnComputeScroll` is called after `OnBeginFrame`, but before
    // `DemandDrawHwAsync`. So `OnBeginFrame` calls before the first draw will
    // end up here regardless of whether `OnComputeScroll` will be called. If
    // these frames contain fling, then don't cancel fling prematurely. Note
    // normally fling cannot happen from user interaction this way because touch
    // scroll happens before fling.
    if (draw_hw_called_) {
      // If we are not ticking flings ourselves, also reset the tracking state
      // for fling so the first tap during / after fling is not suppressed.
      rwhva_->host()->StopFling();
    }
  }

  if (needs_begin_frame) {
    SendBeginFrame(args);
  }
}

const viz::BeginFrameArgs& SynchronousCompositorHost::LastUsedBeginFrameArgs()
    const {
  return last_begin_frame_args_;
}

void SynchronousCompositorHost::OnBeginFrameSourcePausedChanged(bool paused) {
  if (paused != begin_frame_paused_) {
    begin_frame_paused_ = paused;
    SendBeginFramePaused();
  }
}

bool SynchronousCompositorHost::WantsAnimateOnlyBeginFrames() const {
  return false;
}

void SynchronousCompositorHost::SendBeginFramePaused() {
  if (blink::mojom::SynchronousCompositor* compositor =
          GetSynchronousCompositor())
    compositor->SetBeginFrameSourcePaused(begin_frame_paused_);
}

void SynchronousCompositorHost::SendBeginFrame(viz::BeginFrameArgs args) {
  if (num_invalidates_since_last_draw_ > 5u) {
    // Throttle begin frames if there has been no draws in response to
    // invalidates. This can happen if webview is detached or offscreen. There
    // are cases where renderer is still expected to make progress. In this
    // case renderer receives no back pressure so reduce the frequency of begin
    // frames to avoid unnecessary work.
    if (num_begin_frames_to_skip_) {
      TRACE_EVENT_INSTANT0("cc",
                           "SynchronousCompositorHost::SendBeginFrame_skipped",
                           TRACE_EVENT_SCOPE_THREAD);
      num_begin_frames_to_skip_--;
      return;
    } else {
      num_begin_frames_to_skip_ =
          std::min(num_invalidates_since_last_draw_ / 5, 250u);
    }
  } else {
    num_begin_frames_to_skip_ = 0u;
  }

  TRACE_EVENT2("cc", "SynchronousCompositorHost::SendBeginFrame",
               "frame_number", args.frame_id.sequence_number, "frame_time_us",
               args.frame_time);

  if (!bridge_->WaitAfterVSyncOnUIThread())
    return;

  blink::mojom::SynchronousCompositor* compositor = GetSynchronousCompositor();
  DCHECK(compositor);
  compositor->BeginFrame(args, timing_details_);
  timing_details_.clear();
}

void SynchronousCompositorHost::BeginFrameComplete(
    blink::mojom::SyncCompositorCommonRendererParamsPtr params) {
  velocity_in_pixels_per_second_ = 0.f;
  gfx::PointF offset = root_scroll_offset_;
  if (params) {
    UpdateState(std::move(params));
  }
  // Sanity check frame time delta.
  if (last_begin_frame_time_delta_.InMicroseconds() < 100 ||
      last_begin_frame_time_delta_.InMicroseconds() > 1000000) {
    return;
  }
  gfx::Vector2dF scroll = root_scroll_offset_ - offset;
  float major_scroll_in_last_begin_frame =
      std::abs(scroll.x()) > std::abs(scroll.y()) ? scroll.x() : scroll.y();
  velocity_in_pixels_per_second_ = major_scroll_in_last_begin_frame /
                                   last_begin_frame_time_delta_.InSecondsF();
  TRACE_EVENT_INSTANT("cc", "SynchronousCompositorHost::BeginFrameComplete",
                      "scroll", major_scroll_in_last_begin_frame, "delta",
                      last_begin_frame_time_delta_.InMicroseconds());
}

void SynchronousCompositorHost::SetBeginFrameSource(
    viz::BeginFrameSource* begin_frame_source) {
  DCHECK(!begin_frame_source_);
  DCHECK(!outstanding_begin_frame_requests_);
  begin_frame_source_ = begin_frame_source;
}

void SynchronousCompositorHost::AddBeginFrameRequest(
    BeginFrameRequestType request) {
  uint32_t prior_requests = outstanding_begin_frame_requests_;
  outstanding_begin_frame_requests_ = prior_requests | request;

  // Note that if we don't currently have a BeginFrameSource, outstanding begin
  // frame requests will be pushed if/when we get one during
  // |StartObservingRootWindow()| or when the DelegatedFrameHostAndroid sets it.
  viz::BeginFrameSource* source = begin_frame_source_;
  if (source && outstanding_begin_frame_requests_ && !prior_requests)
    source->AddObserver(this);
}

void SynchronousCompositorHost::ClearBeginFrameRequest(
    BeginFrameRequestType request) {
  uint32_t prior_requests = outstanding_begin_frame_requests_;
  outstanding_begin_frame_requests_ = prior_requests & ~request;

  viz::BeginFrameSource* source = begin_frame_source_;
  if (source && !outstanding_begin_frame_requests_ && prior_requests)
    source->RemoveObserver(this);
}

void SynchronousCompositorHost::RequestOneBeginFrame() {
  AddBeginFrameRequest(BEGIN_FRAME);
}

void SynchronousCompositorHost::AddBeginFrameCompletionCallback(
    base::OnceClosure callback) {
  client_->AddBeginFrameCompletionCallback(std::move(callback));
}

viz::SurfaceId SynchronousCompositorHost::GetSurfaceId() const {
  return viz::SurfaceId(frame_sink_id_, local_surface_id_);
}

void SynchronousCompositorHost::DidInvalidate() {
  invalidate_needs_draw_ = true;
}

void SynchronousCompositorHost::WasEvicted() {
  was_evicted_ = true;
}

}  // namespace content