| // 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 "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_widget_host_delegate.h" |
| #include "content/browser/renderer_host/render_widget_host_view_child_frame.h" |
| #include "content/browser/surface_embed/dummy_surface_provider.h" |
| #include "content/browser/web_contents/web_contents_impl.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/input/pointer_lock_result.mojom.h" |
| #include "ui/base/cursor/cursor.h" |
| #include "ui/compositor/compositor.h" |
| |
| namespace content { |
| |
| // static |
| void SurfaceEmbedConnector::Attach(WebContents* child_web_contents, |
| WebContents* parent_web_contents, |
| SurfaceEmbedConnector::Delegate* delegate) { |
| CHECK(child_web_contents); |
| 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, delegate)); |
| auto* connector_ptr = connector.get(); |
| static_cast<WebContentsImpl*>(child_web_contents) |
| ->SetSurfaceEmbedConnector(std::move(connector)); |
| connector_ptr->UpdateViewForCurrentRenderFrameHost(); |
| } |
| |
| // static |
| void SurfaceEmbedConnector::Detach(WebContents* child_web_contents) { |
| // Connector will be freed by ClearSurfaceEmbedConnector(). |
| static_cast<WebContentsImpl*>(child_web_contents) |
| ->ClearSurfaceEmbedConnector(); |
| } |
| |
| SurfaceEmbedConnectorImpl::SurfaceEmbedConnectorImpl( |
| WebContents* child_web_contents, |
| WebContents* parent_web_contents, |
| SurfaceEmbedConnector::Delegate* delegate) |
| : delegate_(delegate), |
| child_web_contents_(static_cast<WebContentsImpl*>(child_web_contents)), |
| parent_web_contents_(parent_web_contents->GetWeakPtr()), |
| dummy_surface_provider_(std::make_unique<DummySurfaceProvider>()) {} |
| |
| SurfaceEmbedConnectorImpl::~SurfaceEmbedConnectorImpl() = default; |
| |
| 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; |
| } |
| |
| SurfaceEmbedConnector::Delegate* SurfaceEmbedConnectorImpl::GetDelegate() { |
| return delegate_; |
| } |
| |
| const viz::FrameSinkId& SurfaceEmbedConnectorImpl::GetFrameSinkId() const { |
| return dummy_surface_provider_->frame_sink_id(); |
| } |
| |
| void SurfaceEmbedConnectorImpl::OnSynchronizeVisualProperties( |
| const blink::FrameVisualProperties& visual_properties) { |
| dummy_surface_provider_->SubmitCompositorFrame( |
| visual_properties.local_surface_id, |
| visual_properties.screen_infos.current().device_scale_factor, |
| visual_properties.local_frame_size); |
| } |
| |
| 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); |
| if (visibility_ != blink::mojom::FrameVisibility::kRenderedInViewport) { |
| OnVisibilityChanged(visibility_); |
| } |
| |
| if (delegate_) { |
| delegate_->SetFrameSinkId(dummy_surface_provider_->frame_sink_id()); |
| } |
| } |
| } |
| |
| RenderWidgetHostViewBase* |
| SurfaceEmbedConnectorImpl::GetParentRenderWidgetHostView() { |
| if (!parent_web_contents_) { |
| return nullptr; |
| } |
| return static_cast<RenderWidgetHostViewBase*>( |
| parent_web_contents()->GetRenderWidgetHostView()); |
| } |
| |
| RenderWidgetHostViewBase* |
| SurfaceEmbedConnectorImpl::GetRootRenderWidgetHostView() { |
| // Assuming one level of embedding. |
| // TODO(crbug.com/496266440): support multiple levels of embedding, e.g., a |
| // WebUI embeds another WebUI, which in turn embeds an external web page. |
| return GetParentRenderWidgetHostView(); |
| } |
| |
| void SurfaceEmbedConnectorImpl::RenderProcessGone() {} |
| |
| void SurfaceEmbedConnectorImpl::FirstSurfaceActivation( |
| const viz::SurfaceInfo& surface_info) {} |
| |
| void SurfaceEmbedConnectorImpl::SendIntrinsicSizingInfoToParent( |
| blink::mojom::IntrinsicSizingInfoPtr) {} |
| |
| void SurfaceEmbedConnectorImpl::SynchronizeVisualProperties( |
| const blink::FrameVisualProperties& visual_properties, |
| bool propagate) {} |
| |
| void SurfaceEmbedConnectorImpl::UpdateCursor(const ui::Cursor& cursor) {} |
| |
| FrameConnector::RootViewFocusState SurfaceEmbedConnectorImpl::HasFocus() { |
| return RootViewFocusState::kNullView; |
| } |
| |
| void SurfaceEmbedConnectorImpl::FocusRootView() {} |
| |
| blink::mojom::PointerLockResult SurfaceEmbedConnectorImpl::LockPointer( |
| bool request_unadjusted_movement) { |
| return blink::mojom::PointerLockResult::kUnknownError; |
| } |
| |
| blink::mojom::PointerLockResult SurfaceEmbedConnectorImpl::ChangePointerLock( |
| bool request_unadjusted_movement) { |
| return blink::mojom::PointerLockResult::kUnknownError; |
| } |
| |
| void SurfaceEmbedConnectorImpl::UnlockPointer() {} |
| |
| bool SurfaceEmbedConnectorImpl::HasSize() { |
| return false; |
| } |
| |
| 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_; |
| } |
| |
| uint32_t SurfaceEmbedConnectorImpl::GetCaptureSequenceNumber() { |
| return capture_sequence_number_; |
| } |
| |
| 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_; |
| } |
| |
| void SurfaceEmbedConnectorImpl::EnableAutoResize(const gfx::Size& min_size, |
| const gfx::Size& max_size) {} |
| |
| void SurfaceEmbedConnectorImpl::DisableAutoResize() {} |
| |
| bool SurfaceEmbedConnectorImpl::IsInert() { |
| return false; |
| } |
| |
| cc::TouchAction SurfaceEmbedConnectorImpl::InheritedEffectiveTouchAction() { |
| return cc::TouchAction::kAuto; |
| } |
| |
| bool SurfaceEmbedConnectorImpl::IsHidden() { |
| // TODO(crbug.com/496266441): Ensure consistency of the values with the |
| // visibility state as we get more complete visibility support. |
| return false; |
| } |
| |
| bool SurfaceEmbedConnectorImpl::IsThrottled() { |
| return false; |
| } |
| |
| bool SurfaceEmbedConnectorImpl::IsSubtreeThrottled() { |
| return false; |
| } |
| |
| bool SurfaceEmbedConnectorImpl::IsDisplayLocked() { |
| return false; |
| } |
| |
| void SurfaceEmbedConnectorImpl::DidUpdateVisualProperties( |
| const cc::RenderFrameMetadata& metadata) {} |
| |
| void SurfaceEmbedConnectorImpl::SetVisibilityForChildViews(bool visible) {} |
| |
| void SurfaceEmbedConnectorImpl::SetLocalFrameSize( |
| const gfx::Size& local_frame_size) {} |
| |
| void SurfaceEmbedConnectorImpl::SetRectInParentView( |
| const gfx::Rect& rect_in_parent_view) {} |
| |
| void SurfaceEmbedConnectorImpl::OnVisibilityChanged( |
| blink::mojom::FrameVisibility visibility) { |
| visibility_ = visibility; |
| |
| // TODO(crbug.com/496266441): If there is a view, propagate the change in |
| // visibility to the current child render frame host and the child web |
| // contents. |
| } |
| |
| bool SurfaceEmbedConnectorImpl::IsVisible() { |
| // TODO(crbug.com/496266441): Ensure consistency of the values with the |
| // visibility state as we get more complete visibility support. |
| return true; |
| } |
| |
| void SurfaceEmbedConnectorImpl::DelegateWasShown() {} |
| |
| Visibility SurfaceEmbedConnectorImpl::EmbedderVisibility() { |
| return Visibility::VISIBLE; |
| } |
| |
| input::RenderWidgetHostViewInput* |
| SurfaceEmbedConnectorImpl::GetParentViewInput() { |
| return GetParentRenderWidgetHostView(); |
| } |
| |
| input::RenderWidgetHostViewInput* |
| SurfaceEmbedConnectorImpl::GetRootViewInput() { |
| return GetRootRenderWidgetHostView(); |
| } |
| |
| void SurfaceEmbedConnectorImpl::UpdateViewForCurrentRenderFrameHost() { |
| // Should not get here without 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); |
| return; |
| } |
| |
| CHECK(base_view->IsRenderWidgetHostViewChildFrame()); |
| auto* child_view = static_cast<RenderWidgetHostViewChildFrame*>(base_view); |
| |
| if (view_ != child_view) { |
| SetView(child_view, /*allow_paint_holding=*/false); |
| } |
| } |
| |
| void SurfaceEmbedConnectorImpl::ResetRectInParentView() { |
| local_surface_id_ = viz::LocalSurfaceId(); |
| rect_in_parent_view_in_dip_ = gfx::Rect(); |
| last_received_local_frame_size_ = gfx::Size(); |
| } |
| |
| } // namespace content |