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

#include "components/viz/service/layers/layer_context_impl.h"

#include <algorithm>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/traced_value.h"
#include "base/trace_event/typed_macros.h"
#include "base/types/expected_macros.h"
#include "cc/animation/animation.h"
#include "cc/animation/animation_host.h"
#include "cc/animation/animation_timeline.h"
#include "cc/animation/keyframe_effect.h"
#include "cc/base/features.h"
#include "cc/debug/layer_tree_debug_state.h"
#include "cc/debug/rendering_stats_instrumentation.h"
#include "cc/input/browser_controls_offset_manager.h"
#include "cc/layers/layer_impl.h"
#include "cc/layers/mirror_layer_impl.h"
#include "cc/layers/nine_patch_layer_impl.h"
#include "cc/layers/nine_patch_thumb_scrollbar_layer_impl.h"
#include "cc/layers/painted_scrollbar_layer_impl.h"
#include "cc/layers/solid_color_layer_impl.h"
#include "cc/layers/solid_color_scrollbar_layer_impl.h"
#include "cc/layers/surface_layer_impl.h"
#include "cc/layers/texture_layer_impl.h"
#include "cc/layers/tile_display_layer_impl.h"
#include "cc/layers/ui_resource_layer_impl.h"
#include "cc/layers/view_transition_content_layer_impl.h"
#include "cc/trees/latency_info_swap_promise.h"
#include "cc/trees/layer_tree_host_impl.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/layer_tree_settings.h"
#include "cc/trees/property_tree.h"
#include "cc/trees/task_runner_provider.h"
#include "cc/trees/tree_synchronizer.h"
#include "components/viz/client/client_resource_provider.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/service/layers/viz_layer_tree_host_impl.h"
#include "ui/gfx/animation/keyframe/keyframed_animation_curve.h"

