blob: ba8841aa8650a961efd2b29b4bfddcb5fad22cf8 [file]
// 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