chromium/android_webview/browser/gfx/browser_view_renderer.cc

// 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 "android_webview/browser/gfx/browser_view_renderer.h"

#include <memory>
#include <utility>

#include "android_webview/browser/gfx/browser_view_renderer_client.h"
#include "android_webview/browser/gfx/compositor_frame_consumer.h"
#include "android_webview/browser/gfx/root_frame_sink.h"
#include "android_webview/browser/gfx/root_frame_sink_proxy.h"
#include "android_webview/common/aw_features.h"
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/supports_user_data.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/traced_value.h"
#include "cc/base/math_util.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
#include "ui/gfx/geometry/vector2d_f.h"

namespace android_webview {

namespace {

const double kEpsilon = 1e-8;

// Used to calculate memory allocation. Determined experimentally.
const size_t kMemoryMultiplier = 20;
const size_t kBytesPerPixel = 4;
const size_t kMemoryAllocationStep = 5 * 1024 * 1024;
uint64_t g_memory_override_in_bytes = 0u;

const void* const kBrowserViewRendererUserDataKey =
    &kBrowserViewRendererUserDataKey;

class BrowserViewRendererUserData : public base::SupportsUserData::Data {
 public:
  explicit BrowserViewRendererUserData(BrowserViewRenderer* ptr) : bvr_(ptr) {}

  static BrowserViewRenderer* GetBrowserViewRenderer(
      content::WebContents* web_contents) {
    if (!web_contents)
      return NULL;
    BrowserViewRendererUserData* data =
        static_cast<BrowserViewRendererUserData*>(
            web_contents->GetUserData(kBrowserViewRendererUserDataKey));
    return data ? data->bvr_.get() : NULL;
  }

 private:
  raw_ptr<BrowserViewRenderer> bvr_;
};

}  // namespace

// static
void BrowserViewRenderer::CalculateTileMemoryPolicy() {
  base::CommandLine* cl = base::CommandLine::ForCurrentProcess();

  // If the value was overridden on the command line, use the specified value.
  bool client_hard_limit_bytes_overridden =
      cl->HasSwitch(switches::kForceGpuMemAvailableMb);
  if (client_hard_limit_bytes_overridden) {
    base::StringToUint64(
        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
            switches::kForceGpuMemAvailableMb),
        &g_memory_override_in_bytes);
    g_memory_override_in_bytes *= 1024 * 1024;
  }
}

// static
BrowserViewRenderer* BrowserViewRenderer::FromWebContents(
    content::WebContents* web_contents) {
  return BrowserViewRendererUserData::GetBrowserViewRenderer(web_contents);
}