namespace viz {

namespace {

#define RETURN_IF_FALSE(expr, error)  \
  do {                                \
    if (!(expr)) {                    \
      return base::unexpected(error); \
    }                                 \
  } while (false)

int GenerateNextDisplayTreeId() {
  static int next_id = 1;
  return next_id++;
}

cc::LayerTreeSettings GetDisplayTreeSettings(
    mojom::LayerContextSettingsPtr remote_settings) {
  cc::LayerTreeSettings settings;
  settings.use_layer_lists = true;
  settings.trees_in_viz_in_viz_process = true;
  settings.display_tree_draw_mode_is_gpu = remote_settings->draw_mode_is_gpu;
  settings.enable_early_damage_check =
      remote_settings->enable_early_damage_check;
  settings.damaged_frame_limit = remote_settings->damaged_frame_limit;
  settings.scrollbar_animator = remote_settings->scrollbar_animator;
  settings.scrollbar_fade_delay = remote_settings->scrollbar_fade_delay;
  settings.scrollbar_fade_duration = remote_settings->scrollbar_fade_duration;
  settings.scrollbar_thinning_duration =
      remote_settings->scrollbar_thinning_duration;
  settings.idle_thickness_scale = remote_settings->idle_thickness_scale;
  settings.top_controls_show_threshold =
      remote_settings->top_controls_show_threshold;
  settings.top_controls_hide_threshold =
      remote_settings->top_controls_hide_threshold;
  settings.minimum_occlusion_tracking_size =
      remote_settings->minimum_occlusion_tracking_size;
  settings.enable_edge_anti_aliasing =
      remote_settings->enable_edge_anti_aliasing;
  settings.enable_backface_visibility_interop =
      remote_settings->enable_backface_visibility_interop;
  settings.enable_fluent_scrollbar = remote_settings->enable_fluent_scrollbar;
  settings.enable_fluent_overlay_scrollbar =
      remote_settings->enable_fluent_overlay_scrollbar;
  settings.enable_unbounded_element = remote_settings->enable_unbounded_element;
  return settings;
}

base::expected<void, std::string> CreateLayer(
    cc::LayerTreeHostImpl& host_impl,
    cc::LayerTreeImpl& tree,
    const mojom::Layer& wire,
    std::unique_ptr<cc::LayerImpl>& layer) {
  cc::mojom::LayerType type = wire.type;
  int id = wire.id;
  switch (type) {
    case cc::mojom::LayerType::kLayer:
      layer = cc::LayerImpl::Create(&tree, id);
      break;

    case cc::mojom::LayerType::kMirror:
      layer = cc::MirrorLayerImpl::Create(&tree, id);
      break;

    case cc::mojom::LayerType::kNinePatch:
      layer = cc::NinePatchLayerImpl::Create(&tree, id);
      break;

    case cc::mojom::LayerType::kNinePatchThumbScrollbar: {
      RETURN_IF_FALSE(wire.general_properties &&
                          wire.general_properties->layer_extra &&
                          wire.general_properties->layer_extra
                              ->is_nine_patch_thumb_scrollbar_layer_extra(),
                      "Invalid layer_extra type for "
                      "NinePatchThumbScrollbarLayerImpl");
      auto& extra = wire.general_properties->layer_extra
                        ->get_nine_patch_thumb_scrollbar_layer_extra();
      cc::ScrollbarOrientation orientation =
          extra->scrollbar_base_extra->is_horizontal_orientation
              ? cc::ScrollbarOrientation::kHorizontal
              : cc::ScrollbarOrientation::kVertical;
      layer = cc::NinePatchThumbScrollbarLayerImpl::Create(
          &tree, id, orientation,
          extra->scrollbar_base_extra->is_left_side_vertical_scrollbar);
      break;
    }

    case cc::mojom::LayerType::kPaintedScrollbar: {
      RETURN_IF_FALSE(wire.general_properties &&
                          wire.general_properties->layer_extra &&
                          wire.general_properties->layer_extra
                              ->is_painted_scrollbar_layer_extra(),
                      "Invalid layer_extra type for PaintedScrollbarLayerImpl");
      auto& extra = wire.general_properties->layer_extra
                        ->get_painted_scrollbar_layer_extra();
      cc::ScrollbarOrientation orientation =
          extra->scrollbar_base_extra->is_horizontal_orientation
              ? cc::ScrollbarOrientation::kHorizontal
              : cc::ScrollbarOrientation::kVertical;
      layer = cc::PaintedScrollbarLayerImpl::Create(
          &tree, id, orientation,
          extra->scrollbar_base_extra->is_left_side_vertical_scrollbar,
          extra->scrollbar_base_extra->is_overlay_scrollbar);
      break;
    }

    case cc::mojom::LayerType::kTileDisplay:
      layer = std::make_unique<cc::TileDisplayLayerImpl>(tree, id);
      break;

    case cc::mojom::LayerType::kSolidColorScrollbar: {
      RETURN_IF_FALSE(wire.general_properties &&
                          wire.general_properties->layer_extra &&
                          wire.general_properties->layer_extra
                              ->is_solid_color_scrollbar_layer_extra(),
                      "Invalid layer_extra type for "
                      "SolidColorScrollbarLayerImpl");
      auto& extra = wire.general_properties->layer_extra
                        ->get_solid_color_scrollbar_layer_extra();
      cc::ScrollbarOrientation orientation =
          extra->scrollbar_base_extra->is_horizontal_orientation
              ? cc::ScrollbarOrientation::kHorizontal
              : cc::ScrollbarOrientation::kVertical;
      layer = cc::SolidColorScrollbarLayerImpl::Create(
          &tree, id, orientation, extra->thumb_thickness, extra->track_start,
          extra->scrollbar_base_extra->is_left_side_vertical_scrollbar);
      break;
    }

    case cc::mojom::LayerType::kSurface:
      // The callback is triggered in the renderer side during WillDraw(),
      // and there is no need to do it in viz.
      layer = cc::SurfaceLayerImpl::Create(&tree, id, base::NullCallback());
      break;

    case cc::mojom::LayerType::kSolidColor:
      layer = cc::SolidColorLayerImpl::Create(&tree, id);
      break;

    case cc::mojom::LayerType::kTexture:
      layer = cc::TextureLayerImpl::Create(&tree, id);
      break;

    case cc::mojom::LayerType::kUIResource:
      layer = cc::UIResourceLayerImpl::Create(&tree, id);
      break;

    case cc::mojom::LayerType::kViewTransitionContent: {
      RETURN_IF_FALSE(wire.general_properties &&
                          wire.general_properties->layer_extra &&
                          wire.general_properties->layer_extra
                              ->is_view_transition_content_layer_extra(),
                      "Invalid layer_extra type for "
                      "ViewTransitionContentLayerImpl");
      auto& extra = wire.general_properties->layer_extra
                        ->get_view_transition_content_layer_extra();
      layer = cc::ViewTransitionContentLayerImpl::Create(
          &tree, id, extra->resource_id, extra->is_live_content_layer,
          extra->max_extents_rect);
      break;
    }

    case cc::mojom::LayerType::kHeadsUpDisplay:
    case cc::mojom::LayerType::kPicture:
    case cc::mojom::LayerType::kVideo:
      return base::unexpected("Invalid LayerType for CreateLayer.");
  }
  return base::ok();
}

template <typename TreeType>
bool IsPropertyTreeIndexValid(const TreeType& tree, int32_t index) {
  return index >= 0 && index < tree.next_available_id();
}

template <typename TreeType>
bool IsOptionalPropertyTreeIndexValid(const TreeType& tree, int32_t index) {
  return index == cc::kInvalidPropertyNodeId ||
         IsPropertyTreeIndexValid(tree, index);
}

base::expected<void, std::string> UpdatePropertyTreeNode(
    cc::PropertyTrees& trees,
    cc::TransformNode& node,
    const mojom::TransformNode& wire) {
  if (wire.id == cc::kSecondaryRootPropertyNodeId &&
      wire.parent_id == cc::kInvalidPropertyNodeId) {
    return base::unexpected(
        "Invalid parent_id for non-root property tree node");
  }

  auto& tree = trees.transform_tree_mutable();
  if (!IsOptionalPropertyTreeIndexValid(tree, wire.parent_frame_id)) {
    return base::unexpected("Invalid parent_frame_id");
  }
  node.parent_frame_id = wire.parent_frame_id;
  node.element_id = wire.element_id;
  if (node.element_id) {
    tree.SetElementIdForNodeId(node.id, node.element_id);
  }
  if (node.local != wire.local || node.origin != wire.origin ||
      node.scroll_offset() != wire.scroll_offset) {
    node.needs_local_transform_update = true;
  }
  node.local = wire.local;
  node.origin = wire.origin;
  node.post_translation = wire.post_translation;
  node.set_to_parent(wire.to_parent);
  node.SetScrollOffset(wire.scroll_offset, cc::DamageReason::kUntracked);
  node.snap_amount = wire.snap_amount;

  if (!wire.sticky_position_constraint_id) {
    node.sticky_position_constraint_id = -1;
  } else if (*wire.sticky_position_constraint_id >=
             tree.sticky_position_data().size()) {
    return base::unexpected("Invalid sticky_position_constraint_id");
  } else {
    node.sticky_position_constraint_id =
        base::saturated_cast<int>(*wire.sticky_position_constraint_id);
  }

  if (!wire.anchor_position_scroll_data_id) {
    node.anchor_position_scroll_data_id = -1;
  } else if (*wire.anchor_position_scroll_data_id >=
             tree.anchor_position_scroll_data().size()) {
    return base::unexpected("Invalid anchor_position_scroll_data_id");
  } else {
    node.anchor_position_scroll_data_id =
        base::saturated_cast<int>(*wire.anchor_position_scroll_data_id);
  }

  node.sorting_context_id = wire.sorting_context_id;

  node.has_potential_animation = wire.has_potential_animation;
  node.is_currently_animating = wire.is_currently_animating;
  node.flattens_inherited_transform = wire.flattens_inherited_transform;
  node.scrolls = wire.scrolls;
  node.should_undo_overscroll = wire.should_undo_overscroll;
  node.should_be_snapped = wire.should_be_snapped;
  node.moved_by_outer_viewport_bounds_delta_y =
      wire.moved_by_outer_viewport_bounds_delta_y;
  node.in_subtree_of_page_scale_layer = wire.in_subtree_of_page_scale_layer;
  node.delegates_to_parent_for_backface = wire.delegates_to_parent_for_backface;
  node.will_change_transform = wire.will_change_transform;
  node.maximum_animation_scale = wire.maximum_animation_scale;
  node.node_and_ancestors_are_animated_or_invertible =
      wire.node_and_ancestors_are_animated_or_invertible;
  node.is_invertible = wire.is_invertible;
  node.ancestors_are_invertible = wire.ancestors_are_invertible;
  node.node_and_ancestors_are_flat = wire.node_and_ancestors_are_flat;
  node.node_or_ancestors_will_change_transform =
      wire.node_or_ancestors_will_change_transform;

  node.visible_frame_element_id = wire.visible_frame_element_id;

  // Note that we only set |transform_changed| to true and never to false since
  // Viz might not have got a change to run and use it via
  // CalculateRenderProperties(). It should only be cleared in
  // ResetAllChangeTracking() which happens at the end of every frame.
  if (wire.transform_changed) {
    node.SetTransformChanged(cc::DamageReason::kUntracked);
  }
  if (!node.SetDamageReasonsForDeserialization(
          cc::DamageReasonSet::FromEnumBitmask(wire.damage_reasons_bit_mask))) {
    // This error case shouldn't be reachable, since
    // DamageReasonSet::FromEnumBitmask should already ignore any bits outside
    // of the set's range.
    return base::unexpected("Invalid damage_reasons_bit_mask");
  }
  node.moved_by_safe_area_bottom = wire.moved_by_safe_area_bottom;
  return base::ok();
}

base::expected<void, std::string> UpdatePropertyTreeNode(
    cc::PropertyTrees& trees,
    cc::ClipNode& node,
    const mojom::ClipNode& wire) {
  if (!IsPropertyTreeIndexValid(trees.transform_tree(), wire.transform_id) ||
      !IsOptionalPropertyTreeIndexValid(trees.effect_tree(),
                                        wire.pixel_moving_filter_id)) {
    return base::unexpected("Invalid transform_id for clip node");
  }
  node.transform_id = wire.transform_id;
  node.clip = wire.clip;
  node.pixel_moving_filter_id = wire.pixel_moving_filter_id;
  return base::ok();
}

base::expected<void, std::string> UpdatePropertyTreeNode(
    cc::PropertyTrees& trees,
    cc::EffectNode& node,
    const mojom::EffectNode& wire) {
  if (!IsPropertyTreeIndexValid(trees.transform_tree(), wire.transform_id)) {
    return base::unexpected("Invalid transform_id for effect node");
  }
  if (!IsPropertyTreeIndexValid(trees.clip_tree(), wire.clip_id)) {
    return base::unexpected("Invalid clip_id for effect node");
  }
  if (!IsPropertyTreeIndexValid(trees.effect_tree(), wire.target_id)) {
    return base::unexpected("Invalid target_id for effect node");
  }
  if (!IsOptionalPropertyTreeIndexValid(
          trees.effect_tree(),
          wire.closest_ancestor_with_cached_render_surface_id)) {
    return base::unexpected(
        "Invalid closest_ancestor_with_cached_render_surface_id for effect "
        "node");
  }
  if (!IsOptionalPropertyTreeIndexValid(
          trees.effect_tree(), wire.closest_ancestor_with_copy_request_id)) {
    return base::unexpected(
        "Invalid closest_ancestor_with_copy_request_id for effect node");
  }
  if (!IsOptionalPropertyTreeIndexValid(
          trees.effect_tree(), wire.closest_ancestor_being_captured_id)) {
    return base::unexpected(
        "Invalid closest_ancestor_being_captured_id for effect node");
  }
  if (!IsOptionalPropertyTreeIndexValid(
          trees.effect_tree(), wire.closest_ancestor_with_shared_element_id)) {
    return base::unexpected(
        "Invalid closest_ancestor_with_shared_element_id for effect node");
  }
  if (!IsOptionalPropertyTreeIndexValid(trees.effect_tree(),
                                        wire.view_transition_target_id)) {
    return base::unexpected(
        "Invalid view_transition_target_id for effect node");
  }
  if (wire.render_surface_reason != cc::RenderSurfaceReason::kNone &&
      !wire.element_id) {
    return base::unexpected(
        "Effect node with render surface must have a valid element_id");
  }
  node.transform_id = wire.transform_id;
  node.clip_id = wire.clip_id;
  node.element_id = wire.element_id;
  if (node.element_id) {
    trees.effect_tree_mutable().SetElementIdForNodeId(node.id, node.element_id);
  }
  node.opacity = wire.opacity;

  // Note that we only set |effect_changed| to true and never to false since Viz
  // might not have got a change to run and use it via
  // CalculateRenderProperties(). It should only be cleared in
  // ResetAllChangeTracking() which happens at the end of every frame.
  if (wire.effect_changed) {
    node.effect_changed = true;
  }
  node.render_surface_reason = wire.render_surface_reason;
  node.surface_contents_scale = wire.surface_contents_scale;
  node.subtree_capture_id = wire.subtree_capture_id;
  node.subtree_size = wire.subtree_size;
  node.blend_mode = wire.blend_mode;
  node.target_id = wire.target_id;
  node.view_transition_target_id = wire.view_transition_target_id;
  node.closest_ancestor_with_cached_render_surface_id =
      wire.closest_ancestor_with_cached_render_surface_id;
  node.closest_ancestor_with_copy_request_id =
      wire.closest_ancestor_with_copy_request_id;
  node.closest_ancestor_being_captured_id =
      wire.closest_ancestor_being_captured_id;
  node.closest_ancestor_with_shared_element_id =
      wire.closest_ancestor_with_shared_element_id;
  node.view_transition_element_resource_id =
      wire.view_transition_element_resource_id;
  node.filters = wire.filters;
  node.backdrop_filters = wire.backdrop_filters;
  node.backdrop_filter_bounds = wire.backdrop_filter_bounds;
  if (wire.backdrop_filter_quality <= 0.0f ||
      wire.backdrop_filter_quality > 1.0f ||
      !std::isfinite(wire.backdrop_filter_quality)) {
    return base::unexpected("Invalid backdrop_filter_quality");
  }
  node.backdrop_filter_quality = wire.backdrop_filter_quality;
  node.backdrop_mask_element_id = wire.backdrop_mask_element_id;
  node.mask_filter_info = wire.mask_filter_info;

  node.cache_render_surface = wire.cache_render_surface;
  node.double_sided = wire.double_sided;
  node.trilinear_filtering = wire.trilinear_filtering;
  node.subtree_hidden = wire.subtree_hidden;
  node.has_potential_filter_animation = wire.has_potential_filter_animation;
  node.has_potential_backdrop_filter_animation =
      wire.has_potential_backdrop_filter_animation;
  node.has_potential_opacity_animation = wire.has_potential_opacity_animation;
  node.subtree_has_copy_request = wire.subtree_has_copy_request;
  node.is_fast_rounded_corner = wire.is_fast_rounded_corner;
  node.may_have_backdrop_effect = wire.may_have_backdrop_effect;
  node.needs_effect_for_2d_scale_transform =
      wire.needs_effect_for_2d_scale_transform;
  node.only_draws_visible_content = wire.only_draws_visible_content;

  return base::ok();
}

base::expected<void, std::string> UpdatePropertyTreeNode(
    cc::PropertyTrees& trees,
    cc::ScrollNode& node,
    const mojom::ScrollNode& wire) {
  if (wire.transform_id != cc::kInvalidPropertyNodeId &&
      !IsPropertyTreeIndexValid(trees.transform_tree(), wire.transform_id)) {
    return base::unexpected("Invalid transform_id for scroll node");
  }
  node.transform_id = wire.transform_id;
  node.container_bounds = wire.container_bounds;
  node.bounds = wire.bounds;
  node.max_scroll_offset_affected_by_page_scale =
      wire.max_scroll_offset_affected_by_page_scale;
  node.scrolls_inner_viewport = wire.scrolls_inner_viewport;
  node.scrolls_outer_viewport = wire.scrolls_outer_viewport;
  node.prevent_viewport_scrolling_from_inner =
      wire.prevent_viewport_scrolling_from_inner;
  node.user_scrollable_horizontal = wire.user_scrollable_horizontal;
  node.user_scrollable_vertical = wire.user_scrollable_vertical;
  node.is_composited = wire.is_composited;
  node.element_id = wire.element_id;
  if (node.element_id) {
    trees.scroll_tree_mutable().SetElementIdForNodeId(node.id, node.element_id);
  }
  return base::ok();
}

base::expected<void, std::string> ValidateTreeIndices(
    cc::LayerTreeImpl& layers) {
  const cc::PropertyTrees& property_trees = *layers.property_trees();
  const auto& transform_tree = property_trees.transform_tree();
  const auto& clip_tree = property_trees.clip_tree();
  const auto& effect_tree = property_trees.effect_tree();
  const auto& scroll_tree = property_trees.scroll_tree();

  for (size_t i = cc::kContentsRootPropertyNodeId; i < transform_tree.size();
       ++i) {
    const auto& node = transform_tree.Node(i);
    if (!IsOptionalPropertyTreeIndexValid(transform_tree,
                                          node.parent_frame_id)) {
      return base::unexpected("Invalid parent_frame_id in transform tree");
    }
    if (node.sticky_position_constraint_id != -1 &&
        static_cast<size_t>(node.sticky_position_constraint_id) >=
            transform_tree.sticky_position_data().size()) {
      return base::unexpected("Invalid sticky_position_constraint_id");
    }
    if (node.anchor_position_scroll_data_id != -1 &&
        static_cast<size_t>(node.anchor_position_scroll_data_id) >=
            transform_tree.anchor_position_scroll_data().size()) {
      return base::unexpected("Invalid anchor_position_scroll_data_id");
    }
  }

  for (size_t i = cc::kContentsRootPropertyNodeId; i < clip_tree.size(); ++i) {
    const auto& node = clip_tree.Node(i);
    if (!IsPropertyTreeIndexValid(transform_tree, node.transform_id)) {
      return base::unexpected("Invalid transform_id in clip tree");
    }
    if (!IsOptionalPropertyTreeIndexValid(effect_tree,
                                          node.pixel_moving_filter_id)) {
      return base::unexpected("Invalid pixel_moving_filter_id in clip tree");
    }
  }

  for (size_t i = cc::kContentsRootPropertyNodeId; i < effect_tree.size();
       ++i) {
    const auto& node = effect_tree.Node(i);
    if (node.HasRenderSurface() && !node.element_id) {
      return base::unexpected(
          "Effect node with render surface must have a valid element_id");
    }
    if (!IsPropertyTreeIndexValid(transform_tree, node.transform_id)) {
      return base::unexpected("Invalid transform_id in effect tree");
    }
    if (!IsPropertyTreeIndexValid(clip_tree, node.clip_id)) {
      return base::unexpected("Invalid clip_id in effect tree");
    }
    if (!IsPropertyTreeIndexValid(effect_tree, node.target_id)) {
      return base::unexpected("Invalid target_id in effect tree");
    }
    if (!IsOptionalPropertyTreeIndexValid(
            effect_tree, node.closest_ancestor_with_cached_render_surface_id)) {
      return base::unexpected(
          "Invalid closest_ancestor_with_cached_render_surface_id in effect "
          "tree");
    }
    if (!IsOptionalPropertyTreeIndexValid(
            effect_tree, node.closest_ancestor_with_copy_request_id)) {
      return base::unexpected(
          "Invalid closest_ancestor_with_copy_request_id in effect tree");
    }
    if (!IsOptionalPropertyTreeIndexValid(
            effect_tree, node.closest_ancestor_being_captured_id)) {
      return base::unexpected(
          "Invalid closest_ancestor_being_captured_id in effect tree");
    }
    if (!IsOptionalPropertyTreeIndexValid(
            effect_tree, node.closest_ancestor_with_shared_element_id)) {
      return base::unexpected(
          "Invalid closest_ancestor_with_shared_element_id in effect tree");
    }
    if (!IsOptionalPropertyTreeIndexValid(effect_tree,
                                          node.view_transition_target_id)) {
      return base::unexpected(
          "Invalid view_transition_target_id in effect tree");
    }
  }

  for (size_t i = cc::kContentsRootPropertyNodeId; i < scroll_tree.size();
       ++i) {
    const auto& node = scroll_tree.Node(i);
    if (node.transform_id != cc::kInvalidPropertyNodeId &&
        !IsPropertyTreeIndexValid(transform_tree, node.transform_id)) {
      return base::unexpected("Invalid transform_id in scroll tree");
    }
  }

  for (auto* layer : layers) {
    if (!IsPropertyTreeIndexValid(transform_tree,
                                  layer->transform_tree_index())) {
      return base::unexpected("Invalid transform_tree_index for layer");
    }
    if (!IsPropertyTreeIndexValid(clip_tree, layer->clip_tree_index())) {
      return base::unexpected("Invalid clip_tree_index for layer");
    }
    if (!IsPropertyTreeIndexValid(effect_tree, layer->effect_tree_index())) {
      return base::unexpected("Invalid effect_tree_index for layer");
    }
    if (!IsPropertyTreeIndexValid(scroll_tree, layer->scroll_tree_index())) {
      return base::unexpected("Invalid scroll_tree_index for layer");
    }
  }

  return base::ok();
}

template <typename TreeType>
base::expected<bool, std::string> ResizePropertyTree(TreeType& tree,
                                                     uint32_t num_nodes) {
  if (num_nodes < 1) {
    return base::unexpected("Property tree size must be at least 1");
  }

  if (num_nodes > static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) {
    return base::unexpected("Property tree size too large");
  }

  if (static_cast<size_t>(num_nodes) == tree.nodes().size()) {
    return false;
  }

  if (static_cast<size_t>(num_nodes) < tree.nodes().size()) {
    tree.RemoveNodes(tree.nodes().size() - num_nodes);
    return true;
  }

  for (size_t i = tree.nodes().size(); i < num_nodes; ++i) {
    tree.Insert(typename TreeType::NodeType(), cc::kRootPropertyNodeId);
  }
  return true;
}

template <typename TreeType, typename WireContainerType>
base::expected<bool, std::string> UpdatePropertyTree(
    cc::PropertyTrees& trees,
    TreeType& tree,
    const WireContainerType& wire_updates) {
  if (wire_updates.empty()) {
    return false;
  }

  for (const auto& wire : wire_updates) {
    if (!IsPropertyTreeIndexValid(tree, wire->id)) {
      return base::unexpected("Invalid property tree node ID");
    }

    if (!IsOptionalPropertyTreeIndexValid(tree, wire->parent_id)) {
      return base::unexpected("Invalid property tree node parent_id");
    }

    if (wire->id > cc::kRootPropertyNodeId && wire->parent_id >= wire->id) {
      return base::unexpected(
          "Property tree node parent_id must be less than id");
    }

    if (wire->parent_id == cc::kInvalidPropertyNodeId &&
        wire->id != cc::kRootPropertyNodeId &&
        wire->id != cc::kSecondaryRootPropertyNodeId) {
      return base::unexpected(
          "Invalid parent_id for non-root property tree node");
    }

    auto& node = tree.MutableNode(wire->id);
    // Defer updating node ID and parent ID until validation succeeds to
    // prevent corrupting the tree state on failure. See
    // https://crbug.com/537935016
    RETURN_IF_ERROR(UpdatePropertyTreeNode(trees, node, *wire));
    node.id = wire->id;
    node.parent_id = wire->parent_id;
  }

  if (cc::kRootPropertyNodeId >= static_cast<int>(tree.size())) {
    return base::unexpected("Missing root property node");
  }
  auto& root_node = tree.MutableNode(cc::kRootPropertyNodeId);
  if (root_node.parent_id != cc::kInvalidPropertyNodeId) {
    return base::unexpected(
        "Root property node must have an invalid parent ID");
  }
  return true;
}

base::expected<std::vector<cc::StickyPositionNodeData>, std::string>
DeserializeStickyPositionData(
    cc::PropertyTrees& trees,
    std::vector<mojom::StickyPositionNodeDataPtr>& wire_data) {
  std::vector<cc::StickyPositionNodeData> sticky_position_node_data;
  sticky_position_node_data.reserve(wire_data.size());
  for (auto& wire : wire_data) {
    if (!IsOptionalPropertyTreeIndexValid(trees.scroll_tree(),
                                          wire->x_scroll_ancestor) ||
        !IsOptionalPropertyTreeIndexValid(trees.scroll_tree(),
                                          wire->y_scroll_ancestor) ||
        (wire->x_scroll_ancestor == cc::kInvalidPropertyNodeId &&
         wire->y_scroll_ancestor == cc::kInvalidPropertyNodeId)) {
      return base::unexpected("Invalid scroll ancestor ID");
    }

    if (!IsOptionalPropertyTreeIndexValid(
            trees.transform_tree(), wire->nearest_node_shifting_sticky_box)) {
      return base::unexpected("Invalid nearest_node_shifting_sticky_box");
    }

    if (!IsOptionalPropertyTreeIndexValid(
            trees.transform_tree(),
            wire->nearest_node_shifting_containing_block)) {
      return base::unexpected("Invalid nearest_node_shifting_containing_block");
    }

    cc::StickyPositionNodeData& data = sticky_position_node_data.emplace_back();
    data.x_scroll_ancestor = wire->x_scroll_ancestor;
    data.y_scroll_ancestor = wire->y_scroll_ancestor;
    data.constraints.is_anchored_left = wire->is_anchored_left;
    data.constraints.is_anchored_right = wire->is_anchored_right;
    data.constraints.is_anchored_top = wire->is_anchored_top;
    data.constraints.is_anchored_bottom = wire->is_anchored_bottom;
    data.constraints.left_offset = wire->left_offset;
    data.constraints.right_offset = wire->right_offset;
    data.constraints.top_offset = wire->top_offset;
    data.constraints.bottom_offset = wire->bottom_offset;
    data.constraints.constraint_box_rect = wire->constraint_box_rect;
    data.constraints.scroll_container_relative_sticky_box_rect =
        wire->scroll_container_relative_sticky_box_rect;
    data.constraints.scroll_container_relative_containing_block_rect =
        wire->scroll_container_relative_containing_block_rect;
    data.constraints.pixel_snap_offset = wire->pixel_snap_offset;
    data.nearest_node_shifting_sticky_box =
        wire->nearest_node_shifting_sticky_box;
    data.nearest_node_shifting_containing_block =
        wire->nearest_node_shifting_containing_block;
    data.total_sticky_box_sticky_offset = wire->total_sticky_box_sticky_offset;
    data.total_containing_block_sticky_offset =
        wire->total_containing_block_sticky_offset;
  }
  return sticky_position_node_data;
}

base::expected<std::vector<cc::AnchorPositionScrollData>, std::string>
DeserializeAnchorPositionScrollData(
    std::vector<mojom::AnchorPositionScrollDataPtr>& wire_data) {
  std::vector<cc::AnchorPositionScrollData> anchor_position_scroll_data;
  for (auto& wire : wire_data) {
    cc::AnchorPositionScrollData& data =
        anchor_position_scroll_data.emplace_back();
    data.adjustment_container_ids = wire->adjustment_container_ids;
    data.accumulated_scroll_origin = wire->accumulated_scroll_origin;
    data.needs_scroll_adjustment_in_x = wire->needs_scroll_adjustment_in_x;
    data.needs_scroll_adjustment_in_y = wire->needs_scroll_adjustment_in_y;
  }
  return anchor_position_scroll_data;
}

base::expected<bool, std::string> UpdateTransformTreeProperties(
    cc::PropertyTrees& trees,
    cc::TransformTree& tree,
    mojom::TransformTreeUpdate& update) {
  if (update.page_scale_factor <= 0 ||
      !std::isfinite(update.page_scale_factor)) {
    return base::unexpected("Invalid page_scale_factor");
  }
  if (update.device_scale_factor <= 0 ||
      !std::isfinite(update.device_scale_factor)) {
    return base::unexpected("Invalid device_scale_factor");
  }
  if (update.device_transform_scale_factor <= 0 ||
      !std::isfinite(update.device_transform_scale_factor)) {
    return base::unexpected("Invalid device_transform_scale_factor");
  }
  for (int id : update.nodes_affected_by_outer_viewport_bounds_delta) {
    if (!IsPropertyTreeIndexValid(tree, id)) {
      return base::unexpected(
          "Invalid node ID in nodes_affected_by_outer_viewport_bounds_delta");
    }
  }
  for (int id : update.nodes_affected_by_safe_area_bottom) {
    if (!IsPropertyTreeIndexValid(tree, id)) {
      return base::unexpected(
          "Invalid node ID in nodes_affected_by_safe_area_bottom");
    }
  }
  tree.set_page_scale_factor(update.page_scale_factor);
  tree.set_device_scale_factor(update.device_scale_factor);
  tree.set_device_transform_scale_factor(update.device_transform_scale_factor);
  tree.set_nodes_affected_by_outer_viewport_bounds_delta(
      std::move(update.nodes_affected_by_outer_viewport_bounds_delta));
  tree.set_nodes_affected_by_safe_area_bottom(
      std::move(update.nodes_affected_by_safe_area_bottom));
  ASSIGN_OR_RETURN(
      tree.sticky_position_data(),
      DeserializeStickyPositionData(trees, update.sticky_position_data));
  ASSIGN_OR_RETURN(
      tree.anchor_position_scroll_data(),
      DeserializeAnchorPositionScrollData(update.anchor_position_scroll_data));

  bool drawn_elastic_overscroll_changed =
      tree.drawn_elastic_overscroll() != update.drawn_elastic_overscroll;
  tree.drawn_elastic_overscroll() = update.drawn_elastic_overscroll;
  return drawn_elastic_overscroll_changed;
}

base::expected<bool, std::string> UpdateScrollTreeProperties(
    cc::PropertyTrees& trees,
    cc::ScrollTree& tree,
    const mojom::ScrollTreeUpdate& update) {
  for (auto const& [element_id, overscroll] : update.elastic_overscroll) {
    if (!std::isfinite(overscroll.x()) || !std::isfinite(overscroll.y())) {
      return base::unexpected("Invalid elastic_overscroll");
    }
  }
  tree.synced_scroll_offset_map() = update.synced_scroll_offsets;
  tree.scrolling_contents_cull_rects() = update.scrolling_contents_cull_rects;
  bool elastic_overscroll_changed =
      tree.elastic_overscroll() != update.elastic_overscroll;
  tree.elastic_overscroll() = update.elastic_overscroll;
  return elastic_overscroll_changed;
}

base::expected<void, std::string> UpdateMirrorLayerExtra(
    const mojom::MirrorLayerExtraPtr& extra,
    cc::MirrorLayerImpl& layer) {
  if (extra->mirrored_layer_id != 0 &&
      !layer.layer_tree_impl()->LayerById(extra->mirrored_layer_id)) {
    return base::unexpected("Invalid mirrored_layer_id");
  }
  layer.SetMirroredLayerId(extra->mirrored_layer_id);
  return base::ok();
}

base::expected<void, std::string> UpdateNinePatchLayerExtra(
    const mojom::NinePatchLayerExtraPtr& extra,
    cc::NinePatchLayerImpl& layer) {
  if (!extra->ui_resource_id) {
    return base::unexpected("Invalid ui_resource_id for NinePatchLayerImpl");
  }
  layer.SetUIResourceId(extra->ui_resource_id);
  layer.SetImageBounds(extra->image_bounds);
  layer.SetLayout(extra->image_aperture, extra->border, extra->layer_occlusion,
                  extra->fill_center);
  layer.SetUV(extra->uv_top_left, extra->uv_bottom_right);
  return base::ok();
}

void UpdateTextureLayerExtra(const mojom::TextureLayerExtraPtr& extra,
                             cc::TextureLayerImpl& layer) {
  layer.SetBlendBackgroundColor(extra->blend_background_color);
  layer.SetForceTextureToOpaque(extra->force_texture_to_opaque);
  layer.SetUVTopLeft(extra->uv_top_left);
  layer.SetUVBottomRight(extra->uv_bottom_right);

  if (extra->transferable_resource) {
    ReleaseCallback release_callback;
    if (!extra->transferable_resource->is_empty()) {
      release_callback = base::BindOnce(
          [](cc::LayerTreeHostImpl* host_impl, ResourceId id,
             scoped_refptr<gpu::ClientSharedImage> shared_image,
             const gpu::SyncToken& sync_token, bool is_lost) {
            host_impl->ReturnResource({id, shared_image->EndImport(sync_token),
                                       /*release_fence=*/gfx::GpuFenceHandle(),
                                       /*count=*/1, is_lost});
          },
          layer.layer_tree_impl()->host_impl(),
          extra->transferable_resource->id,
          extra->transferable_resource->shared_image());
    }
    layer.SetTransferableResource(extra->transferable_resource.value(),
                                  std::move(release_callback));
  } else if (extra->update_transferable_resource) {
    layer.ClearTransferableResource();
  }
}

base::expected<void, std::string> UpdateUIResourceLayerExtra(
    const mojom::UIResourceLayerExtraPtr& extra,
    cc::UIResourceLayerImpl& layer) {
  if (!extra->ui_resource_id) {
    return base::unexpected("Invalid ui_resource_id for UIResourceLayerImpl");
  }
  layer.SetUIResourceId(extra->ui_resource_id);
  layer.SetImageBounds(extra->image_bounds);
  layer.SetUV(extra->uv_top_left, extra->uv_bottom_right);
  return base::ok();
}

void UpdateScrollbarLayerBaseExtra(
    const mojom::ScrollbarLayerBaseExtraPtr& extra,
    cc::ScrollbarLayerImplBase& layer) {
  // ScrollbarLayerImplBase properties
  layer.SetScrollElementId(extra->scroll_element_id);
  layer.set_is_overlay_scrollbar(extra->is_overlay_scrollbar);
  layer.set_is_web_test(extra->is_web_test);
  layer.SetThumbThicknessScaleFactor(extra->thumb_thickness_scale_factor);
  layer.SetCurrentPos(extra->current_pos);
  layer.SetClipLayerLength(extra->clip_layer_length);
  layer.SetScrollLayerLength(extra->scroll_layer_length);
  layer.SetVerticalAdjust(extra->vertical_adjust);
  layer.SetHasFindInPageTickmarks(extra->has_find_in_page_tickmarks);
}

void UpdateNinePatchThumbScrollbarLayerExtra(
    const mojom::NinePatchThumbScrollbarLayerExtraPtr& extra,
    cc::NinePatchThumbScrollbarLayerImpl& layer) {
  UpdateScrollbarLayerBaseExtra(
      extra->scrollbar_base_extra,
      static_cast<cc::ScrollbarLayerImplBase&>(layer));

  layer.SetThumbThickness(extra->thumb_thickness);
  layer.SetMinimumThumbLength(extra->minimum_thumb_length);
  layer.SetTrackStart(extra->track_start);
  layer.SetTrackLength(extra->track_length);
  layer.SetImageBounds(extra->image_bounds);
  layer.SetAperture(extra->aperture);
  layer.set_thumb_ui_resource_id(extra->thumb_ui_resource_id);
  layer.set_track_and_buttons_ui_resource_id(
      extra->track_and_buttons_ui_resource_id);
}

void UpdatePaintedScrollbarLayerExtra(
    const mojom::PaintedScrollbarLayerExtraPtr& extra,
    cc::PaintedScrollbarLayerImpl& layer) {
  UpdateScrollbarLayerBaseExtra(
      extra->scrollbar_base_extra,
      static_cast<cc::ScrollbarLayerImplBase&>(layer));

  layer.set_internal_contents_scale_and_bounds(extra->internal_contents_scale,
                                               extra->internal_content_bounds);

  layer.SetJumpOnTrackClick(extra->jump_on_track_click);
  layer.SetSupportsDragSnapBack(extra->supports_drag_snap_back);
  layer.SetThumbThickness(extra->thumb_thickness);
  layer.SetMinimumThumbLength(extra->minimum_thumb_length);
  layer.SetBackButtonRect(extra->back_button_rect);
  layer.SetForwardButtonRect(extra->forward_button_rect);
  layer.SetTrackRect(extra->track_rect);

  layer.set_track_and_buttons_ui_resource_id(
      extra->track_and_buttons_ui_resource_id);
  layer.set_thumb_ui_resource_id(extra->thumb_ui_resource_id);
  layer.set_uses_nine_patch_track_and_buttons(
      extra->uses_nine_patch_track_and_buttons);

  layer.SetScrollbarPaintedOpacity(extra->painted_opacity);
  if (extra->thumb_color) {
    layer.SetThumbColor(extra->thumb_color.value());
  }
  layer.SetTrackAndButtonsImageBounds(extra->track_and_buttons_image_bounds);
  layer.SetTrackAndButtonsAperture(extra->track_and_buttons_aperture);
}

void UpdateSolidColorScrollbarLayerExtra(
    const mojom::SolidColorScrollbarLayerExtraPtr& extra,
    cc::SolidColorScrollbarLayerImpl& layer) {
  UpdateScrollbarLayerBaseExtra(
      extra->scrollbar_base_extra,
      static_cast<cc::ScrollbarLayerImplBase&>(layer));
  layer.set_color(extra->color);
  // thumb_thickness has no update method in SolidColorScrollbarLayerImpl
  // so it is intentionally ignored here.
}

void UpdateSurfaceLayerExtra(const mojom::SurfaceLayerExtraPtr& extra,
                             cc::SurfaceLayerImpl& layer) {
  layer.SetRange(extra->surface_range, extra->deadline_in_frames);
  layer.SetStretchContentToFillBounds(extra->stretch_content_to_fill_bounds);
  layer.SetSurfaceHitTestable(extra->surface_hit_testable);
  layer.SetHasPointerEventsNone(extra->has_pointer_events_none);
  layer.SetIsReflection(extra->is_reflection);
  if (extra->will_draw_needs_reset) {
    layer.ResetStateForUpdateSubmissionStateCallback();
  }
  layer.SetOverrideChildPaintFlags(extra->override_child_paint_flags);
}

void UpdateViewTransitionContentLayerExtra(
    const mojom::ViewTransitionContentLayerExtraPtr& extra,
    cc::ViewTransitionContentLayerImpl& layer) {
  layer.SetMaxExtentsRect(extra->max_extents_rect);
}

void UpdateTileDisplayLayerExtra(const mojom::TileDisplayLayerExtraPtr& extra,
                                 cc::TileDisplayLayerImpl& layer) {
  layer.SetSolidColor(extra->solid_color);
  layer.SetIsBackdropFilterMask(extra->is_backdrop_filter_mask);
  layer.SetIsDirectlyCompositedImage(extra->is_directly_composited_image);
  layer.SetNearestNeighbor(extra->nearest_neighbor);
  if (extra->has_animated_image_update_rect) {
    layer.set_has_animated_image_update_rect();
  }
  if (extra->has_non_animated_image_update_rect) {
    layer.set_has_non_animated_image_update_rect();
  }
  layer.SetContentColorUsage(extra->content_color_usage);
  layer.SetRecordedBounds(extra->recorded_bounds);
  layer.SetProposedTilingScalesForDeletion(
      extra->proposed_tiling_scales_for_deletion);
}

base::expected<void, std::string> UpdateLayer(const mojom::Layer& wire,
                                              cc::LayerImpl& layer) {
  if (wire.type != layer.GetLayerType()) {
    return base::unexpected("Incorrect layer type used in Layer update.");
  }

  const cc::PropertyTrees& property_trees =
      *layer.layer_tree_impl()->property_trees();
  if (!IsPropertyTreeIndexValid(property_trees.transform_tree(),
                                wire.transform_tree_index)) {
    return base::unexpected(
        base::StrCat({"Invalid transform tree ID: ",
                      base::NumberToString(wire.transform_tree_index)}));
  }

  if (!IsPropertyTreeIndexValid(property_trees.clip_tree(),
                                wire.clip_tree_index)) {
    return base::unexpected(
        base::StrCat({"Invalid clip tree ID: ",
                      base::NumberToString(wire.clip_tree_index)}));
  }

  if (!IsPropertyTreeIndexValid(property_trees.effect_tree(),
                                wire.effect_tree_index)) {
    return base::unexpected(
        base::StrCat({"Invalid effect tree ID: ",
                      base::NumberToString(wire.effect_tree_index)}));
  }

  if (!IsPropertyTreeIndexValid(property_trees.scroll_tree(),
                                wire.scroll_tree_index)) {
    return base::unexpected(
        base::StrCat({"Invalid scroll tree ID: ",
                      base::NumberToString(wire.scroll_tree_index)}));
  }

  layer.SetTransformTreeIndex(wire.transform_tree_index);
  layer.SetClipTreeIndex(wire.clip_tree_index);
  layer.SetEffectTreeIndex(wire.effect_tree_index);
  layer.SetScrollTreeIndex(wire.scroll_tree_index);

  if (wire.general_properties) {
    const auto& general = *wire.general_properties;
    if (general.contents_opaque && !general.contents_opaque_for_text) {
      return base::unexpected(
          "Invalid contents_opaque_for_text: cannot be false if "
          "contents_opaque "
          "is true.");
    }
    if (general.safe_opaque_background_color.isOpaque() !=
        general.contents_opaque) {
      return base::unexpected(
          "Invalid safe_opaque_background_color: opaqueness must agree with "
          "contents_opaque");
    }

    layer.SetBounds(general.bounds);
    layer.SetContentsOpaque(general.contents_opaque);
    layer.SetContentsOpaqueForText(general.contents_opaque_for_text);
    layer.SetDrawsContent(general.is_drawable);
    if (general.layer_property_changed_not_from_property_trees) {
      layer.NoteLayerPropertyChanged();
    }
    if (general.layer_property_changed_from_property_trees) {
      layer.NoteLayerPropertyChangedFromPropertyTrees();
    }
    layer.SetBackgroundColor(general.background_color);
    layer.SetSafeOpaqueBackgroundColor(general.safe_opaque_background_color);
    layer.SetHitTestOpaqueness(general.hit_test_opaqueness);
    layer.SetElementId(general.element_id);
    layer.UnionUpdateRect(general.update_rect);
    layer.SetOffsetToTransformParent(general.offset_to_transform_parent);
    layer.SetShouldCheckBackfaceVisibility(
        general.should_check_backface_visibility);
    if (general.rare_properties) {
      layer.SetFilterQuality(general.rare_properties->filter_quality);
      layer.SetDynamicRangeLimit(general.rare_properties->dynamic_range_limit);
      layer.SetCaptureBounds(general.rare_properties->capture_bounds);
    }

    switch (wire.type) {
      case cc::mojom::LayerType::kMirror:
        RETURN_IF_FALSE(
            general.layer_extra && general.layer_extra->is_mirror_layer_extra(),
            "Invalid layer_extra type for MirrorLayerImpl");
        RETURN_IF_ERROR(UpdateMirrorLayerExtra(
            general.layer_extra->get_mirror_layer_extra(),
            static_cast<cc::MirrorLayerImpl&>(layer)));
        break;
      case cc::mojom::LayerType::kNinePatch:
        RETURN_IF_FALSE(general.layer_extra &&
                            general.layer_extra->is_nine_patch_layer_extra(),
                        "Invalid layer_extra type for NinePatchLayerImpl");
        RETURN_IF_ERROR(UpdateNinePatchLayerExtra(
            general.layer_extra->get_nine_patch_layer_extra(),
            static_cast<cc::NinePatchLayerImpl&>(layer)));
        break;
      case cc::mojom::LayerType::kNinePatchThumbScrollbar:
        RETURN_IF_FALSE(
            general.layer_extra &&
                general.layer_extra
                    ->is_nine_patch_thumb_scrollbar_layer_extra(),
            "Invalid layer_extra type for NinePatchThumbScrollbarLayerImpl");
        UpdateNinePatchThumbScrollbarLayerExtra(
            general.layer_extra->get_nine_patch_thumb_scrollbar_layer_extra(),
            static_cast<cc::NinePatchThumbScrollbarLayerImpl&>(layer));
        break;
      case cc::mojom::LayerType::kPaintedScrollbar:
        RETURN_IF_FALSE(
            general.layer_extra &&
                general.layer_extra->is_painted_scrollbar_layer_extra(),
            "Invalid layer_extra type for PaintedScrollbarLayerImpl");
        UpdatePaintedScrollbarLayerExtra(
            general.layer_extra->get_painted_scrollbar_layer_extra(),
            static_cast<cc::PaintedScrollbarLayerImpl&>(layer));
        break;
      case cc::mojom::LayerType::kSolidColorScrollbar:
        RETURN_IF_FALSE(
            general.layer_extra &&
                general.layer_extra->is_solid_color_scrollbar_layer_extra(),
            "Invalid layer_extra type for SolidColorScrollbarLayerImpl");
        UpdateSolidColorScrollbarLayerExtra(
            general.layer_extra->get_solid_color_scrollbar_layer_extra(),
            static_cast<cc::SolidColorScrollbarLayerImpl&>(layer));
        break;
      case cc::mojom::LayerType::kSurface:
        RETURN_IF_FALSE(general.layer_extra &&
                            general.layer_extra->is_surface_layer_extra(),
                        "Invalid layer_extra type for SurfaceLayerImpl");
        UpdateSurfaceLayerExtra(general.layer_extra->get_surface_layer_extra(),
                                static_cast<cc::SurfaceLayerImpl&>(layer));
        break;
      case cc::mojom::LayerType::kTexture:
        RETURN_IF_FALSE(general.layer_extra &&
                            general.layer_extra->is_texture_layer_extra(),
                        "Invalid layer_extra type for TextureLayerImpl");
        UpdateTextureLayerExtra(general.layer_extra->get_texture_layer_extra(),
                                static_cast<cc::TextureLayerImpl&>(layer));
        break;
      case cc::mojom::LayerType::kTileDisplay:
        RETURN_IF_FALSE(general.layer_extra &&
                            general.layer_extra->is_tile_display_layer_extra(),
                        "Invalid layer_extra type for TileDisplayLayerImpl");
        UpdateTileDisplayLayerExtra(
            general.layer_extra->get_tile_display_layer_extra(),
            static_cast<cc::TileDisplayLayerImpl&>(layer));
        break;
      case cc::mojom::LayerType::kUIResource:
        RETURN_IF_FALSE(general.layer_extra &&
                            general.layer_extra->is_ui_resource_layer_extra(),
                        "Invalid layer_extra type for UIResourceLayerImpl");
        RETURN_IF_ERROR(UpdateUIResourceLayerExtra(
            general.layer_extra->get_ui_resource_layer_extra(),
            static_cast<cc::UIResourceLayerImpl&>(layer)));
        break;
      case cc::mojom::LayerType::kViewTransitionContent:
        RETURN_IF_FALSE(
            general.layer_extra &&
                general.layer_extra->is_view_transition_content_layer_extra(),
            "Invalid layer_extra type for ViewTransitionContentLayerImpl");
        UpdateViewTransitionContentLayerExtra(
            general.layer_extra->get_view_transition_content_layer_extra(),
            static_cast<cc::ViewTransitionContentLayerImpl&>(layer));
        break;
      case cc::mojom::LayerType::kHeadsUpDisplay:
      case cc::mojom::LayerType::kPicture:
      case cc::mojom::LayerType::kVideo:
        return base::unexpected("Invalid LayerType for UpdateLayer.");
      case cc::mojom::LayerType::kLayer:
        RETURN_IF_FALSE(!general.layer_extra,
                        "Unexpected layer_extra for LayerImpl");
        break;
      case cc::mojom::LayerType::kSolidColor:
        RETURN_IF_FALSE(!general.layer_extra,
                        "Unexpected layer_extra for SolidColorLayerImpl");
        break;
    }
  }

  return base::ok();
}

base::expected<void, std::string> CreateOrUpdateLayers(
    cc::LayerTreeHostImpl& host_impl,
    const std::vector<mojom::LayerPtr>& updates,
    std::optional<std::vector<int32_t>>& layer_order,
    cc::LayerTreeImpl& layers) {
  TRACE_EVENT1("viz", "CreateOrUpdateLayers", "LayerCount", updates.size());

  // First add new layers to the tree
  for (auto& wire : updates) {
    cc::LayerImpl* layer = layers.LayerById(wire->id);
    if (!layer) {
      if (!layer_order) {
        // If there's a new layer, there must also be a new |layer_order|.
        return base::unexpected("Invalid layer ID");
      }
      std::unique_ptr<cc::LayerImpl> new_layer;
      RETURN_IF_ERROR(CreateLayer(host_impl, layers, *wire, new_layer));
      layers.AddLayer(std::move(new_layer));
    }
  }

  // Reorder layers if necessary; obsolete layers will be deleted.
  if (layer_order) {
    RETURN_IF_ERROR(cc::TreeSynchronizer::SynchronizeLayerOrder(
        layer_order.value(), layers));
  }

  // Apply layer updates
  for (auto& wire : updates) {
    cc::LayerImpl* layer = layers.LayerById(wire->id);
    if (!layer) {
      return base::unexpected("Layer ID not found after synchronization");
    }
    RETURN_IF_ERROR(UpdateLayer(*wire, *layer));
  }

  return base::ok();
}

base::expected<void, std::string> UpdateViewportPropertyIds(
    cc::LayerTreeImpl& layers,
    cc::PropertyTrees& trees,
    mojom::LayerTreeUpdate& update) {
  const auto& transform_tree = trees.transform_tree();
  const auto& scroll_tree = trees.scroll_tree();
  const auto& clip_tree = trees.clip_tree();
  if (!IsOptionalPropertyTreeIndexValid(
          transform_tree, update.overscroll_elasticity_transform)) {
    return base::unexpected("Invalid overscroll_elasticity_transform");
  }
  if (!IsOptionalPropertyTreeIndexValid(transform_tree,
                                        update.page_scale_transform)) {
    return base::unexpected("Invalid page_scale_transform");
  }
  if (!IsOptionalPropertyTreeIndexValid(scroll_tree, update.inner_scroll)) {
    return base::unexpected("Invalid inner_scroll");
  }
  if (update.inner_scroll == cc::kInvalidPropertyNodeId) {
    if (update.outer_clip != cc::kInvalidPropertyNodeId ||
        update.outer_scroll != cc::kInvalidPropertyNodeId) {
      return base::unexpected(
          "Cannot set outer_clip or outer_scroll without valid inner_scroll");
    }
  } else {
    if (update.outer_scroll == cc::kInvalidPropertyNodeId) {
      return base::unexpected("Must set outer_scroll if inner_scroll is set");
    }
  }
  if (!IsOptionalPropertyTreeIndexValid(clip_tree, update.outer_clip)) {
    return base::unexpected("Invalid outer_clip");
  }
  if (!IsOptionalPropertyTreeIndexValid(scroll_tree, update.outer_scroll)) {
    return base::unexpected("Invalid outer_scroll");
  }

  layers.SetViewportPropertyIds(cc::ViewportPropertyIds{
      .overscroll_elasticity_transform = update.overscroll_elasticity_transform,
      .page_scale_transform = update.page_scale_transform,
      .inner_scroll = update.inner_scroll,
      .outer_clip = update.outer_clip,
      .outer_scroll = update.outer_scroll,
  });
  return base::ok();
}

base::expected<cc::TileDisplayLayerImpl::TileResource, std::string>
DeserializeTileResource(cc::LayerTreeHostImpl* host_impl,
                        mojom::TileResource& wire) {
  if (wire.resource.id == kInvalidResourceId) {
    return base::unexpected("Invalid tile resource");
  }

  ReleaseCallback release_callback = base::BindOnce(
      [](cc::LayerTreeHostImpl* host_impl, ResourceId id,
         scoped_refptr<gpu::ClientSharedImage> shared_image,
         const gpu::SyncToken& sync_token, bool is_lost) {
        host_impl->ReturnResource({id, shared_image->EndImport(sync_token),
                                   /*release_fence=*/gfx::GpuFenceHandle(),
                                   /*count=*/1, is_lost});
      },
      host_impl, wire.resource.id, wire.resource.shared_image());

  auto resource_id = host_impl->resource_provider()->ImportResource(
      wire.resource,
      /*impl_release_callback=*/std::move(release_callback),
      /*main_thread_release_callback=*/base::NullCallback(),
      /*evicted_callback=*/base::NullCallback());

  return cc::TileDisplayLayerImpl::TileResource(resource_id,
                                                wire.resource.GetSize());
}

base::expected<cc::TileDisplayLayerImpl::TileContents, std::string>
DeserializeTileContents(cc::LayerTreeHostImpl* host_impl,
                        mojom::TileContents& wire) {
  switch (wire.which()) {
    case mojom::TileContents::Tag::kMissingReason:
      return cc::TileDisplayLayerImpl::TileContents(
          cc::TileDisplayLayerImpl::NoContents(wire.get_missing_reason()));

    case mojom::TileContents::Tag::kResource:
      return DeserializeTileResource(host_impl, *wire.get_resource());

    case mojom::TileContents::Tag::kSolidColor:
      return cc::TileDisplayLayerImpl::TileContents(wire.get_solid_color());
  }
}

base::expected<void, std::string> DeserializeTiling(
    cc::LayerTreeHostImpl* host_impl,
    cc::TileDisplayLayerImpl& layer,
    mojom::Tiling& wire) {
  if (wire.is_deleted) {
    layer.RemoveTiling(wire.scale_key);
    return base::ok();
  }
  if (wire.tile_size.width() <= 0 || wire.tile_size.height() <= 0) {
    return base::unexpected("Invalid tile_size dimensions in Tiling");
  }

  const float scale_key =
      std::max(wire.raster_scale.x(), wire.raster_scale.y());
  auto& tiling = layer.GetOrCreateTilingFromScaleKey(scale_key);
  tiling.SetRasterTransform(gfx::AxisTransform2d::FromScaleAndTranslation(
      wire.raster_scale, wire.raster_translation));
  tiling.SetTileSize(wire.tile_size);
  tiling.SetTilingRect(wire.tiling_rect);
  for (auto& wire_tile : wire.tiles) {
    const bool is_out_of_bounds =
        wire_tile->column_index >=
            static_cast<uint32_t>(tiling.tiling_data()->num_tiles_x()) ||
        wire_tile->row_index >=
            static_cast<uint32_t>(tiling.tiling_data()->num_tiles_y());
    const bool is_deleted = wire_tile->contents->is_missing_reason() &&
                            wire_tile->contents->get_missing_reason() ==
                                cc::mojom::MissingTileReason::kTileDeleted;
    // Deleted tiles (both in-bounds and out-of-bounds) are not allowed to track
    // damage.
    if (is_deleted && wire_tile->update_damage) {
      return base::unexpected("Deleted tile cannot update damage");
    }

    if (is_out_of_bounds) {
      if (!is_deleted) {
        return base::unexpected("Invalid tile index in Tiling");
      }
    }
    ASSIGN_OR_RETURN(auto contents,
                     DeserializeTileContents(host_impl, *wire_tile->contents));
    tiling.SetTileContents(
        cc::TileIndex{base::saturated_cast<int>(wire_tile->column_index),
                      base::saturated_cast<int>(wire_tile->row_index)},
        std::move(contents), wire_tile->update_damage);
  }
  if (tiling.tiles().empty()) {
    layer.RemoveTiling(tiling.contents_scale_key());
  }
  return base::ok();
}

void DeserializeViewTransitionRequests(
    cc::LayerTreeImpl& layers,
    std::vector<mojom::ViewTransitionRequestPtr>& wire_data) {
  // TODO(crbug.com/467351935): Have `delay_layer_tree_view_deletion` added to
  //  `mojom::ViewTransitionRequestPtr`
  for (auto& wire : wire_data) {
    std::unique_ptr<cc::ViewTransitionRequest> request;
    switch (wire->type) {
      case mojom::CompositorFrameTransitionDirectiveType::kSave:
        // Callback is not used at all in
        // ViewTransitionRequest::ConstructDirective, therefore it's not
        // wired in mojom::ViewTransitionRequest to viz, and here we just use
        // a placeholder.
        request = cc::ViewTransitionRequest::CreateCapture(
            wire->transition_token, wire->maybe_cross_frame_sink,
            wire->capture_resource_ids,
            cc::ViewTransitionRequest::ViewTransitionCaptureCallback(),
            /*delay_layer_tree_view_deletion=*/true);
        break;
      case mojom::CompositorFrameTransitionDirectiveType::kAnimateRenderer:
        request = cc::ViewTransitionRequest::CreateAnimateRenderer(
            wire->transition_token, wire->maybe_cross_frame_sink,
            /*delay_layer_tree_view_deletion=*/true);
        break;
      case mojom::CompositorFrameTransitionDirectiveType::kRelease:
        request = cc::ViewTransitionRequest::CreateRelease(
            wire->transition_token, wire->maybe_cross_frame_sink,
            /*delay_layer_tree_view_deletion=*/true);
        break;
    }
    request->set_sequence_id(wire->sequence_id);
    layers.AddViewTransitionRequest(std::move(request));
  }
}

gfx::StepsTimingFunction::StepPosition DeserializeTimingStepPosition(
    mojom::TimingStepPosition step_position) {
  switch (step_position) {
    case mojom::TimingStepPosition::kStart:
      return gfx::StepsTimingFunction::StepPosition::START;
    case mojom::TimingStepPosition::kEnd:
      return gfx::StepsTimingFunction::StepPosition::END;
    case mojom::TimingStepPosition::kJumpBoth:
      return gfx::StepsTimingFunction::StepPosition::JUMP_BOTH;
    case mojom::TimingStepPosition::kJumpEnd:
      return gfx::StepsTimingFunction::StepPosition::JUMP_END;
    case mojom::TimingStepPosition::kJumpNone:
      return gfx::StepsTimingFunction::StepPosition::JUMP_NONE;
    case mojom::TimingStepPosition::kJumpStart:
      return gfx::StepsTimingFunction::StepPosition::JUMP_START;
  }
}

base::expected<std::unique_ptr<gfx::TimingFunction>, std::string>
DeserializeTimingFunction(mojom::TimingFunction& wire) {
  switch (wire.which()) {
    case mojom::TimingFunction::Tag::kLinear: {
      const auto& wire_points = wire.get_linear();
      std::vector<gfx::LinearEasingPoint> points;
      points.reserve(wire_points.size());
      for (const auto& wire_point : wire_points) {
        points.emplace_back(wire_point->in, wire_point->out);
      }
      if (points.empty()) {
        return gfx::LinearTimingFunction::Create();
      }
      if (points.size() < 2) {
        return base::unexpected(
            "Invalid number of points: must be at least 2 for LinearTiming");
      }
      return gfx::LinearTimingFunction::Create(std::move(points));
    }
    case mojom::TimingFunction::Tag::kCubicBezier: {
      const auto& bezier = *wire.get_cubic_bezier();
      return gfx::CubicBezierTimingFunction::Create(bezier.x1, bezier.y1,
                                                    bezier.x2, bezier.y2);
    }
    case mojom::TimingFunction::Tag::kSteps: {
      const auto& steps = *wire.get_steps();
      if (steps.num_steps == 0 ||
          (steps.step_position == mojom::TimingStepPosition::kJumpNone &&
           steps.num_steps <= 1)) {
        return base::unexpected(
            "Invalid num_steps: must be greater than 0 (or 1 for JumpNone)");
      }
      return gfx::StepsTimingFunction::Create(
          base::saturated_cast<int32_t>(steps.num_steps),
          DeserializeTimingStepPosition(steps.step_position));
    }
  }
}

gfx::TransformOperations DeserializeTransformOperations(
    const std::vector<mojom::TransformOperationPtr>& wire_ops) {
  gfx::TransformOperations transform;
  for (const auto& wire_op : wire_ops) {
    switch (wire_op->which()) {
      case mojom::TransformOperation::Tag::kIdentity:
        transform.AppendIdentity();
        break;
      case mojom::TransformOperation::Tag::kPerspectiveDepth: {
        std::optional<float> depth;
        if (wire_op->get_perspective_depth()) {
          depth = wire_op->get_perspective_depth();
        }
        transform.AppendPerspective(depth);
        break;
      }
      case mojom::TransformOperation::Tag::kSkew: {
        const auto& skew = wire_op->get_skew();
        transform.AppendSkew(skew.x(), skew.y());
        break;
      }
      case mojom::TransformOperation::Tag::kScale: {
        const auto& scale = wire_op->get_scale();
        transform.AppendScale(scale.x(), scale.y(), scale.z());
        break;
      }
      case mojom::TransformOperation::Tag::kTranslate: {
        const auto& translate = wire_op->get_translate();
        transform.AppendTranslate(translate.x(), translate.y(), translate.z());
        break;
      }
      case mojom::TransformOperation::Tag::kRotate: {
        const auto& axis = wire_op->get_rotate()->axis;
        const float angle = wire_op->get_rotate()->angle;
        transform.AppendRotate(axis.x(), axis.y(), axis.z(), angle);
        break;
      }
      case mojom::TransformOperation::Tag::kMatrix:
        transform.AppendMatrix(wire_op->get_matrix());
        break;
    }
  }
  return transform;
}

template <typename CurveType>
using KeyframeType = CurveType::Keyframes::value_type::element_type;

template <typename CurveType>
base::expected<std::unique_ptr<KeyframeType<CurveType>>, std::string>
DeserializeKeyframe(const mojom::AnimationKeyframeValue& value,
                    base::TimeDelta start_time,
                    std::unique_ptr<gfx::TimingFunction> timing_function) {
  using ValueType = std::remove_cvref_t<
      decltype(std::declval<KeyframeType<CurveType>>().Value())>;
  std::unique_ptr<KeyframeType<CurveType>> keyframe;
  if constexpr (std::is_same_v<ValueType, float>) {
    if (value.is_scalar()) {
      keyframe = gfx::FloatKeyframe::Create(start_time, value.get_scalar(),
                                            std::move(timing_function));
    } else {
      return base::unexpected("Invalid keyframe type");
    }
  } else if constexpr (std::is_same_v<ValueType, SkColor>) {
    if (value.is_color()) {
      keyframe = gfx::ColorKeyframe::Create(start_time, value.get_color(),
                                            std::move(timing_function));
    } else {
      return base::unexpected("Invalid keyframe type");
    }
  } else if constexpr (std::is_same_v<ValueType, gfx::SizeF>) {
    if (value.is_size()) {
      keyframe = gfx::SizeKeyframe::Create(start_time, value.get_size(),
                                           std::move(timing_function));
    } else {
      return base::unexpected("Invalid keyframe type");
    }
  } else if constexpr (std::is_same_v<ValueType, gfx::Rect>) {
    if (value.is_rect()) {
      keyframe = gfx::RectKeyframe::Create(start_time, value.get_rect(),
                                           std::move(timing_function));
    } else {
      return base::unexpected("Invalid keyframe type");
    }
  } else if constexpr (std::is_same_v<ValueType, gfx::TransformOperations>) {
    if (value.is_transform()) {
      keyframe = gfx::TransformKeyframe::Create(
          start_time, DeserializeTransformOperations(value.get_transform()),
          std::move(timing_function));
    } else {
      return base::unexpected("Invalid keyframe type");
    }
  } else {
    static_assert(false, "Unsupported curve type");
  }

  if (!keyframe) {
    // This case handles failures from `gfx::Keyframe::Create` calls above
    // if the value was of the correct type but otherwise invalid, or if a
    // new `ValueType` is added to the system without a corresponding
    // `if constexpr` block and `Create` method here.
    return base::unexpected("Invalid keyframe value");
  }
  return keyframe;
}

cc::KeyframeModel::Direction DeserializeAnimationDirection(
    mojom::AnimationDirection direction) {
  switch (direction) {
    case mojom::AnimationDirection::kNormal:
      return cc::KeyframeModel::Direction::NORMAL;
    case mojom::AnimationDirection::kReverse:
      return cc::KeyframeModel::Direction::REVERSE;
    case mojom::AnimationDirection::kAlternateNormal:
      return cc::KeyframeModel::Direction::ALTERNATE_NORMAL;
    case mojom::AnimationDirection::kAlternateReverse:
      return cc::KeyframeModel::Direction::ALTERNATE_REVERSE;
  }
}

cc::KeyframeModel::FillMode DeserializeAnimationFillMode(
    mojom::AnimationFillMode fill_mode) {
  switch (fill_mode) {
    case mojom::AnimationFillMode::kNone:
      return cc::KeyframeModel::FillMode::NONE;
    case mojom::AnimationFillMode::kForwards:
      return cc::KeyframeModel::FillMode::FORWARDS;
    case mojom::AnimationFillMode::kBackwards:
      return cc::KeyframeModel::FillMode::BACKWARDS;
    case mojom::AnimationFillMode::kBoth:
      return cc::KeyframeModel::FillMode::BOTH;
    case mojom::AnimationFillMode::kAuto:
      return cc::KeyframeModel::FillMode::AUTO;
  }
}

template <typename CurveType>
base::expected<void, std::string> DeserializeAnimationCurve(
    const mojom::AnimationKeyframeModel& wire,
    cc::Animation& animation) {
  auto curve = CurveType::Create();
  if (wire.playback_rate == 0.0) {
    return base::unexpected("Invalid playback_rate: cannot be 0");
  }

  ASSIGN_OR_RETURN(auto timing_function,
                   DeserializeTimingFunction(*wire.timing_function));
  curve->SetTimingFunction(std::move(timing_function));
  curve->set_scaled_duration(wire.scaled_duration);
  for (const auto& wire_keyframe : wire.keyframes) {
    std::unique_ptr<gfx::TimingFunction> keyframe_timing_function;
    if (wire_keyframe->timing_function) {
      ASSIGN_OR_RETURN(
          keyframe_timing_function,
          DeserializeTimingFunction(*wire_keyframe->timing_function));
    }
    ASSIGN_OR_RETURN(auto keyframe,
                     DeserializeKeyframe<CurveType>(
                         *wire_keyframe->value, wire_keyframe->start_time,
                         std::move(keyframe_timing_function)));
    curve->AddKeyframe(std::move(keyframe));
  }

  if (wire.group_id == cc::KeyframeModel::kInvalidGroup) {
    return base::unexpected("Invalid group_id");
  }

  auto model = cc::KeyframeModel::Create(
      std::move(curve), wire.id, wire.group_id,
      cc::KeyframeModel::TargetPropertyId(wire.target_property_type));
  model->set_direction(DeserializeAnimationDirection(wire.direction));
  model->set_fill_mode(DeserializeAnimationFillMode(wire.fill_mode));
  model->set_playback_rate(wire.playback_rate);
  model->set_iterations(wire.iterations);
  model->set_iteration_start(wire.iteration_start);
  model->set_start_delay(wire.start_delay);
  model->set_hold_time(wire.hold_time);
  model->set_element_id(wire.element_id);
  animation.keyframe_effect()->AddKeyframeModel(std::move(model));
  return base::ok();
}

base::expected<void, std::string> DeserializeAnimation(
    const mojom::Animation& wire,
    cc::AnimationTimeline& timeline) {
  if (timeline.GetAnimationById(wire.id)) {
    return base::unexpected("Unexpected duplicate animation ID");
  }

  scoped_refptr<cc::Animation> animation = cc::Animation::Create(wire.id);
  timeline.AttachAnimation(animation);

  if (wire.element_id) {
    animation->AttachElement(wire.element_id);
  }

  for (const auto& wire_model : wire.keyframe_models) {
    if (wire_model->keyframes.empty()) {
      return base::unexpected("Unexpected animation with no keyframes");
    }
    // We use the first keyframe to determine the curve type. All keyframes will
    // be validated against this type.
    switch (wire_model->keyframes[0]->value->which()) {
      case mojom::AnimationKeyframeValue::Tag::kScalar:
        RETURN_IF_ERROR(
            DeserializeAnimationCurve<gfx::KeyframedFloatAnimationCurve>(
                *wire_model, *animation));
        break;
      case mojom::AnimationKeyframeValue::Tag::kColor:
        RETURN_IF_ERROR(
            DeserializeAnimationCurve<gfx::KeyframedColorAnimationCurve>(
                *wire_model, *animation));
        break;
      case mojom::AnimationKeyframeValue::Tag::kSize:
      case mojom::AnimationKeyframeValue::Tag::kRect:
        return base::unexpected("Unsupported keyframe value type");
      case mojom::AnimationKeyframeValue::Tag::kTransform:
        RETURN_IF_ERROR(
            DeserializeAnimationCurve<gfx::KeyframedTransformAnimationCurve>(
                *wire_model, *animation));
        break;
    }
  }
  animation->keyframe_effect()->UpdateTickingState();
  return base::ok();
}

base::expected<void, std::string> DeserializeAnimationTimeline(
    const mojom::AnimationTimeline& wire,
    cc::AnimationHost& host) {
  scoped_refptr<cc::AnimationTimeline> timeline = host.GetTimelineById(wire.id);
  bool add_new_timeline = false;
  if (!timeline) {
    timeline = cc::AnimationTimeline::Create(wire.id);
    add_new_timeline = true;
  }
  for (int32_t id : wire.removed_animations) {
    if (auto* animation = timeline->GetAnimationById(id)) {
      timeline->DetachAnimation(animation);
    }
  }
  for (const auto& wire_animation : wire.new_animations) {
    RETURN_IF_ERROR(DeserializeAnimation(*wire_animation, *timeline));
  }
  if (add_new_timeline) {
    host.AddAnimationTimeline(timeline);
  }
  return base::ok();
}

base::expected<void, std::string> DeserializeAnimationUpdates(
    const mojom::LayerTreeUpdate& update,
    cc::AnimationHost& host) {
  if (update.removed_animation_timelines) {
    for (int32_t id : *update.removed_animation_timelines) {
      if (auto* timeline = host.GetTimelineById(id)) {
        host.RemoveAnimationTimeline(timeline);
      }
    }
  }

  if (update.animation_timelines) {
    for (const auto& wire : *update.animation_timelines) {
      RETURN_IF_ERROR(DeserializeAnimationTimeline(*wire, host));
    }
  }

  return base::ok();
}

class DirectLayerTreeFrameSink : public cc::LayerTreeFrameSink {
 public:
  DirectLayerTreeFrameSink(base::WeakPtr<CompositorFrameSinkSupport> support,
                           const LocalSurfaceId& local_surface_id)
      : support_(std::move(support)), local_surface_id_(local_surface_id) {}
  ~DirectLayerTreeFrameSink() override = default;

