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

#include "content/browser/surface_embed/surface_embed_connector_impl.h"

#include "base/check_is_test.h"
#include "build/build_config.h"
#include "components/input/cursor_manager.h"
#include "components/input/render_widget_host_input_event_router.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_host_manager.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_delegate.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents_observer.h"
#include "mojo/public/cpp/bindings/message.h"
#include "third_party/blink/public/common/frame/frame_visual_properties.h"
#include "third_party/blink/public/mojom/frame/intrinsic_sizing_info.mojom.h"
#include "third_party/blink/public/mojom/frame/lifecycle.mojom-shared.h"
#include "third_party/blink/public/mojom/input/pointer_lock_result.mojom.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/base/cursor/cursor.h"
#include "ui/compositor/compositor.h"

#if BUILDFLAG(IS_ANDROID)
#include "ui/android/view_android.h"
#include "ui/android/window_android.h"
#include "ui/android/window_android_compositor.h"
#endif

namespace content {

// Forwards notifications about the child web contents to the connector.
class SurfaceEmbedConnectorImpl::WCObserver : public WebContentsObserver {
 public:
  explicit WCObserver(SurfaceEmbedConnectorImpl* surface_embed_connector,
                      WebContents* child_web_contents)
      : WebContentsObserver(child_web_contents),
        surface_embed_connector_(surface_embed_connector) {}

  ~WCObserver() override = default;

  // WebContentsObserver:
  void RenderFrameHostChanged(RenderFrameHost* old_host,
                              RenderFrameHost* new_host) override {
    // Re-stitch for the new main frame. UpdateAccessibilityTree() resets the
    // stored embed parent first, so a stale relationship is not observed during
    // or after cross-document navigation. Refresh the outgoing frame's AX data
    // so it reflects the cleared relationship.
    surface_embed_connector_->UpdateAccessibilityTree();
    if (old_host) {
      static_cast<RenderFrameHostImpl*>(old_host)->UpdateAXTreeData();
    }
  }

  void AXTreeIDForMainFrameHasChanged() override {
    surface_embed_connector_->UpdateAccessibilityTree();
  }

 private:
  raw_ptr<SurfaceEmbedConnectorImpl> surface_embed_connector_;
};

// Observes the parent WebContents to propagate visibility changes.
//
// LIFECYCLE NOTE: Unlike older implementation patterns that explicitly reset
// and deleted this observer object immediately during `WebContentsDestroyed`,
// this object is now left alive as a dormant `unique_ptr` for the remaining
// lifetime of the `SurfaceEmbedConnectorImpl`. We rely on the base
// `WebContentsObserver` to safely detach from the parent `WebContents` when it
// is destroyed, preventing further callbacks.
class SurfaceEmbedConnectorImpl::ParentWCObserver : public WebContentsObserver {
 public:
  ParentWCObserver(SurfaceEmbedConnectorImpl* surface_embed_connector,
                   WebContents* parent_web_contents)
      : WebContentsObserver(parent_web_contents),
        surface_embed_connector_(surface_embed_connector) {}

  ~ParentWCObserver() override = default;

  // WebContentsObserver:
  void OnVisibilityChanged(Visibility visibility) override {
    surface_embed_connector_->ParentVisibilityChanged(visibility);
  }

  // NOTE: We deliberately do NOT implement WebContentsDestroyed() here because
  // it is called in the middle of WebContentsImpl destructor, making it unsafe
  // for outliving objects to rely on. Instead, cleanup happens automatically
  // via ResetWebContents() in the base WebContentsObserver and the invalidation
  // of the WeakPtr parent_web_contents_.