BrowserViewRenderer::BrowserViewRenderer(
    BrowserViewRendererClient* client,
    const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
    const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
    : client_(client),
      ui_task_runner_(ui_task_runner),
      current_compositor_frame_consumer_(nullptr),
      compositor_(nullptr),
      is_paused_(false),
      view_visible_(false),
      window_visible_(false),
      attached_to_window_(false),
      was_attached_(false),
      hardware_enabled_(false),
      dip_scale_(0.f),
      page_scale_factor_(1.f),
      min_page_scale_factor_(0.f),
      max_page_scale_factor_(0.f),
      on_new_picture_enable_(false),
      clear_view_(false),
      offscreen_pre_raster_(false) {
  begin_frame_source_ = std::make_unique<BeginFrameSourceWebView>();
  root_frame_sink_proxy_ = std::make_unique<RootFrameSinkProxy>(
      ui_task_runner_, this, begin_frame_source_.get());
  UpdateBeginFrameSource();

  base::OnceCallback<base::PlatformThreadId()> compute_current_thread_id =
      base::BindOnce([]() { return base::PlatformThread::CurrentId(); });
  io_task_runner->PostTask(
      FROM_HERE, std::move(compute_current_thread_id)
                     .Then(base::BindPostTaskToCurrentDefault(base::BindOnce(
                         &BrowserViewRenderer::SetBrowserIOThreadId,
                         weak_ptr_factory_.GetWeakPtr()))));
}

BrowserViewRenderer::~BrowserViewRenderer() {
  DCHECK(compositor_map_.empty());
  DCHECK(!current_compositor_frame_consumer_);
  if (foreground_for_gpu_resources_) {
    // Cannot leave a dangling foreground compositor. Just detach from
    // destructor.
    OnDetachedFromWindow();
  }

  // We need to destroy |root_frame_sink_proxy_| before |begin_frame_source_|;
  root_frame_sink_proxy_.reset();
}

base::WeakPtr<CompositorFrameProducer> BrowserViewRenderer::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

void BrowserViewRenderer::SetCurrentCompositorFrameConsumer(
    CompositorFrameConsumer* compositor_frame_consumer) {
  if (compositor_frame_consumer == current_compositor_frame_consumer_) {
    return;
  }
  current_compositor_frame_consumer_ = compositor_frame_consumer;
  if (current_compositor_frame_consumer_) {
    // Previous renderer will evict CompositorFrame, compositor needs to submit
    // next frames with new local surface id.
    if (compositor_)
      compositor_->WasEvicted();

    RootFrameSinkGetter root_sink_getter;
    if (root_frame_sink_proxy_)
      root_sink_getter = root_frame_sink_proxy_->GetRootFrameSinkCallback();
    current_compositor_frame_consumer_->SetCompositorFrameProducer(
        this, std::move(root_sink_getter));
    OnParentDrawDataUpdated(current_compositor_frame_consumer_);
  }
}

void BrowserViewRenderer::RegisterWithWebContents(
    content::WebContents* web_contents) {
  web_contents->SetUserData(
      kBrowserViewRendererUserDataKey,
      std::make_unique<BrowserViewRendererUserData>(this));
}

void BrowserViewRenderer::TrimMemory() {
  DCHECK(ui_task_runner_->BelongsToCurrentThread());
  TRACE_EVENT0("android_webview", "BrowserViewRenderer::TrimMemory");

  // Trimming memory might destroy HardwareRenderer which will evict
  // CompositorFrame, compositor needs to submit next frames with new local
  // surface id.
  if (compositor_)
    compositor_->WasEvicted();

  // Just set the memory limit to 0 and drop all tiles. This will be reset to
  // normal levels in the next DrawGL call.
  if (!offscreen_pre_raster_)
    ReleaseHardware();
}

gfx::Rect BrowserViewRenderer::ComputeTileRectAndUpdateMemoryPolicy() {
  if (!compositor_) {
    return gfx::Rect();
  }

  if (!hardware_enabled_) {
    compositor_->SetMemoryPolicy(0u);
    return gfx::Rect();
  }

  gfx::Transform transform_for_tile_priority =
      external_draw_constraints_.transform;

  gfx::Rect viewport_rect_for_tile_priority_in_view_space;
  gfx::Transform screen_to_view;
  if (transform_for_tile_priority.GetInverse(&screen_to_view)) {
    // Convert from screen space to view space.
    viewport_rect_for_tile_priority_in_view_space =
        cc::MathUtil::ProjectEnclosingClippedRect(
            screen_to_view,
            gfx::Rect(external_draw_constraints_.viewport_size));
  }
  viewport_rect_for_tile_priority_in_view_space.Intersect(gfx::Rect(size_));

  size_t bytes_limit = 0u;
  if (g_memory_override_in_bytes) {
    bytes_limit = static_cast<size_t>(g_memory_override_in_bytes);
  } else {
    // Note we are using |last_on_draw_global_visible_rect_| rather than
    // |external_draw_constraints_.viewport_size|. This is to reduce budget
    // for a webview that's much smaller than the surface it's rendering.
    gfx::Rect interest_rect;
    if (offscreen_pre_raster_) {
      interest_rect = gfx::Rect(size_);
    } else {
      // Re-compute screen-space rect for computing tile budget, since tile is
      // rastered in screen space.
      gfx::Rect viewport_rect_for_tile_priority_in_screen_space =
          cc::MathUtil::ProjectEnclosingClippedRect(
              transform_for_tile_priority,
              viewport_rect_for_tile_priority_in_view_space);
      // Intersect by viewport size again, in case axis-aligning operations made
      // the rect bigger than necessary.
      viewport_rect_for_tile_priority_in_screen_space.Intersect(
          gfx::Rect(external_draw_constraints_.viewport_size));
      interest_rect = viewport_rect_for_tile_priority_in_screen_space.IsEmpty()
                          ? last_on_draw_global_visible_rect_
                          : viewport_rect_for_tile_priority_in_screen_space;
    }

    size_t width = interest_rect.width();
    size_t height = interest_rect.height();
    bytes_limit = kMemoryMultiplier * kBytesPerPixel * width * height;
    // Round up to a multiple of kMemoryAllocationStep.
    bytes_limit =
        (bytes_limit / kMemoryAllocationStep + 1) * kMemoryAllocationStep;
  }

  compositor_->SetMemoryPolicy(bytes_limit);
  return viewport_rect_for_tile_priority_in_view_space;
}

content::SynchronousCompositor* BrowserViewRenderer::FindCompositor(
    const viz::FrameSinkId& frame_sink_id) const {
  const auto& compositor_iterator = compositor_map_.find(frame_sink_id);
  if (compositor_iterator == compositor_map_.end())
    return nullptr;

  return compositor_iterator->second;
}

void BrowserViewRenderer::PrepareToDraw(const gfx::Point& scroll,
                                        const gfx::Rect& global_visible_rect) {
  last_on_draw_scroll_offset_ = scroll;
  last_on_draw_global_visible_rect_ = global_visible_rect;
}

bool BrowserViewRenderer::CanOnDraw() {
  if (!compositor_) {
    TRACE_EVENT_INSTANT0("android_webview", "EarlyOut_NoCompositor",
                         TRACE_EVENT_SCOPE_THREAD);
    return false;
  }
  if (clear_view_) {
    TRACE_EVENT_INSTANT0("android_webview", "EarlyOut_ClearView",
                         TRACE_EVENT_SCOPE_THREAD);
    return false;
  }

  return true;
}

bool BrowserViewRenderer::OnDrawHardware() {
  DCHECK(current_compositor_frame_consumer_);
  TRACE_EVENT0("android_webview", "BrowserViewRenderer::OnDrawHardware");

  const bool did_invalidate = did_invalidate_since_last_draw_;
  did_invalidate_since_last_draw_ = false;

  if (!CanOnDraw()) {
    return false;
  }

  current_compositor_frame_consumer_->SetScrollOffsetOnUI(
      last_on_draw_scroll_offset_);
  hardware_enabled_ = true;

  DoUpdateParentDrawData();
  // Do not override (ie leave empty) for offscreen raster.
  gfx::Size viewport_size_for_tile_priority =
      offscreen_pre_raster_ ? gfx::Size()
                            : external_draw_constraints_.viewport_size;
  gfx::Rect viewport_rect_for_tile_priority_in_view_space =
      ComputeTileRectAndUpdateMemoryPolicy();

  // Explanation for the various viewports and transforms. There are:
  // * "default" viewport" (and identity transform) that's normally used by
  //   compositor. This is |size_| in this file.
  // * "draw" viewport and transform. Compositor applies them at the root at
  //   draw time. This is contained in SkCanvas for a software draw
  // * "tile" viewport and transform. These are set in hardware draw to
  //   correctly prioritize and raster tiles.
  // The draw viewport was added to support software draw's ability to change
  // the viewport and transform at draw time to anything the embedding app
  // desires. However the tile system was not expecting its viewport to jump
  // around, and only move incrementally due to user input. This required adding
  // the tile viewport and transform. Tile and default are separate to reduce
  // memory in the case when only a small portion of webview (ie the default
  // viewport) is actually visible.
  // We intersect the tile viewport with the default viewport above so that the
  // tile viewport can only shrink and not grow from the default viewport. This
  // is because webview can also be small in relation to the surface size, so
  // and growing the tile viewport can cause more tiles to be rastered than
  // necessary.

  scoped_refptr<content::SynchronousCompositor::FrameFuture> future =
      compositor_->DemandDrawHwAsync(
          size_, viewport_rect_for_tile_priority_in_view_space,
          external_draw_constraints_.transform);
  CopyOutputRequestQueue requests;
  copy_requests_.swap(requests);
  for (auto& copy_request_ptr : requests) {
    if (!copy_request_ptr->has_result_task_runner())
      copy_request_ptr->set_result_task_runner(ui_task_runner_);
  }
  std::unique_ptr<ChildFrame> child_frame = std::make_unique<ChildFrame>(
      std::move(future), frame_sink_id_, viewport_size_for_tile_priority,
      external_draw_constraints_.transform, offscreen_pre_raster_, dip_scale_,
      std::move(requests), did_invalidate,
      begin_frame_source_->LastDispatchedBeginFrameArgs(), renderer_thread_ids_,
      browser_io_thread_id_);

  ReturnUnusedResource(
      current_compositor_frame_consumer_->SetFrameOnUI(std::move(child_frame)));
  return true;
}

void BrowserViewRenderer::OnParentDrawDataUpdated(
    CompositorFrameConsumer* compositor_frame_consumer) {
  DCHECK(compositor_frame_consumer);
  if (compositor_frame_consumer != current_compositor_frame_consumer_)
    return;
  if (!DoUpdateParentDrawData())
    return;
  PostInvalidate(compositor_);
  ComputeTileRectAndUpdateMemoryPolicy();
}

bool BrowserViewRenderer::DoUpdateParentDrawData() {
  ParentCompositorDrawConstraints new_constraints;
  viz::FrameTimingDetailsMap new_timing_details;
  viz::FrameSinkId id;
  uint32_t frame_token = 0u;
  base::TimeDelta preferred_frame_interval;
  current_compositor_frame_consumer_->TakeParentDrawDataOnUI(
      &new_constraints, &id, &new_timing_details, &frame_token,
      &preferred_frame_interval);

  content::SynchronousCompositor* compositor = FindCompositor(id);
  if (compositor) {
    compositor->DidPresentCompositorFrames(std::move(new_timing_details),
                                           frame_token);
  }

  client_->SetPreferredFrameInterval(preferred_frame_interval);

  if (external_draw_constraints_ == new_constraints)
    return false;
  external_draw_constraints_ = new_constraints;
  return true;
}

void BrowserViewRenderer::OnViewTreeForceDarkStateChanged(
    bool view_tree_force_dark_state) {
  client_->OnViewTreeForceDarkStateChanged(view_tree_force_dark_state);
}

void BrowserViewRenderer::ChildSurfaceWasEvicted() {
  if (compositor_)
    compositor_->WasEvicted();
}

void BrowserViewRenderer::RemoveCompositorFrameConsumer(
    CompositorFrameConsumer* consumer) {
  ReturnUncommittedFrames(consumer->PassUncommittedFrameOnUI());
  if (current_compositor_frame_consumer_ == consumer)
    SetCurrentCompositorFrameConsumer(nullptr);
}

void BrowserViewRenderer::ReturnUncommittedFrames(
    ChildFrameQueue child_frames) {
  for (auto& child_frame : child_frames)
    ReturnUnusedResource(std::move(child_frame));
}

void BrowserViewRenderer::ReturnUnusedResource(
    std::unique_ptr<ChildFrame> child_frame) {
  if (!child_frame.get() || !child_frame->frame.get())
    return;

  std::vector<viz::ReturnedResource> resources =
      viz::TransferableResource::ReturnResources(
          child_frame->frame->resource_list);
  content::SynchronousCompositor* compositor =
      FindCompositor(child_frame->frame_sink_id);
  if (compositor && !resources.empty())
    compositor->ReturnResources(child_frame->layer_tree_frame_sink_id,
                                std::move(resources));
}

void BrowserViewRenderer::ReturnUsedResources(
    std::vector<viz::ReturnedResource> resources,
    const viz::FrameSinkId& frame_sink_id,
    uint32_t layer_tree_frame_sink_id) {
  content::SynchronousCompositor* compositor = FindCompositor(frame_sink_id);
  if (compositor && !resources.empty())
    compositor->ReturnResources(layer_tree_frame_sink_id, std::move(resources));
  has_rendered_frame_ = true;
}

bool BrowserViewRenderer::OnDrawSoftware(SkCanvas* canvas) {
  did_invalidate_since_last_draw_ = false;
  return CanOnDraw() && CompositeSW(canvas, /*software_canvas=*/true);
}

float BrowserViewRenderer::GetVelocityInPixelsPerSecond() {
  if (!compositor_) {
    return 0.f;
  }
  return compositor_->GetVelocityInPixelsPerSecond();
}

bool BrowserViewRenderer::NeedToDrawBackgroundColor() {
  return !has_rendered_frame_;
}

sk_sp<SkPicture> BrowserViewRenderer::CapturePicture(int width,
                                                     int height) {
  TRACE_EVENT0("android_webview", "BrowserViewRenderer::CapturePicture");

  // Return empty Picture objects for empty SkPictures.
  if (width <= 0 || height <= 0) {
    SkPictureRecorder emptyRecorder;
    emptyRecorder.beginRecording(0, 0);
    return emptyRecorder.finishRecordingAsPicture();
  }

  SkPictureRecorder recorder;
  SkCanvas* rec_canvas = recorder.beginRecording(width, height);
  if (compositor_) {
    {
      // Reset scroll back to the origin, will go back to the old
      // value when scroll_reset is out of scope.
      base::AutoReset<gfx::PointF> scroll_reset(&scroll_offset_unscaled_,
                                                gfx::PointF());
      compositor_->DidChangeRootLayerScrollOffset(scroll_offset_unscaled_);
      CompositeSW(rec_canvas, /*software_canvas=*/false);
    }
    compositor_->DidChangeRootLayerScrollOffset(scroll_offset_unscaled_);
  }
  return recorder.finishRecordingAsPicture();
}

void BrowserViewRenderer::EnableOnNewPicture(bool enabled) {
  on_new_picture_enable_ = enabled;
}

void BrowserViewRenderer::ClearView() {
  TRACE_EVENT_INSTANT0("android_webview",
                       "BrowserViewRenderer::ClearView",
                       TRACE_EVENT_SCOPE_THREAD);
  if (clear_view_)
    return;

  clear_view_ = true;
  // Always invalidate ignoring the compositor to actually clear the webview.
  PostInvalidate(compositor_);
}

void BrowserViewRenderer::SetOffscreenPreRaster(bool enable) {
  if (offscreen_pre_raster_ != enable) {
    offscreen_pre_raster_ = enable;
    ComputeTileRectAndUpdateMemoryPolicy();
  }
}

void BrowserViewRenderer::SetIsPaused(bool paused) {
  TRACE_EVENT_INSTANT1("android_webview",
                       "BrowserViewRenderer::SetIsPaused",
                       TRACE_EVENT_SCOPE_THREAD,
                       "paused",
                       paused);
  is_paused_ = paused;
  UpdateBeginFrameSource();
}

void BrowserViewRenderer::SetViewVisibility(bool view_visible) {
  TRACE_EVENT_INSTANT1("android_webview",
                       "BrowserViewRenderer::SetViewVisibility",
                       TRACE_EVENT_SCOPE_THREAD,
                       "view_visible",
                       view_visible);
  view_visible_ = view_visible;
}

void BrowserViewRenderer::SetWindowVisibility(bool window_visible) {
  TRACE_EVENT_INSTANT1("android_webview",
                       "BrowserViewRenderer::SetWindowVisibility",
                       TRACE_EVENT_SCOPE_THREAD,
                       "window_visible",
                       window_visible);
  window_visible_ = window_visible;
  UpdateBeginFrameSource();
  UpdateForegroundForGpuResources();
}

void BrowserViewRenderer::OnSizeChanged(int width, int height) {
  TRACE_EVENT_INSTANT2("android_webview",
                       "BrowserViewRenderer::OnSizeChanged",
                       TRACE_EVENT_SCOPE_THREAD,
                       "width",
                       width,
                       "height",
                       height);
  size_.SetSize(width, height);
  if (offscreen_pre_raster_)
    ComputeTileRectAndUpdateMemoryPolicy();
}

void BrowserViewRenderer::OnAttachedToWindow(int width, int height) {
  TRACE_EVENT2("android_webview",
               "BrowserViewRenderer::OnAttachedToWindow",
               "width",
               width,
               "height",
               height);
  attached_to_window_ = true;
  was_attached_ = true;

  size_.SetSize(width, height);
  if (offscreen_pre_raster_)
    ComputeTileRectAndUpdateMemoryPolicy();
  UpdateBeginFrameSource();
  UpdateForegroundForGpuResources();
}

void BrowserViewRenderer::OnDetachedFromWindow() {
  TRACE_EVENT0("android_webview", "BrowserViewRenderer::OnDetachedFromWindow");
  attached_to_window_ = false;
  ReleaseHardware();
  UpdateBeginFrameSource();
  UpdateForegroundForGpuResources();
}

void BrowserViewRenderer::ZoomBy(float delta) {
  if (!compositor_)
    return;
  compositor_->SynchronouslyZoomBy(
      delta, gfx::Point(size_.width() / 2, size_.height() / 2));
}

void BrowserViewRenderer::OnComputeScroll(base::TimeTicks animation_time) {
  if (!compositor_)
    return;
  TRACE_EVENT0("android_webview", "BrowserViewRenderer::OnComputeScroll");
  compositor_->OnComputeScroll(animation_time);
}

void BrowserViewRenderer::ReleaseHardware() {
  if (current_compositor_frame_consumer_) {
    ReturnUncommittedFrames(
        current_compositor_frame_consumer_->PassUncommittedFrameOnUI());
  }
  hardware_enabled_ = false;
  has_rendered_frame_ = false;
  ComputeTileRectAndUpdateMemoryPolicy();
}

bool BrowserViewRenderer::IsVisible() const {
  // Ignore |window_visible_| if |attached_to_window_| is false.
  return view_visible_ && (!attached_to_window_ || window_visible_);
}

bool BrowserViewRenderer::IsClientVisible() const {
  // When WebView is not paused, we declare it visible even before it is
  // attached to window to allow for background operations. If it ever gets
  // attached though, the WebView is visible as long as it is attached
  // to a window and the window is visible.
  return is_paused_
             ? false
             : !was_attached_ || (attached_to_window_ && window_visible_);
}

void BrowserViewRenderer::UpdateBeginFrameSource() {
  if (IsClientVisible()) {
    begin_frame_source_->SetParentSource(
        RootBeginFrameSourceWebView::GetInstance());
  } else {
    begin_frame_source_->SetParentSource(nullptr);
  }
}

void BrowserViewRenderer::UpdateForegroundForGpuResources() {
  bool foreground = attached_to_window_ && window_visible_;
  if (foreground != foreground_for_gpu_resources_) {
    foreground_for_gpu_resources_ = foreground;
    if (!compositor_) {
      return;
    }
    if (foreground_for_gpu_resources_) {
      compositor_->OnCompositorVisible();
    } else {
      compositor_->OnCompositorHidden();
    }
  }
}

gfx::Rect BrowserViewRenderer::GetScreenRect() const {
  return gfx::Rect(client_->GetLocationOnScreen(), size_);
}

void BrowserViewRenderer::DidInitializeCompositor(
    content::SynchronousCompositor* compositor,
    const viz::FrameSinkId& frame_sink_id) {
  TRACE_EVENT_INSTANT0("android_webview",
                       "BrowserViewRenderer::DidInitializeCompositor",
                       TRACE_EVENT_SCOPE_THREAD);
  DCHECK(compositor);
  // This assumes that a RenderViewHost has at most 1 synchronous compositor
  // througout its lifetime.
  DCHECK(compositor_map_.count(frame_sink_id) == 0);
  compositor_map_[frame_sink_id] = compositor;
  if (root_frame_sink_proxy_)
    root_frame_sink_proxy_->AddChildFrameSinkId(frame_sink_id);

  compositor->SetBeginFrameSource(begin_frame_source_.get());

  // At this point, the RVHChanged event for the new RVH that contains the
  // |compositor| might have been fired already, in which case just set the
  // current compositor with the new compositor.
  if (frame_sink_id == frame_sink_id_)
    SetActiveCompositor(compositor);
}

void BrowserViewRenderer::DidDestroyCompositor(
    content::SynchronousCompositor* compositor,
    const viz::FrameSinkId& frame_sink_id) {
  TRACE_EVENT_INSTANT0("android_webview",
                       "BrowserViewRenderer::DidDestroyCompositor",
                       TRACE_EVENT_SCOPE_THREAD);
  DCHECK(compositor_map_.count(frame_sink_id));
  if (compositor_ == compositor) {
    if (compositor_ && foreground_for_gpu_resources_) {
      compositor_->OnCompositorHidden();
    }
    compositor_ = nullptr;
    copy_requests_.clear();
  }

  if (root_frame_sink_proxy_)
    root_frame_sink_proxy_->RemoveChildFrameSinkId(frame_sink_id);
  compositor_map_.erase(frame_sink_id);
}

void BrowserViewRenderer::SetActiveFrameSinkId(
    const viz::FrameSinkId& frame_sink_id) {
  frame_sink_id_ = frame_sink_id;
  SetActiveCompositor(FindCompositor(frame_sink_id));
}

void BrowserViewRenderer::SetActiveCompositor(
    content::SynchronousCompositor* compositor) {
  if (compositor_ == compositor)
    return;

  content::SynchronousCompositor* existing_compositor = compositor_;
  if (existing_compositor) {
    existing_compositor->SetMemoryPolicy(0u);
  }
  compositor_ = compositor;
  copy_requests_.clear();
  if (compositor_) {
    ComputeTileRectAndUpdateMemoryPolicy();
    compositor_->DidBecomeActive();
  }
  if (foreground_for_gpu_resources_) {
    if (compositor_) {
      compositor_->OnCompositorVisible();
    }
    if (existing_compositor) {
      existing_compositor->OnCompositorHidden();
    }
  }
}

void BrowserViewRenderer::SetDipScale(float dip_scale) {
  dip_scale_ = dip_scale;
  CHECK_GT(dip_scale_, 0.f);
}

gfx::Point BrowserViewRenderer::max_scroll_offset() const {
  DCHECK_GT(dip_scale_, 0.f);
  return gfx::ToCeiledPoint(
      gfx::ScalePoint(max_scroll_offset_unscaled_, page_scale_factor_));
}

void BrowserViewRenderer::ScrollTo(const gfx::Point& scroll_offset) {
  gfx::Point max_offset = max_scroll_offset();
  gfx::PointF scroll_offset_unscaled;
  // To preserve the invariant that scrolling to the maximum physical pixel
  // value also scrolls to the maximum dip pixel value we transform the physical
  // offset into the dip offset by using a proportion (instead of dividing by
  // dip_scale * page_scale_factor).
  if (max_offset.x()) {
    scroll_offset_unscaled.set_x(
        (scroll_offset.x() * max_scroll_offset_unscaled_.x()) / max_offset.x());
  }
  if (max_offset.y()) {
    scroll_offset_unscaled.set_y(
        (scroll_offset.y() * max_scroll_offset_unscaled_.y()) / max_offset.y());
  }

  DCHECK_LE(0.f, scroll_offset_unscaled.x());
  DCHECK_LE(0.f, scroll_offset_unscaled.y());
  DCHECK(scroll_offset_unscaled.x() < max_scroll_offset_unscaled_.x() ||
         scroll_offset_unscaled.x() - max_scroll_offset_unscaled_.x() <
             kEpsilon)
      << scroll_offset_unscaled.x() << " " << max_scroll_offset_unscaled_.x();
  DCHECK(scroll_offset_unscaled.y() < max_scroll_offset_unscaled_.y() ||
         scroll_offset_unscaled.y() - max_scroll_offset_unscaled_.y() <
             kEpsilon)
      << scroll_offset_unscaled.y() << " " << max_scroll_offset_unscaled_.y();

  if (scroll_offset_unscaled_ == scroll_offset_unscaled)
    return;

  scroll_offset_unscaled_ = scroll_offset_unscaled;

  TRACE_EVENT_INSTANT2("android_webview", "BrowserViewRenderer::ScrollTo",
                       TRACE_EVENT_SCOPE_THREAD, "x",
                       scroll_offset_unscaled.x(), "y",
                       scroll_offset_unscaled.y());

  if (compositor_)
    compositor_->DidChangeRootLayerScrollOffset(scroll_offset_unscaled);
}

void BrowserViewRenderer::RestoreScrollAfterTransition(
    const gfx::Point& scroll_offset) {
  // Determine if the clipped scroll offset.
  gfx::Point clipped_offset = scroll_offset;
  clipped_offset.SetToMin(max_scroll_offset());

  // If the scroll will be clipped due to the max scroll then we haven't
  // received a ScrollStateUpdate with the revised max scroll values. This
  // situation occurs due to a race in exiting fullscreen mode request, we need
  // to wait for the max scroll values to be updated before applying the
  // restored scroll values.
  if (clipped_offset != scroll_offset) {
    scroll_on_scroll_state_update_ = scroll_offset;
  } else {
    ScrollTo(scroll_offset);
  }
}

void BrowserViewRenderer::DidUpdateContent(
    content::SynchronousCompositor* compositor) {
  TRACE_EVENT_INSTANT0("android_webview",
                       "BrowserViewRenderer::DidUpdateContent",
                       TRACE_EVENT_SCOPE_THREAD);
  if (compositor != compositor_)
    return;

  clear_view_ = false;
  if (on_new_picture_enable_)
    client_->OnNewPicture();
}

void BrowserViewRenderer::SetTotalRootLayerScrollOffset(
    const gfx::PointF& scroll_offset_unscaled) {
  if (scroll_offset_unscaled_ == scroll_offset_unscaled)
    return;
  scroll_offset_unscaled_ = scroll_offset_unscaled;

  gfx::Point max_offset = max_scroll_offset();
  gfx::Point scroll_offset;
  // For an explanation as to why this is done this way see the comment in
  // BrowserViewRenderer::ScrollTo.
  if (max_scroll_offset_unscaled_.x()) {
    scroll_offset.set_x(
        base::ClampRound((scroll_offset_unscaled.x() * max_offset.x()) /
                         max_scroll_offset_unscaled_.x()));
  }

  if (max_scroll_offset_unscaled_.y()) {
    scroll_offset.set_y(
        base::ClampRound((scroll_offset_unscaled.y() * max_offset.y()) /
                         max_scroll_offset_unscaled_.y()));
  }

  DCHECK_LE(0, scroll_offset.x());
  DCHECK_LE(0, scroll_offset.y());
  DCHECK_LE(scroll_offset.x(), max_offset.x());
  DCHECK_LE(scroll_offset.y(), max_offset.y());

  client_->ScrollContainerViewTo(scroll_offset);
}

void BrowserViewRenderer::UpdateRootLayerState(
    content::SynchronousCompositor* compositor,
    const gfx::PointF& total_scroll_offset,
    const gfx::PointF& total_max_scroll_offset,
    const gfx::SizeF& scrollable_size,
    float page_scale_factor,
    float min_page_scale_factor,
    float max_page_scale_factor) {
  if (compositor != compositor_)
    return;

  gfx::SizeF scrollable_size_dip = scrollable_size;
  scrollable_size_dip.Scale(1 / dip_scale_);

  TRACE_EVENT_INSTANT1(
      "android_webview", "BrowserViewRenderer::UpdateRootLayerState",
      TRACE_EVENT_SCOPE_THREAD, "state",
      RootLayerStateAsValue(total_scroll_offset, scrollable_size_dip));

  DCHECK_GE(total_max_scroll_offset.x(), 0.f);
  DCHECK_GE(total_max_scroll_offset.y(), 0.f);
  DCHECK_GT(page_scale_factor, 0.f);
  // SetDipScale should have been called at least once before this is called.
  DCHECK_GT(dip_scale_, 0.f);

  bool apply_scroll_to = false;
  if (max_scroll_offset_unscaled_ != total_max_scroll_offset ||
      scrollable_size_dip_ != scrollable_size_dip ||
      page_scale_factor_ != page_scale_factor ||
      min_page_scale_factor_ != min_page_scale_factor ||
      max_page_scale_factor_ != max_page_scale_factor) {
    max_scroll_offset_unscaled_ = total_max_scroll_offset;
    scrollable_size_dip_ = scrollable_size_dip;
    page_scale_factor_ = page_scale_factor;
    min_page_scale_factor_ = min_page_scale_factor;
    max_page_scale_factor_ = max_page_scale_factor;

    client_->UpdateScrollState(max_scroll_offset(), scrollable_size_dip,
                               page_scale_factor, min_page_scale_factor,
                               max_page_scale_factor);
    apply_scroll_to = scroll_on_scroll_state_update_.has_value();
  }

  SetTotalRootLayerScrollOffset(total_scroll_offset);

  if (apply_scroll_to) {
    ScrollTo(scroll_on_scroll_state_update_.value());
    scroll_on_scroll_state_update_.reset();
  }
}

std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
BrowserViewRenderer::RootLayerStateAsValue(
    const gfx::PointF& total_scroll_offset,
    const gfx::SizeF& scrollable_size_dip) {
  std::unique_ptr<base::trace_event::TracedValue> state(
      new base::trace_event::TracedValue());

  state->SetDouble("total_scroll_offset.x", total_scroll_offset.x());
  state->SetDouble("total_scroll_offset.y", total_scroll_offset.y());

  state->SetDouble("max_scroll_offset_unscaled.x",
                   max_scroll_offset_unscaled_.x());
  state->SetDouble("max_scroll_offset_unscaled.y",
                   max_scroll_offset_unscaled_.y());

  state->SetDouble("scrollable_size_dip.width", scrollable_size_dip.width());
  state->SetDouble("scrollable_size_dip.height", scrollable_size_dip.height());

  state->SetDouble("page_scale_factor", page_scale_factor_);
  return std::move(state);
}

void BrowserViewRenderer::DidOverscroll(
    content::SynchronousCompositor* compositor,
    const gfx::Vector2dF& accumulated_overscroll,
    const gfx::Vector2dF& latest_overscroll_delta,
    const gfx::Vector2dF& current_fling_velocity) {
  if (compositor != compositor_)
    return;

  const float physical_pixel_scale = dip_scale_ * page_scale_factor_;
  if (accumulated_overscroll == latest_overscroll_delta)
    overscroll_rounding_error_ = gfx::Vector2dF();
  gfx::Vector2dF scaled_overscroll_delta =
      gfx::ScaleVector2d(latest_overscroll_delta, physical_pixel_scale);
  gfx::Vector2d rounded_overscroll_delta = gfx::ToRoundedVector2d(
      scaled_overscroll_delta + overscroll_rounding_error_);
  overscroll_rounding_error_ =
      scaled_overscroll_delta - rounded_overscroll_delta;
  gfx::Vector2dF fling_velocity_pixels =
      gfx::ScaleVector2d(current_fling_velocity, physical_pixel_scale);

  client_->DidOverscroll(rounded_overscroll_delta, fling_velocity_pixels,
                         begin_frame_source_->inside_begin_frame());
}

ui::TouchHandleDrawable* BrowserViewRenderer::CreateDrawable() {
  return client_->CreateDrawable();
}

void BrowserViewRenderer::CopyOutput(
    content::SynchronousCompositor* compositor,
    std::unique_ptr<viz::CopyOutputRequest> copy_request) {
  if (compositor != compositor_ || !hardware_enabled_)
    return;
  copy_requests_.emplace_back(std::move(copy_request));
  PostInvalidate(compositor_);
}

void BrowserViewRenderer::Invalidate() {
  if (compositor_)
    compositor_->DidInvalidate();
  PostInvalidate(compositor_);
}

void BrowserViewRenderer::ReturnResourcesFromViz(
    viz::FrameSinkId frame_sink_id,
    uint32_t layer_tree_frame_sink_id,
    std::vector<viz::ReturnedResource> resources) {
  ReturnUsedResources(std::move(resources), frame_sink_id,
                      layer_tree_frame_sink_id);
}

void BrowserViewRenderer::OnCompositorFrameTransitionDirectiveProcessed(
    viz::FrameSinkId frame_sink_id,
    uint32_t layer_tree_frame_sink_id,
    uint32_t sequence_id) {
  content::SynchronousCompositor* compositor = FindCompositor(frame_sink_id);
  if (compositor) {
    compositor->OnCompositorFrameTransitionDirectiveProcessed(
        layer_tree_frame_sink_id, sequence_id);
  }
}

void BrowserViewRenderer::OnInputEvent() {
  if (root_frame_sink_proxy_)
    root_frame_sink_proxy_->OnInputEvent();
}

void BrowserViewRenderer::AddBeginFrameCompletionCallback(
    base::OnceClosure callback) {
  begin_frame_source_->AddBeginFrameCompletionCallback(std::move(callback));
}

void BrowserViewRenderer::SetThreadIds(const std::vector<int32_t>& thread_ids) {
  renderer_thread_ids_ = thread_ids;
}

void BrowserViewRenderer::PostInvalidate(
    content::SynchronousCompositor* compositor) {
  TRACE_EVENT_INSTANT0("android_webview", "BrowserViewRenderer::PostInvalidate",
                       TRACE_EVENT_SCOPE_THREAD);
  if (compositor != compositor_)
    return;

  did_invalidate_since_last_draw_ = true;
  client_->PostInvalidate(
      RootBeginFrameSourceWebView::GetInstance()->inside_begin_frame());
}

bool BrowserViewRenderer::CompositeSW(SkCanvas* canvas, bool software_canvas) {
  DCHECK(compositor_);
  return compositor_->DemandDrawSw(canvas, software_canvas);
}

std::string BrowserViewRenderer::ToString() const {
  std::string str;
  base::StringAppendF(&str, "is_paused: %d ", is_paused_);
  base::StringAppendF(&str, "view_visible: %d ", view_visible_);
  base::StringAppendF(&str, "window_visible: %d ", window_visible_);
  base::StringAppendF(&str, "dip_scale: %f ", dip_scale_);
  base::StringAppendF(&str, "page_scale_factor: %f ", page_scale_factor_);
  base::StringAppendF(&str, "view size: %s ", size_.ToString().c_str());
  base::StringAppendF(&str, "attached_to_window: %d ", attached_to_window_);
  base::StringAppendF(&str,
                      "global visible rect: %s ",
                      last_on_draw_global_visible_rect_.ToString().c_str());
  base::StringAppendF(&str, "scroll_offset_unscaled: %s ",
                      scroll_offset_unscaled_.ToString().c_str());
  base::StringAppendF(&str,
                      "overscroll_rounding_error_: %s ",
                      overscroll_rounding_error_.ToString().c_str());
  base::StringAppendF(
      &str, "on_new_picture_enable: %d ", on_new_picture_enable_);
  base::StringAppendF(&str, "clear_view: %d ", clear_view_);
  return str;
}

void BrowserViewRenderer::SetBrowserIOThreadId(
    base::PlatformThreadId thread_id) {
  browser_io_thread_id_ = thread_id;
}

}  // namespace android_webview