  // cc::LayerTreeFrameSink overrides:
  void SetLocalSurfaceId(const LocalSurfaceId& local_surface_id) override {
    local_surface_id_ = local_surface_id;
  }

  void SubmitCompositorFrame(CompositorFrame frame,
                             bool hit_test_data_changed) override {
    if (!support_) {
      std::vector<ReturnedResource> returned_resources =
          TransferableResource::ReturnResources(frame.resource_list);
      if (!returned_resources.empty() && client_) {
        client_->ReclaimResources(std::move(returned_resources));
      }
      return;
    }
    support_->MaybeSubmitCompositorFrame(local_surface_id_, std::move(frame),
                                         std::nullopt, 0);
  }

  void DidNotProduceFrame(const BeginFrameAck& ack,
                          cc::FrameSkippedReason reason) override {
    if (support_) {
      support_->DidNotProduceFrame(ack);
    }
  }

  void NotifyNewLocalSurfaceIdExpectedWhilePaused() override {
    if (support_) {
      support_->NotifyNewLocalSurfaceIdExpectedWhilePaused();
    }
  }

 private:
  const base::WeakPtr<CompositorFrameSinkSupport> support_;
  LocalSurfaceId local_surface_id_;
};

}  // namespace

LayerContextImpl::LayerContextImpl(CompositorFrameSinkSupport* compositor_sink,
                                   mojom::PendingLayerContext& context,
                                   mojom::LayerContextSettingsPtr settings)
    : LayerContextImpl(compositor_sink,
                       std::move(settings),
                       std::move(context.receiver),
                       std::move(context.client)) {
  // Always expect valid context receiver & client to be passed to the
  // public constructor.
  CHECK(receiver_);
  CHECK(client_);
}

// static
std::unique_ptr<LayerContextImpl> LayerContextImpl::CreateForTesting(
    CompositorFrameSinkSupport* compositor_sink,
    mojom::LayerContextSettingsPtr settings) {
  return base::WrapUnique<LayerContextImpl>(new LayerContextImpl(
      compositor_sink, std::move(settings),
      mojo::PendingAssociatedReceiver<mojom::LayerContext>(),
      mojo::PendingAssociatedRemote<mojom::LayerContextClient>()));
}

LayerContextImpl::LayerContextImpl(
    CompositorFrameSinkSupport* compositor_sink,
    mojom::LayerContextSettingsPtr settings,
    mojo::PendingAssociatedReceiver<mojom::LayerContext> receiver_pipe,
    mojo::PendingAssociatedRemote<mojom::LayerContextClient> client_pipe)
    : compositor_sink_(compositor_sink),
      task_runner_provider_(cc::TaskRunnerProvider::CreateForDisplayTree(
          base::SingleThreadTaskRunner::GetCurrentDefault())),
      rendering_stats_(cc::RenderingStatsInstrumentation::Create()),
      host_impl_(VizLayerTreeHostImpl::Create(
          GetDisplayTreeSettings(std::move(settings)),
          this,
          task_runner_provider_.get(),
          rendering_stats_.get(),
          /*task_graph_runner=*/nullptr,
          animation_host_->CreateImplInstance(),
          /*dark_mode_filter=*/nullptr,
          GenerateNextDisplayTreeId(),
          /*image_worker_task_runner=*/nullptr,
          /*scheduling_delegate=*/nullptr)) {
  if (receiver_pipe.is_valid() && client_pipe.is_valid()) {
    receiver_ = std::make_unique<mojo::AssociatedReceiver<mojom::LayerContext>>(
        this, std::move(receiver_pipe));
    client_ =
        std::make_unique<mojo::AssociatedRemote<mojom::LayerContextClient>>(
            std::move(client_pipe));
  }
  CHECK(host_impl_->InitializeFrameSink(this));
}

LayerContextImpl::~LayerContextImpl() {
  ResetBoundUnboundedFrameSink();
  DoReturnResources();
  host_impl_->ReleaseLayerTreeFrameSink();
}

void LayerContextImpl::BeginFrame(const BeginFrameArgs& args) {
  if (base::FeatureList::IsEnabled(features::kTreeAnimationsInViz)) {
    compositor_sink_->SetLayerContextWantsBeginFrames(false);
    // TODO(zmo): The stage breakdown var is |start_update_display_tree|.
    // Consider using a difference name, so it works for TreeAnimationsInViz
    // mode as well.
    base::TimeTicks start_begin_frame = base::TimeTicks::Now();
    DoDrawInternal(args, start_begin_frame);
  }
}

void LayerContextImpl::ReceiveReturnsFromParent(
    std::vector<ReturnedResource> resources) {
  // Impl and Main thread task runners are the same. They bind to the viz
  // thread.
  auto* task_runner = task_runner_provider_->MainThreadTaskRunner();
  if (!task_runner->BelongsToCurrentThread()) {
    task_runner->PostTask(
        FROM_HERE,
        base::BindOnce(&LayerContextImpl::ReceiveReturnsFromParent,
                       weak_factory_.GetWeakPtr(), std::move(resources)));
    return;
  }
  host_impl_->resource_provider()->ReceiveReturnsFromParent(
      std::move(resources));
  DoReturnResources();
}

void LayerContextImpl::DoReturnResources() {
  if (!resources_to_return_.empty()) {
    compositor_sink_->DoReturnResources(std::move(resources_to_return_));
  }
}

void LayerContextImpl::HandleBadMojoMessage(const std::string& function,
                                            const std::string& error) {
  receiver_->ReportBadMessage(function + "() : " + error);
}

void LayerContextImpl::DidLoseLayerTreeFrameSinkOnImplThread() {
  NOTREACHED();
}

void LayerContextImpl::SetBeginFrameSource(BeginFrameSource* source) {}

void LayerContextImpl::DidReceiveCompositorFrameAckOnImplThread() {
  NOTIMPLEMENTED();
}

void LayerContextImpl::OnCanDrawStateChanged(bool can_draw) {}

void LayerContextImpl::NotifyReadyToActivate() {}

bool LayerContextImpl::IsReadyToActivate() {
  return false;
}

void LayerContextImpl::NotifyReadyToDraw() {}

void LayerContextImpl::SetNeedsRedrawOnImplThread() {
  if (base::FeatureList::IsEnabled(features::kTreeAnimationsInViz)) {
    compositor_sink_->SetLayerContextWantsBeginFrames(true);
  }
}

void LayerContextImpl::SetNeedsOneBeginImplFrameOnImplThread() {
  if (base::FeatureList::IsEnabled(features::kTreeAnimationsInViz)) {
    compositor_sink_->SetLayerContextWantsBeginFrames(true);
  }
}

void LayerContextImpl::SetNeedsPrepareTilesOnImplThread() {
  NOTREACHED();
}

void LayerContextImpl::SetNeedsCommitOnImplThread(cc::BeginMainFrameReason,
                                                  bool urgent,
                                                  bool unthrottled) {
  NOTREACHED();
}

void LayerContextImpl::SetVideoNeedsBeginFrames(bool needs_begin_frames) {}
void LayerContextImpl::DidChangeBeginFrameSourcePaused(bool paused) {}

void LayerContextImpl::SetDeferBeginMainFrameFromImpl(
    bool defer_begin_main_frame) {}

bool LayerContextImpl::IsInsideDraw() {
  return false;
}

void LayerContextImpl::RenewTreePriority() {}

void LayerContextImpl::PostDelayedAnimationTaskOnImplThread(
    base::OnceClosure task,
    base::TimeDelta delay) {}

void LayerContextImpl::DidActivateSyncTree() {}

void LayerContextImpl::DidPrepareTiles() {}

void LayerContextImpl::DidCompletePageScaleAnimationOnImplThread() {}

void LayerContextImpl::OnDrawForLayerTreeFrameSink(
    bool resourceless_software_draw,
    bool skip_draw) {}

void LayerContextImpl::SetNeedsImplSideInvalidation(
    bool needs_first_draw_on_activation) {}

void LayerContextImpl::NotifyImageDecodeRequestFinished(int request_id,
                                                        bool speculative,
                                                        bool decode_succeeded) {
}

void LayerContextImpl::NotifyTransitionRequestFinished(
    uint32_t sequence_id,
    const ViewTransitionElementResourceRects&) {}

void LayerContextImpl::DidPresentCompositorFrameOnImplThread(
    uint32_t frame_token,
    cc::PresentationTimeCallbackBuffer::PendingCallbacks callbacks,
    const FrameTimingDetails& details) {
  NOTIMPLEMENTED();
}

void LayerContextImpl::NotifyAnimationWorkletStateChange(
    cc::AnimationWorkletMutationState state,
    cc::ElementListType element_list_type) {}

void LayerContextImpl::NotifyPaintWorkletStateChange(
    cc::Scheduler::PaintWorkletState state) {}

void LayerContextImpl::NotifyCompositorMetricsTrackerResults(
    cc::CustomTrackerResults results) {}

bool LayerContextImpl::IsInSynchronousComposite() const {
  return false;
}

void LayerContextImpl::FrameSinksToThrottleUpdated(
    const base::flat_set<FrameSinkId>& ids) {}

void LayerContextImpl::ClearHistory() {}

void LayerContextImpl::SetHasActiveThreadedScroll(bool is_scrolling) {}

void LayerContextImpl::SetWaitingForScrollEvent(bool waiting_for_scroll_event) {
}

size_t LayerContextImpl::CommitDurationSampleCountForTesting() const {
  return 0;
}

void LayerContextImpl::ReturnResource(ReturnedResource returned_resource) {
  resources_to_return_.emplace_back(std::move(returned_resource));
}

void LayerContextImpl::DidObserveFirstScrollDelay(
    int source_frame_number,
    base::TimeDelta first_scroll_delay,
    base::TimeTicks first_scroll_timestamp) {}

bool LayerContextImpl::BindToClient(cc::LayerTreeFrameSinkClient* client) {
  frame_sink_client_ = client;
  return true;
}

void LayerContextImpl::DetachFromClient() {
  frame_sink_client_ = nullptr;
}

void LayerContextImpl::SetLocalSurfaceId(
    const LocalSurfaceId& local_surface_id) {
  // There are a few places that calls this. One is from LayerTreeHostImpl in
  // TreesInViz mode in viz process, and it's unnecessary to call it. The
  // others are from ui/aura/window.cc, and their frame_sink_ should not be
  // LayerContextImpl.
  NOTREACHED();
}

void LayerContextImpl::SubmitCompositorFrame(CompositorFrame frame,
                                             bool hit_test_data_changed) {
  if (!host_impl_->GetCurrentLocalSurfaceId().is_valid()) {
    return;
  }

  std::optional<HitTestRegionList> hit_test_region_list =
      host_impl_->BuildHitTestData();

  // TODO(vmiura): Implement other functionality from
  // AsyncLayerTreeFrameSink::SubmitCompositorFrame()

  auto result = compositor_sink_->MaybeSubmitCompositorFrame(
      host_impl_->GetCurrentLocalSurfaceId(), std::move(frame),
      std::move(hit_test_region_list), 0);
  if (result != SubmitResult::ACCEPTED) {
    client_->ResetWithReason(
        static_cast<uint32_t>(result),
        CompositorFrameSinkSupport::GetSubmitResultAsString(result));
    return;
  }

  if (base::FeatureList::IsEnabled(features::kTreeAnimationsInViz)) {
    constexpr bool start_ready_animations = true;
    host_impl_->UpdateAnimationState(start_ready_animations);
  }
}

void LayerContextImpl::DidNotProduceFrame(const BeginFrameAck& ack,
                                          cc::FrameSkippedReason reason) {
  compositor_sink_->DidNotProduceFrame(ack);
}

void LayerContextImpl::NotifyNewLocalSurfaceIdExpectedWhilePaused() {
  compositor_sink_->NotifyNewLocalSurfaceIdExpectedWhilePaused();
}

void LayerContextImpl::SetVisible(bool visible) {
  host_impl_->SetVisible(visible);
}

void LayerContextImpl::UpdateDisplayTree(mojom::LayerTreeUpdatePtr update) {
  CHECK(receiver_);

  const BeginFrameArgs begin_frame_args = update->begin_frame_args;
  auto start_update_display_tree = base::TimeTicks::Now();
  const bool frame_has_damage = update->frame_has_damage;
  const bool is_flush = update->is_flush;
  auto result = DoUpdateDisplayTree(std::move(update));
  if (!result.has_value()) {
    HandleBadMojoMessage("UpdateDisplayTree", result.error());
    return;
  }

  // If this is a flush-only update, we only want to synchronize the state
  // and return any resources that were released. We skip the draw and
  // expensive post-sync recomputations.
  if (is_flush) {
    DoReturnResources();
    return;
  }

  // After a tree update, either Draw or schedule animations.
  DoDraw(begin_frame_args, start_update_display_tree, frame_has_damage);

  // We may have resources to return after a tree update and draw.
  DoReturnResources();
}

base::expected<void, std::string> LayerContextImpl::DoUpdateDisplayTree(
    mojom::LayerTreeUpdatePtr update) {
  TryBindUnboundedFrameSink();
  TRACE_EVENT0("viz", "LayerContextImpl::DoUpdateDisplayTree");
  cc::LayerTreeImpl& layers = *host_impl_->active_tree();
  cc::PropertyTrees& property_trees = *layers.property_trees();

  // Any update to the display tree requires a new draw properties update if
  // validation fails and returns early, because we may have already mutated
  // some state (like taking render surfaces or resizing trees).
  base::ScopedClosureRunner cleanup(base::BindOnce(
      [](cc::LayerTreeImpl* layers) {
        layers->set_needs_update_draw_properties();
      },
      &layers));

  std::vector<std::unique_ptr<cc::RenderSurfaceImpl>> old_render_surfaces;
  property_trees.effect_tree_mutable().TakeRenderSurfaces(&old_render_surfaces);

  // We resize all property trees first, as layers and property tree nodes
  // themselves may index one or more other property tree nodes. These indices
  // need to be validated, and the dependency can be cyclic (e.g. scroll nodes
  // may index transform nodes and transform nodes may index scroll nodes).
  ASSIGN_OR_RETURN(const bool transform_size_changed,
                   ResizePropertyTree(property_trees.transform_tree_mutable(),
                                      update->num_transform_nodes));
  ASSIGN_OR_RETURN(const bool clip_size_changed,
                   ResizePropertyTree(property_trees.clip_tree_mutable(),
                                      update->num_clip_nodes));
  const bool effect_size_increased =
      update->num_effect_nodes > property_trees.effect_tree().nodes().size();
  ASSIGN_OR_RETURN(const bool effect_size_changed,
                   ResizePropertyTree(property_trees.effect_tree_mutable(),
                                      update->num_effect_nodes));
  ASSIGN_OR_RETURN(const bool scroll_size_changed,
                   ResizePropertyTree(property_trees.scroll_tree_mutable(),
                                      update->num_scroll_nodes));

  // Transform tree properties need to update before its nodes are updated, as
  // the nodes may index properties on the tree itself (e.g. scroll
  // constraints). Note that these properties may also index scroll and
  // transform nodes, so they must be deserialized after the trees are resized
  // above.
  bool transform_properties_changed = false;
  bool transform_layer_properties_changed = false;
  if (update->transform_tree_update) {
    transform_properties_changed = true;
    ASSIGN_OR_RETURN(
        transform_layer_properties_changed,
        UpdateTransformTreeProperties(property_trees,
                                      property_trees.transform_tree_mutable(),
                                      *update->transform_tree_update));
    if (transform_layer_properties_changed) {
      layers.set_needs_update_draw_properties();
    }
  }

  bool scroll_properties_changed = false;
  if (update->scroll_tree_update) {
    ASSIGN_OR_RETURN(scroll_properties_changed,
                     UpdateScrollTreeProperties(
                         property_trees, property_trees.scroll_tree_mutable(),
                         *update->scroll_tree_update));
    if (scroll_properties_changed) {
      layers.set_needs_update_draw_properties();
    }
  }

  ASSIGN_OR_RETURN(const bool transform_nodes_changed,
                   UpdatePropertyTree(property_trees,
                                      property_trees.transform_tree_mutable(),
                                      update->transform_nodes));
  ASSIGN_OR_RETURN(
      const bool clip_nodes_changed,
      UpdatePropertyTree(property_trees, property_trees.clip_tree_mutable(),
                         update->clip_nodes));
  ASSIGN_OR_RETURN(
      const bool effect_nodes_changed,
      UpdatePropertyTree(property_trees, property_trees.effect_tree_mutable(),
                         update->effect_nodes));
  ASSIGN_OR_RETURN(
      const bool scroll_nodes_changed,
      UpdatePropertyTree(property_trees, property_trees.scroll_tree_mutable(),
                         update->scroll_nodes));

  // Property tree updates may have invalidated the existing viewport property
  // ID values. Update them now.
  RETURN_IF_ERROR(UpdateViewportPropertyIds(layers, property_trees, *update));

  // Pull any copy output requests that came in over the wire.
  for (const auto& wire : update->effect_nodes) {
    for (auto&& copy_request : wire->copy_output_requests) {
      property_trees.effect_tree_mutable().AddCopyRequest(
          wire->id, std::move(copy_request));
    }
  }

  if (update->surface_ranges) {
    base::flat_set<SurfaceRange> surface_ranges(std::from_range,
                                                *(update->surface_ranges));
    layers.ClearSurfaceRanges();
    layers.SetSurfaceRanges(surface_ranges);
  }

  if (update->view_transition_requests) {
    DeserializeViewTransitionRequests(layers,
                                      *(update->view_transition_requests));
  }

  RETURN_IF_ERROR(CreateOrUpdateLayers(
      *(this->host_impl_.get()), update->layers, update->layer_order, layers));

  // After layers are updated, validate backdrop_mask_element_id.
  for (const auto& wire : update->effect_nodes) {
    if (wire->backdrop_mask_element_id) {
      if (auto* layer =
              layers.LayerByElementId(wire->backdrop_mask_element_id)) {
        if (layer->GetLayerType() != cc::mojom::LayerType::kTileDisplay) {
          return base::unexpected(base::StrCat(
              {"Invalid backdrop_mask_element_id (",
               base::NumberToString(
                   wire->backdrop_mask_element_id.GetInternalValue()),
               ") on effect node ", base::NumberToString(wire->id),
               ": layer is not a TileDisplayLayer"}));
        }
      } else {
        return base::unexpected(base::StrCat(
            {"Invalid backdrop_mask_element_id (",
             base::NumberToString(
                 wire->backdrop_mask_element_id.GetInternalValue()),
             ") on effect node ", base::NumberToString(wire->id),
             ": layer not found. Total layers: ",
             base::NumberToString(layers.NumLayers())}));
      }
    }
  }

  if (update->local_surface_id_from_parent) {
    layers.SetLocalSurfaceIdFromParent(*update->local_surface_id_from_parent);
  }

  // Regular updates (non-flush) must provide a valid current LocalSurfaceId.
  // During backgrounding (flush updates), this may be omitted if the renderer
  // no longer has a valid ID.
  if (!update->is_flush) {
    RETURN_IF_FALSE(update->current_local_surface_id,
                    "Missing current_local_surface_id in non-flush update");
  }

  if (update->current_local_surface_id) {
    host_impl_->set_current_local_surface_id_from_client(
        *update->current_local_surface_id);
  }

  RETURN_IF_FALSE(update->next_frame_token > 0, "invalid frame token");
  host_impl_->set_next_frame_token_from_client(update->next_frame_token);

  host_impl_->set_tracked_element_rects_from_client(
      std::move(update->tracked_element_rects));

  for (const auto& latency : update->latency_info) {
    if (latency.terminated()) {
      return base::unexpected("Received already-terminated LatencyInfo");
    }
    layers.QueuePinnedSwapPromise(
        std::make_unique<cc::LatencyInfoSwapPromise>(latency));
  }

  host_impl_->set_send_frame_token_to_embedder(
      update->send_frame_token_to_embedder);
  host_impl_->set_is_handling_interaction_from_client(
      update->is_handling_interaction);
  if (update->delegated_ink_metadata) {
    layers.set_delegated_ink_metadata(
        std::make_unique<gfx::DelegatedInkMetadata>(
            *update->delegated_ink_metadata));
  } else {
    layers.clear_delegated_ink_metadata();
  }

  if (update->screenshot_destination) {
    host_impl_->SetScreenshotDestinationToken(
        update->screenshot_destination->value());
  }

  host_impl_->SetMayThrottleIfUndrawnFrames(
      update->may_throttle_if_undrawn_frames);
  host_impl_->set_viewport_mobile_optimized(
      update->is_viewport_mobile_optimized);
  layers.set_is_animating_hud_contents(update->is_animating_hud_contents);

  {
    TRACE_EVENT1("viz", "DeserializeTilings", "TilingCount",
                 update->tilings.size());
    for (const auto& tiling : update->tilings) {
      if (cc::LayerImpl* layer = layers.LayerById(tiling->layer_id)) {
        if (layer->GetLayerType() != cc::mojom::LayerType::kTileDisplay) {
          return base::unexpected("Invalid tile update");
        }
        RETURN_IF_ERROR(DeserializeTiling(
            host_impl_.get(), static_cast<cc::TileDisplayLayerImpl&>(*layer),
            *tiling));
      }
    }
  }

  // Call UpdateViewportPropertyIds() to set layer properties (namely,
  // is_inner_viewport_scroll_layer_). This needs to happen after
  // CreateOrUpdateLayers() sets layer element IDs and before
  // SetPageScaleFactorAndLimitsForDisplayTree().
  RETURN_IF_ERROR(UpdateViewportPropertyIds(layers, property_trees, *update));

  layers.set_background_color(update->background_color);
  layers.set_source_frame_number(update->source_frame_number);
  layers.set_trace_id(
      cc::BeginMainFrameTraceId::FromUnsafeValue(update->trace_id));
  layers.set_primary_main_frame_item_sequence_number(
      update->primary_main_frame_item_sequence_number);
  layers.SetDeviceViewportRect(update->device_viewport);

  layers.RegisterSelection(update->selection);

  if (update->page_scale_factor <= 0 ||
      !std::isfinite(update->page_scale_factor) ||
      update->min_page_scale_factor <= 0 ||
      !std::isfinite(update->min_page_scale_factor) ||
      update->max_page_scale_factor <= 0 ||
      !std::isfinite(update->max_page_scale_factor) ||
      update->min_page_scale_factor > update->max_page_scale_factor) {
    return base::unexpected("Invalid page scale factors");
  }
  layers.SetPageScaleFactorAndLimitsForDisplayTree(
      update->page_scale_factor, update->min_page_scale_factor,
      update->max_page_scale_factor);

  if (update->external_page_scale_factor <= 0 ||
      !std::isfinite(update->external_page_scale_factor)) {
    return base::unexpected("Invalid external page scale factor");
  }
  // The renderer has already applied this scale to serialized effect nodes, so
  // Viz cannot rediscover the change while updating surface contents scales.
  const bool external_page_scale_factor_changed =
      layers.external_page_scale_factor() != update->external_page_scale_factor;
  layers.SetExternalPageScaleFactor(update->external_page_scale_factor);

  if (update->device_scale_factor <= 0 ||
      !std::isfinite(update->device_scale_factor)) {
    return base::unexpected("Invalid device scale factor");
  }
  layers.SetDeviceScaleFactor(update->device_scale_factor);
  if (update->painted_device_scale_factor <= 0 ||
      !std::isfinite(update->painted_device_scale_factor)) {
    return base::unexpected("Invalid painted device scale factor");
  }
  if (update->max_safe_area_inset_bottom < 0 ||
      !std::isfinite(update->max_safe_area_inset_bottom)) {
    return base::unexpected("Invalid max safe area inset bottom");
  }
  if (!std::isfinite(update->browser_controls_params.top_controls_height) ||
      !std::isfinite(update->browser_controls_params.top_controls_min_height) ||
      !std::isfinite(update->browser_controls_params.bottom_controls_height) ||
      !std::isfinite(
          update->browser_controls_params.bottom_controls_min_height)) {
    return base::unexpected("Invalid browser controls params");
  }
  update->browser_controls_params.top_controls_height =
      std::max(0.f, update->browser_controls_params.top_controls_height);
  update->browser_controls_params.top_controls_min_height =
      std::clamp(update->browser_controls_params.top_controls_min_height, 0.f,
                 update->browser_controls_params.top_controls_height);
  update->browser_controls_params.bottom_controls_height =
      std::max(0.f, update->browser_controls_params.bottom_controls_height);
  update->browser_controls_params.bottom_controls_min_height =
      std::clamp(update->browser_controls_params.bottom_controls_min_height,
                 0.f, update->browser_controls_params.bottom_controls_height);
  layers.SetBrowserControlsParams(update->browser_controls_params);
  host_impl_->browser_controls_manager()->SetOffsetTagModifications(
      update->browser_controls_offset_tag_modifications);

  layers.set_display_transform_hint(update->display_transform_hint);
  layers.SetMaxSafeAreaInsetBottom(update->max_safe_area_inset_bottom);
  layers.set_painted_device_scale_factor(update->painted_device_scale_factor);
  layers.SetDisplayColorSpaces(update->display_color_spaces);

  host_impl_->SetCurrentBrowserControlsShownRatio(
      update->top_controls_shown_ratio, update->bottom_controls_shown_ratio);

  host_impl_->SetViewportDamage(update->viewport_damage_rect);
  host_impl_->SetRootLayerDamageRect(update->root_layer_damage_rect);
  host_impl_->SetDebugState(update->debug_state);

  for (auto& ui_resource_request : update->ui_resource_requests) {
    if (ui_resource_request->type ==
        mojom::TransferableUIResourceRequest::Type::kCreate) {
      if (!ui_resource_request->transferable_resource ||
          ui_resource_request->transferable_resource->is_empty()) {
        return base::unexpected(
            "Invalid transferable resource in UI resource creation");
      }
      if (ui_resource_request->transferable_resource->GetSize().width() <= 0 ||
          ui_resource_request->transferable_resource->GetSize().height() <= 0) {
        return base::unexpected(
            "Invalid dimensions for transferable UI resource.");
      }
      ReleaseCallback release_callback = base::BindOnce(
          [](cc::LayerTreeHostImpl* host_impl, ResourceId id,
             scoped_refptr<gpu::ClientSharedImage> shared_image,
             const gpu::SyncToken& sync_token, bool is_lost) {
            host_impl->ReturnResource({id, shared_image->EndImport(sync_token),
                                       /*release_fence=*/gfx::GpuFenceHandle(),
                                       /*count=*/1, is_lost});
          },
          host_impl_.get(), ui_resource_request->transferable_resource->id,
          ui_resource_request->transferable_resource->shared_image());

      auto resource_id = host_impl_->resource_provider()->ImportResource(
          ui_resource_request->transferable_resource.value(),
          /*impl_release_callback=*/std::move(release_callback),
          /*main_thread_release_callback=*/base::NullCallback(),
          /*evicted_callback=*/base::NullCallback());

      host_impl_->CreateUIResourceFromImportedResource(
          ui_resource_request->uid, resource_id,
          ui_resource_request->transferable_resource->GetSize(),
          ui_resource_request->opaque);
    } else {
      host_impl_->DeleteUIResource(ui_resource_request->uid);
    }
  }

  const bool viewport_deltas_changed =
      property_trees.inner_viewport_container_bounds_delta() !=
          update->inner_viewport_container_bounds_delta ||
      property_trees.outer_viewport_container_bounds_delta() !=
          update->outer_viewport_container_bounds_delta;
  property_trees.SetInnerViewportContainerBoundsDelta(
      update->inner_viewport_container_bounds_delta);
  property_trees.SetOuterViewportContainerBoundsDelta(
      update->outer_viewport_container_bounds_delta);

  property_trees.UpdateChangeTracking();
  property_trees.transform_tree_mutable().set_needs_update(
      transform_size_changed || transform_properties_changed ||
      transform_nodes_changed ||
      property_trees.transform_tree().needs_update());
  property_trees.clip_tree_mutable().set_needs_update(
      clip_size_changed || clip_nodes_changed ||
      // External scale changes surface contents scales, which affects clips
      // expressed in surface space.
      external_page_scale_factor_changed ||
      property_trees.clip_tree().needs_update());
  property_trees.effect_tree_mutable().set_needs_update(
      effect_size_changed || effect_nodes_changed ||
      property_trees.effect_tree().needs_update());

  const bool any_tree_except_effect_size_changed =
      viewport_deltas_changed || transform_size_changed ||
      transform_nodes_changed || clip_size_changed || clip_nodes_changed ||
      effect_nodes_changed || scroll_size_changed || scroll_nodes_changed;
  const bool any_tree_changed =
      any_tree_except_effect_size_changed || effect_size_changed;
  property_trees.set_changed(any_tree_changed);
  if (any_tree_changed) {
    property_trees.ResetCachedData();

    // Any property tree change normally requires a draw property update.
    // However, if the only change is that some effect nodes were removed, we
    // can defer the update until we determine if any render surfaces were
    // removed. This is handled below.
    if (any_tree_except_effect_size_changed || effect_size_increased) {
      layers.set_needs_update_draw_properties();
    }
  }

  // Ensure all property tree and layer indices are valid.
  RETURN_IF_ERROR(ValidateTreeIndices(layers));

  const bool render_surfaces_changed =
      property_trees.effect_tree_mutable().CreateOrReuseRenderSurfaces(
          &old_render_surfaces, &layers);
  if (render_surfaces_changed) {
    layers.set_needs_update_draw_properties();
  }
  // Set this last, making sure renderer side state isn't overwritten by other
  // updates. As this is a transient property, we should set but not clear it.
  if (update->full_tree_damaged) {
    property_trees.set_full_tree_damaged(true);
    layers.set_needs_update_draw_properties();
  }

  // Safe down-cast: AnimationHost is the only subclass of MutatorHost.
  auto* animation_host = static_cast<cc::AnimationHost*>(layers.mutator_host());
  RETURN_IF_ERROR(DeserializeAnimationUpdates(*update, *animation_host));
  host_impl_->ActivateAnimations();

  // Only propagate property tree changes to layers if the property trees
  // actually changed. This avoids redundant work and prevents incorrectly
  // flagging draw properties as needing an update when no relevant properties
  // have changed.
  if (any_tree_changed || scroll_properties_changed ||
      transform_layer_properties_changed || update->full_tree_damaged) {
    layers.MoveChangeTrackingToLayers();
  }

  cleanup.ReplaceClosure(base::DoNothing());
  return base::ok();
}

void LayerContextImpl::DoDraw(const BeginFrameArgs& begin_frame_args,
                              base::TimeTicks start_update_display_tree,
                              bool frame_has_damage) {
  if (base::FeatureList::IsEnabled(features::kTreeAnimationsInViz)) {
    compositor_sink_->SetLayerContextWantsBeginFrames(true);
  } else {
    DoDrawInternal(begin_frame_args, start_update_display_tree,
                   frame_has_damage);
  }
}

void LayerContextImpl::DoDrawInternal(const BeginFrameArgs& begin_frame_args,
                                      base::TimeTicks start_update_display_tree,
                                      std::optional<bool> frame_has_damage) {
  TRACE_EVENT0("viz", "LayerContextImpl::DoDrawInternal");

  // If Renderer marks the frame as NOT damaged, then Viz should skip drawing.
  if (frame_has_damage && !frame_has_damage.value()) {
    return;
  }

  // Client/Renderer will never call UpdateDisplayTree if CanDraw() is false.
  // (crbug.com/454680865): Using DUMP_WILL_BE_CHECK allows all non official
  // builds to fail the check whereas official builds dumps without crashing.
  DUMP_WILL_BE_CHECK(host_impl_->CanDraw());

  host_impl_->WillBeginImplFrame(begin_frame_args);

  cc::FrameData frame;
  TreesInVizTiming stage_breakdown;
  stage_breakdown.start_update_display_tree = start_update_display_tree;
  // TODO(vmiura): Manage these flags properly.
  const bool has_damage = true;
  frame.begin_frame_ack = BeginFrameAck(begin_frame_args, has_damage);
  frame.origin_begin_main_frame_args = begin_frame_args;
  stage_breakdown.start_prepare_to_draw = base::TimeTicks::Now();

  auto expects_to_draw = frame_has_damage.value_or(false);
  auto draw_result = host_impl_->PrepareToDraw(&frame, expects_to_draw);

  // If a frame is expected to be drawn, then draw should succeed.
  if (expects_to_draw) {
    DUMP_WILL_BE_CHECK_EQ(draw_result, cc::DrawResult::kSuccess);
  }

  // Notifies the client which of the tilings it nominated for deletion are
  // actually safe to delete. This is done after PrepareToDraw() so that we have
  // the most up-to-date information on which tilings were used for the current
  // frame.
  SendTilingsCleanupNotificationToClient();

  stage_breakdown.start_draw_layers = base::TimeTicks::Now();
  frame.set_trees_in_viz_timestamps(std::move(stage_breakdown));

  // |DrawLayers| is expected to succeed. Adding a check to ensure that
  // is happening.
  std::optional<cc::SubmitInfo> submit_info = host_impl_->DrawLayers(&frame);
  DUMP_WILL_BE_CHECK(submit_info.has_value());

  host_impl_->DidDrawAllLayers(frame);
  host_impl_->DidFinishImplFrame(begin_frame_args);
}

void LayerContextImpl::SendTilingsCleanupNotificationToClient() {
  for (cc::LayerImpl* layer : *host_impl_->active_tree()) {
    if (layer->GetLayerType() == cc::mojom::LayerType::kTileDisplay) {
      auto* tile_layer = static_cast<cc::TileDisplayLayerImpl*>(layer);
      std::vector<float> scales_to_remove =
          tile_layer->GetSafeToDeleteTilings();
      if (!scales_to_remove.empty()) {
        client_->get()->OnTilingsReadyForCleanup(tile_layer->id(),
                                                 scales_to_remove);
      }
    }
  }
}

void LayerContextImpl::UpdateDisplayTiling(mojom::TilingPtr tiling) {
  CHECK(receiver_);
  auto result = DoUpdateDisplayTiling(std::move(tiling));
  if (!result.has_value()) {
    HandleBadMojoMessage("UpdateDisplayTiling", result.error());
  }
}

base::expected<void, std::string> LayerContextImpl::DoUpdateDisplayTiling(
    mojom::TilingPtr tiling) {
  cc::LayerTreeImpl& layers = *host_impl_->active_tree();
  if (cc::LayerImpl* layer = layers.LayerById(tiling->layer_id)) {
    if (layer->GetLayerType() != cc::mojom::LayerType::kTileDisplay) {
      return base::unexpected("Invalid tile update");
    }

    return DeserializeTiling(host_impl_.get(),
                             static_cast<cc::TileDisplayLayerImpl&>(*layer),
                             *tiling);
  }
  return base::ok();
}

void LayerContextImpl::SetTargetLocalSurfaceId(
    const LocalSurfaceId& target_local_surface_id) {
  CHECK(receiver_);
  auto result = DoSetTargetLocalSurfaceId(target_local_surface_id);
  if (!result.has_value()) {
    HandleBadMojoMessage("SetTargetLocalSurfaceId", result.error());
  }
}

base::expected<void, std::string> LayerContextImpl::DoSetTargetLocalSurfaceId(
    const LocalSurfaceId& target_local_surface_id) {
  if (!target_local_surface_id.is_valid()) {
    return base::unexpected("Invalid target_local_surface_id");
  }
  host_impl_->SetTargetLocalSurfaceId(target_local_surface_id);
  return base::ok();
}

void LayerContextImpl::SetUnboundedFrameSinkId(
    const FrameSinkId& frame_sink_id,
    const LocalSurfaceId& local_surface_id) {
  // Unbounded surface FrameSinkIds are allocated by the browser process, so
  // they must not use the renderer's own client_id namespace.
  if (frame_sink_id.client_id() ==
      compositor_sink_->frame_sink_id().client_id()) {
    HandleBadMojoMessage("SetUnboundedFrameSinkId",
                         "Invalid client_id for Unbounded Element");
    return;
  }
  pending_unbounded_frame_sink_id_ = frame_sink_id;
  pending_unbounded_local_surface_id_ = local_surface_id;
  TryBindUnboundedFrameSink();
}

void LayerContextImpl::ResetBoundUnboundedFrameSink() {
  if (bound_unbounded_frame_sink_id_.is_valid()) {
    if (auto* support =
            compositor_sink_->frame_sink_manager()->GetFrameSinkForId(
                bound_unbounded_frame_sink_id_)) {
      support->set_resource_return_delegate(nullptr);
    }
    bound_unbounded_frame_sink_id_ = FrameSinkId();
  }
  unbounded_support_.reset();
}

void LayerContextImpl::SetUnboundedLocalSurfaceId(
    const LocalSurfaceId& local_surface_id) {
  if (pending_unbounded_frame_sink_id_.is_valid()) {
    pending_unbounded_local_surface_id_ = local_surface_id;
  } else {
    host_impl_->SetUnboundedLocalSurfaceId(local_surface_id);
  }
}

void LayerContextImpl::DismissUnboundedFrameSink() {
  ResetBoundUnboundedFrameSink();
  pending_unbounded_frame_sink_id_ = FrameSinkId();
  pending_unbounded_local_surface_id_ = LocalSurfaceId();
  host_impl_->DismissUnboundedFrameSink();
}

void LayerContextImpl::TryBindUnboundedFrameSink() {
  if (!pending_unbounded_frame_sink_id_.is_valid()) {
    return;
  }
  if (!compositor_sink_->frame_sink_manager()->IsValidUnboundedFrameSinkId(
          compositor_sink_->frame_sink_id(),
          pending_unbounded_frame_sink_id_)) {
    return;
  }
  if (bound_unbounded_frame_sink_id_.is_valid()) {
    ResetBoundUnboundedFrameSink();
    host_impl_->DismissUnboundedFrameSink();
  }
  CompositorFrameSinkSupport* support =
      compositor_sink_->frame_sink_manager()->GetFrameSinkForId(
          pending_unbounded_frame_sink_id_);
  if (!support) {
    // When TreesInViz is enabled, the browser process does not create a
    // CompositorFrameSink via IPC for unbounded elements. Auto-instantiate an
    // internal CompositorFrameSinkSupport here so Viz can composite and
    // render the unbounded element's surface.
    CHECK(base::FeatureList::IsEnabled(features::kTreesInViz));
    unbounded_support_ = std::make_unique<CompositorFrameSinkSupport>(
        /*client=*/nullptr, compositor_sink_->frame_sink_manager(),
        pending_unbounded_frame_sink_id_, /*is_root=*/false);
    support = unbounded_support_.get();
  }
  DCHECK(!support->resource_return_delegate());
  support->set_resource_return_delegate(this);
  bound_unbounded_frame_sink_id_ = pending_unbounded_frame_sink_id_;
  auto direct_sink = std::make_unique<DirectLayerTreeFrameSink>(
      support->GetWeakPtr(), pending_unbounded_local_surface_id_);
  host_impl_->SetUnboundedFrameSink(std::move(direct_sink),
                                    pending_unbounded_local_surface_id_);
  pending_unbounded_frame_sink_id_ = FrameSinkId();
  pending_unbounded_local_surface_id_ = LocalSurfaceId();
}

}  // namespace viz