 private:
  raw_ptr<SurfaceEmbedConnectorImpl> surface_embed_connector_;
};

// static
void SurfaceEmbedConnector::Attach(WebContents* child_web_contents,
                                   RenderFrameHost* outer_document_rfh,
                                   SurfaceEmbedConnector::Delegate* delegate) {
  CHECK(child_web_contents);
  CHECK(outer_document_rfh);
  WebContents* parent_web_contents =
      WebContents::FromRenderFrameHost(outer_document_rfh);
  CHECK(parent_web_contents);
  // Must Detach the child before re-Attaching.
  CHECK(!child_web_contents->GetSurfaceEmbedConnector());
  auto connector = base::WrapUnique(new SurfaceEmbedConnectorImpl(
      child_web_contents, parent_web_contents, outer_document_rfh, delegate));
  static_cast<WebContentsImpl*>(child_web_contents)
      ->SetSurfaceEmbedConnector(std::move(connector));
}

// static
void SurfaceEmbedConnector::Detach(WebContents* child_web_contents) {
  if (auto* connector = static_cast<SurfaceEmbedConnectorImpl*>(
          child_web_contents->GetSurfaceEmbedConnector())) {
    // Note: we set visibility to not-rendered prior to detachment because if
    // the WebContents isn't attached to any surface, it won't be rendered so it
    // SHOULD have the visibility of kNotRendered, to prevent
    // visibility/intersection notifications from being sent to it.
    connector->OnVisibilityChanged(blink::mojom::FrameVisibility::kNotRendered);

    // Clear the container accessibility info so we don't try to stitch later.
    connector->SetParentAccessibilityInfo(ui::kInvalidAXNodeID,
                                          ui::AXTreeIDUnknown());
  }

  // Frees the connector and refreshes the child main frame's AX data.
  static_cast<WebContentsImpl*>(child_web_contents)
      ->ClearSurfaceEmbedConnector();
}

SurfaceEmbedConnectorImpl::SurfaceEmbedConnectorImpl(
    WebContents* child_web_contents,
    WebContents* parent_web_contents,
    RenderFrameHost* embedder_rfh,
    SurfaceEmbedConnector::Delegate* delegate)
    : delegate_(delegate),
      child_web_contents_(static_cast<WebContentsImpl*>(child_web_contents)),
      // Rely on Chromium's WeakPtrFactory to automatically invalidate this
      // pointer safely at the start of parent_web_contents's destructor.
      parent_web_contents_(parent_web_contents->GetWeakPtr()),
      embedder_rfh_(
          static_cast<RenderFrameHostImpl*>(embedder_rfh)->GetWeakPtr()) {
  CHECK_EQ(WebContents::FromRenderFrameHost(embedder_rfh), parent_web_contents);
  wc_observer_ = std::make_unique<WCObserver>(this, child_web_contents);
  parent_wc_observer_ =
      std::make_unique<ParentWCObserver>(this, parent_web_contents);
  CHECK(current_child_frame_host());

  // Current_child_frame_host must be the primary main frame of the child
  // WebContents.
  CHECK_EQ(current_child_frame_host()->GetOutermostMainFrameOrEmbedder(),
           current_child_frame_host());
  screen_infos_ =
      current_child_frame_host()->GetRenderWidgetHost()->GetScreenInfos();
}

SurfaceEmbedConnectorImpl::~SurfaceEmbedConnectorImpl() {
  SetView(nullptr, /*allow_paint_holding=*/false);
}

WebContentsView* SurfaceEmbedConnectorImpl::GetParentWebContentsView() const {
  return parent_web_contents() ? parent_web_contents()->GetView() : nullptr;
}

RenderViewHostDelegateView*
SurfaceEmbedConnectorImpl::GetParentRenderViewHostDelegateView() const {
  return parent_web_contents() ? parent_web_contents()->GetDelegateView()
                               : nullptr;
}

input::RenderWidgetHostInputEventRouter*
SurfaceEmbedConnectorImpl::GetInputEventRouter() {
  return parent_web_contents() ? parent_web_contents()->GetInputEventRouter()
                               : nullptr;
}

TextInputManager* SurfaceEmbedConnectorImpl::GetTextInputManager() {
  return parent_web_contents() ? parent_web_contents()->GetTextInputManager()
                               : nullptr;
}

WebContentsDelegate* SurfaceEmbedConnectorImpl::GetFirstWebContentsDelegate()
    const {
  return parent_web_contents()
             ? parent_web_contents()->GetFirstWebContentsDelegate()
             : nullptr;
}

bool SurfaceEmbedConnectorImpl::HasPointerLockWidgetInParentChain() const {
  return parent_web_contents() &&
         parent_web_contents()->HasPointerLockWidgetInParentChain();
}

void SurfaceEmbedConnectorImpl::SetPointerLockWidgetInParentChain(
    RenderWidgetHostImpl* widget) {
  if (parent_web_contents()) {
    parent_web_contents()->SetPointerLockWidgetInParentChain(widget);
  }
}

bool SurfaceEmbedConnectorImpl::HasPointerLock(
    RenderWidgetHostImpl* render_widget_host) const {
  return parent_web_contents() &&
         parent_web_contents()->HasPointerLock(render_widget_host);
}

RenderWidgetHostImpl* SurfaceEmbedConnectorImpl::GetPointerLockWidget() const {
  return parent_web_contents() ? parent_web_contents()->GetPointerLockWidget()
                               : nullptr;
}

SurfaceEmbedConnector::Delegate* SurfaceEmbedConnectorImpl::GetDelegate() {
  return delegate_;
}

const viz::FrameSinkId& SurfaceEmbedConnectorImpl::GetFrameSinkId() const {
  return frame_sink_id_;
}

void SurfaceEmbedConnectorImpl::OnSynchronizeVisualProperties(
    const blink::FrameVisualProperties& visual_properties) {
  // If the `rect_in_local_root` or current ScreenInfo of the frame has
  // changed, then the viz::LocalSurfaceId must also change.
  if ((last_received_local_frame_size_ != visual_properties.local_frame_size ||
       screen_infos_.current() != visual_properties.screen_infos.current() ||
       last_received_zoom_level_ != visual_properties.zoom_level ||
       last_received_css_zoom_factor_ != visual_properties.css_zoom_factor) &&
      local_surface_id_ == visual_properties.local_surface_id) {
    mojo::ReportBadMessage(
        "SurfaceEmbedConnectorImpl: Resize parameters changed but the local "
        "surface ID remained unchanged.");
    return;
  }
  SynchronizeVisualProperties(visual_properties, true);
}

void SurfaceEmbedConnectorImpl::UpdateRenderThrottlingStatus(
    bool is_throttled,
    bool subtree_throttled,
    bool display_locked) {
  if (is_throttled != is_throttled_ ||
      subtree_throttled != subtree_throttled_ ||
      display_locked != display_locked_) {
    is_throttled_ = is_throttled;
    subtree_throttled_ = subtree_throttled;
    display_locked_ = display_locked;
    if (view_) {
      view_->UpdateRenderThrottlingStatus();
    }
  }
}

// static
WebContentsImpl* SurfaceEmbedConnectorImpl::GetParentWebContents(
    WebContentsImpl* web_contents) {
  if (SurfaceEmbedConnector* connector =
          web_contents->GetSurfaceEmbedConnector()) {
    return static_cast<SurfaceEmbedConnectorImpl*>(connector)
        ->parent_web_contents();
  }
  return web_contents->GetOuterWebContents();
}

// static
WebContentsImpl* SurfaceEmbedConnectorImpl::GetRootWebContents(
    WebContentsImpl* web_contents) {
  auto* root = web_contents;
  while (auto* parent = GetParentWebContents(root)) {
    root = parent;
  }
  return root;
}

// static
bool SurfaceEmbedConnectorImpl::ContainsOrIsFocusedWebContents(
    WebContentsImpl* web_contents) {
  // Focused frame tree is managed by root WebContents, so retrieve it from the
  // root WebContents.
  WebContentsImpl* root_web_contents = GetRootWebContents(web_contents);
  WebContentsImpl* focused_web_contents =
      root_web_contents->GetFocusedWebContents();
  while (focused_web_contents) {
    if (focused_web_contents == web_contents) {
      return true;
    }
    focused_web_contents = GetParentWebContents(focused_web_contents);
  }

  return false;
}

FrameTree* SurfaceEmbedConnectorImpl::GetFocusFrameTreeIfContainsFocus() {
  if (!parent_web_contents_ ||
      !ContainsOrIsFocusedWebContents(child_web_contents())) {
    return nullptr;
  }
  return GetRootWebContents(parent_web_contents())->GetFocusedFrameTree();
}

void SurfaceEmbedConnectorImpl::SetFocusedFrameTree(
    FrameTree* frame_tree_to_focus) {
  if (!parent_web_contents_) {
    return;
  }

  // Update focused frame tree stored in the embedder.
  parent_web_contents()->SetFocusedFrameTree(frame_tree_to_focus);
  // The `frame_tree_to_focus` must belong to this WebContents
  // or an inner WebContents in the subtree. Otherwise, this object's
  // SetFocusedFrameTree should not be involved.
  CHECK(ContainsOrIsFocusedWebContents(child_web_contents()));

  CHECK(embedder_rfh_);
  FrameTreeNode* embedder_node = embedder_rfh_->frame_tree_node();
  embedder_node->frame_tree().SetFocusedFrame(embedder_node,
                                              /*source=*/nullptr);
  delegate_->RequestFocusOnEmbedElement();

  // Ensure that outer frame trees are focused.
  parent_web_contents()->GetPrimaryFrameTree().FocusOuterFrameTrees();

  // Ensure that the embedded page has focus. This is needed when the focused
  // frame tree belongs to an inner WebContents of the SurfaceEmbed child.
  child_web_contents()
      ->GetPrimaryMainFrame()
      ->GetRenderWidgetHost()
      ->SetPageFocus(true);

  // Ensure that the embedder's page has focus so that it can display active UI
  // and therefore the embedded plugin is also active.
  parent_web_contents()
      ->GetPrimaryMainFrame()
      ->GetRenderWidgetHost()
      ->SetPageFocus(true);
}

void SurfaceEmbedConnectorImpl::ClearFocusOnInnerWebContents() {
  if (!parent_web_contents_) {
    // Don't expect parent to be destroyed before child outside of tests.
    CHECK_IS_TEST();
    return;
  }

  if (!ContainsOrIsFocusedWebContents(child_web_contents())) {
    return;
  }

  // Using the same logic as the one for inner WebContents in WebContentsImpl
  // destructor to unset focus for child WebContents by setting focus on the
  // root WebContents.
  GetRootWebContents(parent_web_contents())
      ->SetAsFocusedWebContentsIfNecessary();
}

WebContentsImpl* SurfaceEmbedConnectorImpl::parent_web_contents() const {
  return static_cast<WebContentsImpl*>(parent_web_contents_.get());
}

void SurfaceEmbedConnectorImpl::SetView(RenderWidgetHostViewChildFrame* view,
                                        bool allow_paint_holding) {
  // Detach ourselves from the previous `view_`.
  if (view_) {
    RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView();
    if (root_view && root_view->GetCursorManager()) {
      // TODO(surface-embed): Consider renaming this API to ViewBeingDetached if
      // view_ is not necessarily guaranteed to be destroyed.
      root_view->GetCursorManager()->ViewBeingDestroyed(view_);
    }
    // The RenderWidgetHostDelegate needs to be checked because SetView() can
    // be called during nested WebContents destruction. See
    // https://crbug.com/644306.
    if (GetParentRenderWidgetHostView() &&
        GetParentRenderWidgetHostView()->host()->delegate() &&
        GetParentRenderWidgetHostView()
            ->host()
            ->delegate()
            ->GetInputEventRouter()) {
      GetParentRenderWidgetHostView()
          ->host()
          ->delegate()
          ->GetInputEventRouter()
          ->WillDetachChildView(view_);
    }
    view_->SetFrameConnector(nullptr);
  }

  ResetRectInParentView();
  view_ = view;

  // Attach ourselves to the new view and size it appropriately. Also update
  // visibility in case the frame owner is hidden in parent process. We should
  // try to move these updates to a single IPC (see https://crbug.com/750179).
  if (view_) {
    view_->SetFrameConnector(this);
    view_->host()->UpdateVisualProperties(/*propagate=*/true);

    // If the child frame is already visible, it became visible before the
    // frame connector was attached. We need to retroactively update the
    // visibility of its child views.
    if (!view_->host()->IsHidden()) {
      SetVisibilityForChildViews(true);
    }

    if (visibility_ != blink::mojom::FrameVisibility::kRenderedInViewport) {
      OnVisibilityChanged(visibility_);
    }

    frame_sink_id_ = view_->GetFrameSinkId();

    if (delegate_) {
      delegate_->SetFrameSinkId(frame_sink_id_, allow_paint_holding);
    }

    MaybeRefreshKeepSurfaceAlive();
  }
}

RenderWidgetHostViewBase*
SurfaceEmbedConnectorImpl::GetParentRenderWidgetHostView() {
  if (!parent_web_contents_) {
    return nullptr;
  }
  return static_cast<RenderWidgetHostViewBase*>(
      parent_web_contents()->GetRenderWidgetHostView());
}

RenderWidgetHostViewBase*
SurfaceEmbedConnectorImpl::GetRootRenderWidgetHostView() {
  if (!parent_web_contents_) {
    return nullptr;
  }
  return static_cast<RenderWidgetHostViewBase*>(
      GetRootWebContents(parent_web_contents())->GetRenderWidgetHostView());
}

void SurfaceEmbedConnectorImpl::RenderProcessGone() {
  delegate_->ChildProcessGone();

  // TODO(crbug.com/479743223): CrossProcessFrameConnector does a lot of logging
  // and sometimes reloading here that's about child frames in the usual sense.
  // Things embedded here do not always have those semantics, but it might make
  // sense to do something parallel.
}

void SurfaceEmbedConnectorImpl::FirstSurfaceActivation(
    const viz::SurfaceInfo& surface_info) {
  MaybeRefreshKeepSurfaceAlive();
}

void SurfaceEmbedConnectorImpl::SendIntrinsicSizingInfoToParent(
    blink::mojom::IntrinsicSizingInfoPtr) {}

void SurfaceEmbedConnectorImpl::SynchronizeVisualProperties(
    const blink::FrameVisualProperties& visual_properties,
    bool propagate) {
  last_received_zoom_level_ = visual_properties.zoom_level;
  last_received_css_zoom_factor_ = visual_properties.css_zoom_factor;
  last_received_local_frame_size_ = visual_properties.local_frame_size;
  screen_infos_ = visual_properties.screen_infos;
  bool local_surface_id_changed =
      (local_surface_id_ != visual_properties.local_surface_id);
  local_surface_id_ = visual_properties.local_surface_id;
  SetRectInParentView(visual_properties.rect_in_local_root);
  SetLocalFrameSize(visual_properties.local_frame_size);

  if (!view_) {
    return;
  }

  view_->UpdateScreenInfo();

  RenderWidgetHostImpl* render_widget_host = view_->host();
  CHECK(render_widget_host);

  render_widget_host->SetAutoResize(visual_properties.auto_resize_enabled,
                                    visual_properties.min_size_for_auto_resize,
                                    visual_properties.max_size_for_auto_resize);
  render_widget_host->SetVisualPropertiesFromParentFrame(
      visual_properties.page_scale_factor,
      visual_properties.compositing_scale_factor,
      visual_properties.is_pinch_gesture_active,
      visual_properties.visible_viewport_size,
      visual_properties.compositor_viewport,
      visual_properties.root_widget_viewport_segments);

  render_widget_host->UpdateVisualProperties(propagate);

  if (local_surface_id_changed) {
    MaybeRefreshKeepSurfaceAlive();
  }
}

void SurfaceEmbedConnectorImpl::UpdateCursor(const ui::Cursor& cursor) {
  RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView();

  // UpdateCursor messages are ignored if the root view does not support
  // cursors.
  if (root_view && root_view->GetCursorManager()) {
    root_view->GetCursorManager()->UpdateCursor(view_, cursor);
  }
}

FrameConnector::RootViewFocusState SurfaceEmbedConnectorImpl::HasFocus() {
  RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView();
  if (!root_view) {
    return RootViewFocusState::kNotFocused;
  }
  return root_view->HasFocus() ? RootViewFocusState::kFocused
                               : RootViewFocusState::kNotFocused;
}

void SurfaceEmbedConnectorImpl::FocusRootView() {
  if (RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView()) {
    root_view->Focus();
  }
}

blink::mojom::PointerLockResult SurfaceEmbedConnectorImpl::LockPointer(
    bool request_unadjusted_movement) {
  if (RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView()) {
    return root_view->LockPointer(request_unadjusted_movement);
  }
  return blink::mojom::PointerLockResult::kWrongDocument;
}

blink::mojom::PointerLockResult SurfaceEmbedConnectorImpl::ChangePointerLock(
    bool request_unadjusted_movement) {
  if (RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView()) {
    return root_view->ChangePointerLock(request_unadjusted_movement);
  }
  return blink::mojom::PointerLockResult::kWrongDocument;
}

void SurfaceEmbedConnectorImpl::UnlockPointer() {
  if (RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView()) {
    root_view->UnlockPointer();
  }
}

bool SurfaceEmbedConnectorImpl::HasSize() {
  return has_size_;
}

const display::ScreenInfos& SurfaceEmbedConnectorImpl::GetScreenInfos() {
  return screen_infos_;
}

const viz::LocalSurfaceId& SurfaceEmbedConnectorImpl::GetLocalSurfaceId() {
  return local_surface_id_;
}

const blink::mojom::ViewportIntersectionState&
SurfaceEmbedConnectorImpl::GetIntersectionState() {
  return intersection_state_;
}


const gfx::Rect& SurfaceEmbedConnectorImpl::GetRectInParentViewInDip() {
  return rect_in_parent_view_in_dip_;
}

const gfx::Size& SurfaceEmbedConnectorImpl::GetLocalFrameSizeInDip() {
  return local_frame_size_in_dip_;
}

const gfx::Size& SurfaceEmbedConnectorImpl::GetLocalFrameSizeInPixels() {
  return local_frame_size_in_pixels_;
}

double SurfaceEmbedConnectorImpl::GetCssZoomFactor() {
  return last_received_css_zoom_factor_;
}

double SurfaceEmbedConnectorImpl::GetCssZoomFactorForTesting() {
  return last_received_css_zoom_factor_;
}

const gfx::Size&
SurfaceEmbedConnectorImpl::GetLocalFrameSizeInPixelsForTesting() {
  return local_frame_size_in_pixels_;
}

bool SurfaceEmbedConnectorImpl::IsThrottledForTesting() {
  return IsThrottled();
}

bool SurfaceEmbedConnectorImpl::IsSubtreeThrottledForTesting() {
  return IsSubtreeThrottled();
}

bool SurfaceEmbedConnectorImpl::IsDisplayLockedForTesting() {
  return IsDisplayLocked();
}

void SurfaceEmbedConnectorImpl::EnableAutoResize(const gfx::Size& min_size,
                                                 const gfx::Size& max_size) {}

void SurfaceEmbedConnectorImpl::DisableAutoResize() {}

bool SurfaceEmbedConnectorImpl::IsInert() {
  return is_inert_;
}

cc::TouchAction SurfaceEmbedConnectorImpl::InheritedEffectiveTouchAction() {
  return inherited_effective_touch_action_;
}

bool SurfaceEmbedConnectorImpl::IsHidden() {
  // We want IsHidden() to return false even when the page isn't actually
  // rendering us, since WebContents may want to render us for features like
  // capture; any CSS that's hiding us should make us not show up incorrectly
  // in the parent renderer regardless.
  //
  // NOTE: This relies on parent_web_contents_ (a WeakPtr) which automatically
  // clears to null when the parent is destroyed, ensuring IsHidden() becomes
  // true immediately.
  return !parent_web_contents_;
}

bool SurfaceEmbedConnectorImpl::IsThrottled() {
  return is_throttled_;
}

bool SurfaceEmbedConnectorImpl::IsSubtreeThrottled() {
  return subtree_throttled_;
}

bool SurfaceEmbedConnectorImpl::IsDisplayLocked() {
  return display_locked_;
}

void SurfaceEmbedConnectorImpl::DidUpdateVisualProperties(
    const cc::RenderFrameMetadata& metadata) {
  if (metadata.local_surface_id.has_value() &&
      local_surface_id_ != *metadata.local_surface_id) {
    delegate_->UpdateLocalSurfaceIdFromChild(*metadata.local_surface_id);
  }
}

void SurfaceEmbedConnectorImpl::SetVisibilityForChildViews(bool visible) {
  if (current_child_frame_host()) {
    current_child_frame_host()->SetVisibilityForChildViews(visible);
  }
}

void SurfaceEmbedConnectorImpl::SetKeepSurfaceAlive(bool keep_alive) {
  should_keep_alive_ = keep_alive;
  // We may be force-shown by WebContents to enable tab capture, even if we're
  // in background. To enable that, we want to create a reference to the
  // surface, to help the compositor notice its capture; this won't be created
  // by the parent renderer unless it gets actually painted.
  if (!view_) {
    keep_surface_alive_.RunAndReset();
    return;
  }

  auto surface_id = view_->GetCurrentSurfaceId();
#if BUILDFLAG(IS_ANDROID)
  ui::WindowAndroidCompositor* compositor = nullptr;
  if (view_->GetNativeView() && view_->GetNativeView()->GetWindowAndroid()) {
    compositor = view_->GetNativeView()->GetWindowAndroid()->GetCompositor();
  }
  if (should_keep_alive_ && compositor && surface_id.is_valid()) {
    keep_surface_alive_ = base::ScopedClosureRunner(
        compositor->TakeScopedKeepSurfaceAliveCallback(surface_id));
#else
  if (should_keep_alive_ && view_->GetCompositor() && surface_id.is_valid()) {
    keep_surface_alive_ =
        view_->GetCompositor()->TakeScopedKeepSurfaceAliveCallback(surface_id);
#endif
  } else {
    keep_surface_alive_.RunAndReset();
  }
}

bool SurfaceEmbedConnectorImpl::IsKeepingAlive() const {
  return should_keep_alive_;
}

void SurfaceEmbedConnectorImpl::MaybeRefreshKeepSurfaceAlive() {
  if (should_keep_alive_) {
    SetKeepSurfaceAlive(true);
  }
}

void SurfaceEmbedConnectorImpl::SetLocalFrameSize(
    const gfx::Size& local_frame_size) {
  has_size_ = true;
  const float dsf = screen_infos_.current().device_scale_factor;
  local_frame_size_in_pixels_ = local_frame_size;
  local_frame_size_in_dip_ =
      gfx::ScaleToRoundedSize(local_frame_size, 1.f / dsf);
}

void SurfaceEmbedConnectorImpl::SetRectInParentView(
    const gfx::Rect& rect_in_parent_view) {
  const float dsf = screen_infos_.current().device_scale_factor;
  rect_in_parent_view_in_dip_ = gfx::Rect(
      gfx::ScaleToFlooredPoint(rect_in_parent_view.origin(), 1.f / dsf),
      gfx::ScaleToCeiledSize(rect_in_parent_view.size(), 1.f / dsf));

  if (view_) {
    view_->SetBounds(rect_in_parent_view_in_dip_);
  }

  // TODO(crbug.com/496266440): Notify the embedder of the rect change so that
  // it can call SendScreenRects on all subtrees rooted at the guest web
  // contents?
}

void SurfaceEmbedConnectorImpl::OnVisibilityChanged(
    blink::mojom::FrameVisibility visibility) {
  visibility_ = visibility;

  if (!view_) {
    return;
  }

  if (current_child_frame_host()) {
    current_child_frame_host()->VisibilityChanged(visibility_);
  }

  UpdateChildVisibility();
}

bool SurfaceEmbedConnectorImpl::IsVisible() {
  if (visibility_ == blink::mojom::FrameVisibility::kNotRendered ||
      GetIntersectionState().viewport_intersection.IsEmpty()) {
    return false;
  }

  if (EmbedderVisibility() != Visibility::VISIBLE) {
    return false;
  }

  return true;
}

void SurfaceEmbedConnectorImpl::DelegateWasShown() {}

Visibility SurfaceEmbedConnectorImpl::EmbedderVisibility() {
  if (!parent_web_contents()) {
    return Visibility::HIDDEN;
  }
  return parent_web_contents()->GetVisibility();
}

input::RenderWidgetHostViewInput*
SurfaceEmbedConnectorImpl::GetParentViewInput() {
  return GetParentRenderWidgetHostView();
}

input::RenderWidgetHostViewInput*
SurfaceEmbedConnectorImpl::GetRootViewInput() {
  return GetRootRenderWidgetHostView();
}

void SurfaceEmbedConnectorImpl::UpdateViewForCurrentRenderFrameHost() {
  // Should not get here without being attached to a child WebContents.
  CHECK(child_web_contents_);

  // Get the current RenderWidgetHostView for the child WebContents.
  auto* base_view = static_cast<RenderWidgetHostViewBase*>(
      child_web_contents_->GetRenderWidgetHostView());

  if (!base_view) {
    SetView(nullptr, /*allow_paint_holding=*/false);
  } else {
    CHECK(base_view->IsRenderWidgetHostViewChildFrame());
    auto* child_view = static_cast<RenderWidgetHostViewChildFrame*>(base_view);

    if (view_ != child_view) {
      SetView(child_view, /*allow_paint_holding=*/false);
    }
  }

  UpdateAccessibilityTree();
}

void SurfaceEmbedConnectorImpl::OnAttachedToParent() {
  UpdateViewForCurrentRenderFrameHost();
  if (parent_web_contents()) {
    CHECK(embedder_rfh_);
    parent_web_contents()->SurfaceEmbedChildWebContentsAttached(
        child_web_contents_, embedder_rfh_.get());
  }
}

void SurfaceEmbedConnectorImpl::OnDetachedFromParent() {
  if (parent_web_contents()) {
    parent_web_contents()->SurfaceEmbedChildWebContentsDetached(
        child_web_contents_);
  }
}

void SurfaceEmbedConnectorImpl::ResetRectInParentView() {
  local_surface_id_ = viz::LocalSurfaceId();
  // TODO(crbug.com/40561516): Consider whether we actually need the next 2
  // lines or not.
  rect_in_parent_view_in_dip_ = gfx::Rect();
  last_received_local_frame_size_ = gfx::Size();
}

void SurfaceEmbedConnectorImpl::UpdateAccessibilityTree() {
  auto* child_rfh = child_web_contents_
                        ? static_cast<content::RenderFrameHostImpl*>(
                              child_web_contents_->GetPrimaryMainFrame())
                        : nullptr;

  if (!child_rfh) {
    return;
  }

  const ui::AXTreeID previous_embed_parent_ax_tree_id =
      embed_parent_ax_tree_id_;
  embed_parent_ax_tree_id_ = ui::AXTreeIDUnknown();

  if (container_accessibility_node_id_ != ui::kInvalidAXNodeID &&
      container_accessibility_tree_id_ != ui::AXTreeIDUnknown()) {
    auto child_ax_tree_id = child_rfh->GetAXTreeID();
    auto parent_ax_tree_id = container_accessibility_tree_id_;
    auto* parent_render_frame_host =
        content::RenderFrameHost::FromAXTreeID(parent_ax_tree_id);

    const bool parent_is_valid =
        parent_render_frame_host &&
        WebContents::FromRenderFrameHost(parent_render_frame_host) ==
            parent_web_contents();

    if (child_ax_tree_id != ui::AXTreeIDUnknown() && parent_is_valid) {
      ui::AXActionData action_data;
      action_data.action = ax::mojom::Action::kStitchChildTree;
      action_data.target_tree_id = parent_ax_tree_id;
      // Note we set the target node ID and not the target role. Setting both is
      // an error that is logged but the program proceeds without any other
      // error.
      action_data.target_node_id = container_accessibility_node_id_;
      action_data.child_tree_id = child_ax_tree_id;
      parent_render_frame_host->AccessibilityPerformAction(action_data);
      embed_parent_ax_tree_id_ = parent_ax_tree_id;
    }
  }

  if (embed_parent_ax_tree_id_ != previous_embed_parent_ax_tree_id) {
    child_rfh->UpdateAXTreeData();
  }
}

ui::AXTreeID SurfaceEmbedConnectorImpl::GetParentAXTreeID() const {
  return embed_parent_ax_tree_id_;
}

void SurfaceEmbedConnectorImpl::SetParentAccessibilityInfo(
    ui::AXNodeID ax_node_id,
    const ui::AXTreeID& ax_tree_id) {
  if (ax_node_id == container_accessibility_node_id_ &&
      ax_tree_id == container_accessibility_tree_id_) {
    return;
  }
  container_accessibility_node_id_ = ax_node_id;
  container_accessibility_tree_id_ = ax_tree_id;
  UpdateAccessibilityTree();
}

RenderFrameHostImpl* SurfaceEmbedConnectorImpl::current_child_frame_host()
    const {
  if (!child_web_contents()) {
    return nullptr;
  }
  return static_cast<RenderFrameHostImpl*>(
      child_web_contents()->GetPrimaryMainFrame());
}

void SurfaceEmbedConnectorImpl::ParentVisibilityChanged(Visibility visibility) {
  UpdateChildVisibility();
}

void SurfaceEmbedConnectorImpl::UpdateChildVisibility() {
  if (!view_) {
    return;
  }

  bool parent_is_visible = (EmbedderVisibility() == Visibility::VISIBLE);

  if (!parent_is_visible) {
    child_web_contents_->WasHidden();
    return;
  }

  switch (visibility_) {
    case blink::mojom::FrameVisibility::kRenderedInViewport:
      child_web_contents_->WasShown();
      break;
    case blink::mojom::FrameVisibility::kNotRendered:
      child_web_contents_->WasHidden();
      break;
    case blink::mojom::FrameVisibility::kRenderedOutOfViewport:
      child_web_contents_->WasOccluded();
      break;
  }
}

}  // namespace content
