| // 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 |