blob: a5c68e62dbe3ebb9392cfc24f1975359b380937f [file] [log] [blame]
// Copyright 2014 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/renderer_host/cross_process_frame_connector.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/optional_trace_event.h"
#include "components/input/cursor_manager.h"
#include "components/input/render_widget_host_input_event_router.h"
#include "components/viz/common/features.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_host_manager.h"
#include "content/browser/renderer_host/render_frame_proxy_host.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/common/features.h"
#include "third_party/blink/public/common/frame/frame_visual_properties.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/mojom/frame/intrinsic_sizing_info.mojom.h"
#include "ui/base/cursor/cursor.h"
#include "ui/gfx/geometry/dip_util.h"
namespace content {
CrossProcessFrameConnector::CrossProcessFrameConnector(
RenderFrameProxyHost* frame_proxy_in_parent_renderer)
: frame_proxy_in_parent_renderer_(frame_proxy_in_parent_renderer) {
// Skip for tests.
if (!frame_proxy_in_parent_renderer_) {
screen_infos_ = display::ScreenInfos(display::ScreenInfo());
return;
}
// At this point, SetView() has not been called and so the associated
// RenderWidgetHost doesn't have a view yet. That means calling
// GetScreenInfos() on the associated RenderWidgetHost will just default to
// the primary display, which may not be appropriate. So instead we call
// GetScreenInfos() on the root RenderWidgetHost, which will be guaranteed to
// be on the correct display. All subsequent updates to |screen_infos_|
// ultimately come from the root, so it makes sense to do it here as well.
screen_infos_ = current_child_frame_host()
->GetOutermostMainFrameOrEmbedder()
->GetRenderWidgetHost()
->GetScreenInfos();
}
CrossProcessFrameConnector::~CrossProcessFrameConnector() {
if (!IsVisible()) {
// MaybeLogCrash will check 1) if there was a crash or not and 2) if the
// crash might have been already logged earlier as kCrashedWhileVisible or
// kShownAfterCrashing.
MaybeLogCrash(CrashVisibility::kNeverVisibleAfterCrash);
}
// Notify the view of this object being destroyed, if the view still exists.
SetView(nullptr, /*allow_paint_holding=*/false);
}
void CrossProcessFrameConnector::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())
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_) {
if (has_crashed_ && !IsVisible()) {
// MaybeLogCrash will check 1) if there was a crash or not and 2) if the
// crash might have been already logged earlier as kCrashedWhileVisible or
// kShownAfterCrashing.
MaybeLogCrash(CrashVisibility::kNeverVisibleAfterCrash);
}
is_crash_already_logged_ = has_crashed_ = false;
delegate_was_shown_after_crash_ = false;
view_->SetFrameConnector(this);
if (visibility_ != blink::mojom::FrameVisibility::kRenderedInViewport)
OnVisibilityChanged(visibility_);
if (frame_proxy_in_parent_renderer_ &&
frame_proxy_in_parent_renderer_->is_render_frame_proxy_live()) {
frame_proxy_in_parent_renderer_->GetAssociatedRemoteFrame()
->SetFrameSinkId(view_->GetFrameSinkId(), allow_paint_holding);
}
}
}
void CrossProcessFrameConnector::RenderProcessGone() {
OPTIONAL_TRACE_EVENT1("content",
"CrossProcessFrameConnector::RenderProcessGone",
"visibility", visibility_);
has_crashed_ = true;
RenderFrameHostImpl* current_child_rfh = current_child_frame_host();
int process_id = current_child_rfh->GetProcess()->GetDeprecatedID();
// If a parent, outer document or embedder of `current_child_rfh` has crashed
// and has the same RPH, we only want to record the crash once.
for (auto* rfh = current_child_rfh->GetParentOrOuterDocumentOrEmbedder(); rfh;
rfh = rfh->GetParentOrOuterDocumentOrEmbedder()) {
if (rfh->GetProcess()->GetDeprecatedID() == process_id) {
// The crash will be already logged by the ancestor - ignore this crash in
// the current instance of the CrossProcessFrameConnector.
is_crash_already_logged_ = true;
}
}
if (IsVisible())
MaybeLogCrash(CrashVisibility::kCrashedWhileVisible);
frame_proxy_in_parent_renderer_->ChildProcessGone();
if (current_child_rfh->delegate()) {
// If a subframe crashed on a hidden tab, mark the tab for reload to avoid
// showing a sad frame to the user if they ever switch back to that tab. Do
// this for subframes that are either visible in viewport or visible but
// scrolled out of view, but skip subframes that are not rendered (e.g., via
// "display:none"), since in that case the user wouldn't see a sad frame
// anyway. Prerendering subframes do not enter this code since
// RenderFrameHostImpl immediately cancels prerender if a render process
// exits. We only mark the tab for reload for active subframes to exclude
// cases like crashed frames in the back/forward cache.
bool did_mark_for_reload = false;
if (current_child_rfh->delegate()->GetVisibility() != Visibility::VISIBLE &&
visibility_ != blink::mojom::FrameVisibility::kNotRendered &&
base::FeatureList::IsEnabled(
features::kReloadHiddenTabsWithCrashedSubframes) && (
!base::FeatureList::IsEnabled(
features::kReloadHiddenTabsWithActiveCrashedSubframes) ||
current_child_rfh->IsActive()
)) {
frame_proxy_in_parent_renderer_->frame_tree_node()
->frame_tree()
.controller()
.SetNeedsReload(
NavigationControllerImpl::NeedsReloadType::kCrashedSubframe);
did_mark_for_reload = true;
UMA_HISTOGRAM_ENUMERATION(
"Stability.ChildFrameCrash.TabMarkedForReload.Visibility",
visibility_);
}
UMA_HISTOGRAM_BOOLEAN("Stability.ChildFrameCrash.TabMarkedForReload",
did_mark_for_reload);
}
}
void CrossProcessFrameConnector::SendIntrinsicSizingInfoToParent(
blink::mojom::IntrinsicSizingInfoPtr sizing_info) {
// The width/height should not be negative since gfx::SizeF will clamp
// negative values to zero.
DCHECK((sizing_info->size.width() >= 0.f) &&
(sizing_info->size.height() >= 0.f));
DCHECK((sizing_info->aspect_ratio.width() >= 0.f) &&
(sizing_info->aspect_ratio.height() >= 0.f));
if (!frame_proxy_in_parent_renderer_->is_render_frame_proxy_live())
return;
frame_proxy_in_parent_renderer_->GetAssociatedRemoteFrame()
->IntrinsicSizingInfoOfChildChanged(std::move(sizing_info));
}
void CrossProcessFrameConnector::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;
local_surface_id_ = visual_properties.local_surface_id;
capture_sequence_number_ = visual_properties.capture_sequence_number;
SetRectInParentView(visual_properties.rect_in_local_root);
SetLocalFrameSize(visual_properties.local_frame_size);
if (!view_)
return;
view_->UpdateScreenInfo();
RenderWidgetHostImpl* render_widget_host = view_->host();
DCHECK(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);
}
input::RenderWidgetHostViewInput*
CrossProcessFrameConnector::GetParentViewInput() {
return GetParentRenderWidgetHostView();
}
input::RenderWidgetHostViewInput*
CrossProcessFrameConnector::GetRootViewInput() {
return GetRootRenderWidgetHostView();
}
void CrossProcessFrameConnector::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);
}
CrossProcessFrameConnector::RootViewFocusState
CrossProcessFrameConnector::HasFocus() {
RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView();
if (!root_view) {
return RootViewFocusState::kNullView;
}
return root_view->HasFocus() ? RootViewFocusState::kFocused
: RootViewFocusState::kNotFocused;
}
void CrossProcessFrameConnector::FocusRootView() {
RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView();
if (root_view)
root_view->Focus();
}
blink::mojom::PointerLockResult CrossProcessFrameConnector::LockPointer(
bool request_unadjusted_movement) {
RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView();
if (root_view)
return root_view->LockPointer(request_unadjusted_movement);
return blink::mojom::PointerLockResult::kWrongDocument;
}
blink::mojom::PointerLockResult CrossProcessFrameConnector::ChangePointerLock(
bool request_unadjusted_movement) {
RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView();
if (root_view)
return root_view->ChangePointerLock(request_unadjusted_movement);
return blink::mojom::PointerLockResult::kWrongDocument;
}
void CrossProcessFrameConnector::UnlockPointer() {
RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView();
if (root_view)
root_view->UnlockPointer();
}
void CrossProcessFrameConnector::OnSynchronizeVisualProperties(
const blink::FrameVisualProperties& visual_properties) {
TRACE_EVENT_WITH_FLOW2(
TRACE_DISABLED_BY_DEFAULT("viz.surface_id_flow"),
"CrossProcessFrameConnector::OnSynchronizeVisualProperties Receive "
"Message",
TRACE_ID_GLOBAL(visual_properties.local_surface_id.submission_trace_id()),
TRACE_EVENT_FLAG_FLOW_IN, "message",
"FrameHostMsg_SynchronizeVisualProperties", "new_local_surface_id",
visual_properties.local_surface_id.ToString());
// 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() ||
capture_sequence_number() != visual_properties.capture_sequence_number ||
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) {
bad_message::ReceivedBadMessage(
frame_proxy_in_parent_renderer_->GetProcess(),
bad_message::CPFC_RESIZE_PARAMS_CHANGED_LOCAL_SURFACE_ID_UNCHANGED);
return;
}
SynchronizeVisualProperties(visual_properties);
}
void CrossProcessFrameConnector::UpdateViewportIntersection(
const blink::mojom::ViewportIntersectionState& intersection_state,
const std::optional<blink::FrameVisualProperties>& visual_properties) {
bool intersection_changed = !intersection_state.Equals(intersection_state_);
if (intersection_changed) {
RenderWidgetHostImpl* host = view_ ? view_->host() : nullptr;
bool main_frame = host && host->owner_delegate();
bool visual_properties_changed = false;
if (visual_properties.has_value()) {
// Subtlety: RenderWidgetHostViewChildFrame::UpdateViewportIntersection()
// will quietly fail to propagate the new intersection state for main
// frames, including fenced frames. For those cases, we need to ensure
// that the updated VisualProperties are still propagated.
std::optional<blink::VisualProperties> last_properties;
if (host && !main_frame)
last_properties = host->LastComputedVisualProperties();
SynchronizeVisualProperties(visual_properties.value(), main_frame);
if (host && !main_frame) {
visual_properties_changed =
last_properties != host->LastComputedVisualProperties();
}
}
UpdateViewportIntersectionInternal(intersection_state,
visual_properties_changed);
} else if (visual_properties.has_value()) {
SynchronizeVisualProperties(visual_properties.value(), true);
}
}
void CrossProcessFrameConnector::UpdateViewportIntersectionInternal(
const blink::mojom::ViewportIntersectionState& intersection_state,
bool include_visual_properties) {
intersection_state_ = intersection_state;
if (view_) {
CHECK(current_child_frame_host());
current_child_frame_host()
->delegate()
->OnRemoteSubframeViewportIntersectionStateChanged(
current_child_frame_host(), intersection_state);
// Only ship over the visual properties if they were included in the update
// viewport intersection message.
view_->UpdateViewportIntersection(
intersection_state_, include_visual_properties
? view_->host()->LastComputedVisualProperties()
: std::nullopt);
}
if (IsVisible()) {
// Record metrics if a crashed subframe became visible as a result of this
// viewport intersection update. For example, this might happen if a user
// scrolls to a crashed subframe.
MaybeLogShownCrash(ShownAfterCrashingReason::kViewportIntersection);
}
}
void CrossProcessFrameConnector::OnVisibilityChanged(
blink::mojom::FrameVisibility visibility) {
bool visible = visibility != blink::mojom::FrameVisibility::kNotRendered;
visibility_ = visibility;
if (IsVisible()) {
// Record metrics if a crashed subframe became visible as a result of this
// visibility change.
MaybeLogShownCrash(ShownAfterCrashingReason::kVisibility);
}
if (!view_)
return;
// TODO(crbug.com/40103184) Remove this CHECK when the bug is fixed.
CHECK(current_child_frame_host());
current_child_frame_host()->VisibilityChanged(visibility_);
// If there is an inner WebContents, it should be notified of the change in
// the visibility. The Show/Hide methods will not be called if an inner
// WebContents exists since the corresponding WebContents will itself call
// Show/Hide on all the RenderWidgetHostViews (including this) one.
if (view_->host()
->frame_tree()
->delegate()
->OnRenderFrameProxyVisibilityChanged(frame_proxy_in_parent_renderer_,
visibility_)) {
return;
}
if (visible && !view_->host()->frame_tree()->IsHidden()) {
view_->Show();
} else if (!visible) {
view_->Hide();
}
}
void CrossProcessFrameConnector::SetIsInert(bool inert) {
is_inert_ = inert;
if (view_)
view_->SetIsInert();
}
void CrossProcessFrameConnector::OnSetInheritedEffectiveTouchAction(
cc::TouchAction touch_action) {
inherited_effective_touch_action_ = touch_action;
if (view_)
view_->UpdateInheritedEffectiveTouchAction();
}
RenderWidgetHostViewBase*
CrossProcessFrameConnector::GetRootRenderWidgetHostView() {
// Tests may not have frame_proxy_in_parent_renderer_ set.
if (!frame_proxy_in_parent_renderer_)
return nullptr;
RenderFrameHostImpl* root =
current_child_frame_host()
->GetOutermostMainFrameOrEmbedderExcludingProspectiveOwners();
return static_cast<RenderWidgetHostViewBase*>(root->GetView());
}
RenderWidgetHostViewBase*
CrossProcessFrameConnector::GetParentRenderWidgetHostView() {
// Input always hits the parent view if there is one so we should
// escape to an embedder.
RenderFrameHostImpl* parent =
current_child_frame_host()
? current_child_frame_host()
->GetParentOrOuterDocumentOrEmbedderExcludingProspectiveOwners()
: nullptr;
return parent ? static_cast<RenderWidgetHostViewBase*>(parent->GetView())
: nullptr;
}
void CrossProcessFrameConnector::EnableAutoResize(const gfx::Size& min_size,
const gfx::Size& max_size) {
frame_proxy_in_parent_renderer_->EnableAutoResize(min_size, max_size);
}
void CrossProcessFrameConnector::DisableAutoResize() {
frame_proxy_in_parent_renderer_->DisableAutoResize();
}
bool CrossProcessFrameConnector::IsInert() const {
return is_inert_;
}
cc::TouchAction CrossProcessFrameConnector::InheritedEffectiveTouchAction()
const {
return inherited_effective_touch_action_;
}
bool CrossProcessFrameConnector::IsHidden() const {
return visibility_ == blink::mojom::FrameVisibility::kNotRendered;
}
void CrossProcessFrameConnector::DidUpdateVisualProperties(
const cc::RenderFrameMetadata& metadata) {
frame_proxy_in_parent_renderer_->DidUpdateVisualProperties(metadata);
}
void CrossProcessFrameConnector::SetVisibilityForChildViews(
bool visible) const {
current_child_frame_host()->SetVisibilityForChildViews(visible);
}
void CrossProcessFrameConnector::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 CrossProcessFrameConnector::SetRectInParentView(
const gfx::Rect& rect_in_parent_view) {
gfx::Rect old_rect = rect_in_parent_view_in_dip_;
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_ && frame_proxy_in_parent_renderer_) {
view_->SetBounds(rect_in_parent_view_in_dip_);
// Other local root frames nested underneath this one implicitly have their
// view rects changed when their ancestor is repositioned, and therefore
// need to have their screen rects updated.
FrameTreeNode* proxy_node =
frame_proxy_in_parent_renderer_->frame_tree_node();
if (old_rect.x() != rect_in_parent_view_in_dip_.x() ||
old_rect.y() != rect_in_parent_view_in_dip_.y()) {
for (FrameTreeNode* node :
proxy_node->frame_tree().SubtreeNodes(proxy_node)) {
if (node != proxy_node && node->current_frame_host()->is_local_root())
node->current_frame_host()->GetRenderWidgetHost()->SendScreenRects();
}
}
}
}
void CrossProcessFrameConnector::ResetRectInParentView() {
local_surface_id_ = viz::LocalSurfaceId();
// TODO(lfg): Why do we need to reset the rect_in_parent_view_in_dip_ that
// comes from the parent when setting the child? https://crbug.com/809275
rect_in_parent_view_in_dip_ = gfx::Rect();
last_received_local_frame_size_ = gfx::Size();
}
void CrossProcessFrameConnector::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();
}
}
bool CrossProcessFrameConnector::IsThrottled() const {
return is_throttled_;
}
bool CrossProcessFrameConnector::IsSubtreeThrottled() const {
return subtree_throttled_;
}
bool CrossProcessFrameConnector::IsDisplayLocked() const {
return display_locked_;
}
bool CrossProcessFrameConnector::MaybeLogCrash(CrashVisibility visibility) {
if (!has_crashed_)
return false;
// Only log once per renderer crash.
if (is_crash_already_logged_)
return false;
is_crash_already_logged_ = true;
// Actually log the UMA.
UMA_HISTOGRAM_ENUMERATION("Stability.ChildFrameCrash.Visibility", visibility);
if (child_frame_crash_shown_closure_for_testing_)
std::move(child_frame_crash_shown_closure_for_testing_).Run();
return true;
}
void CrossProcessFrameConnector::MaybeLogShownCrash(
ShownAfterCrashingReason reason) {
// Check if an ancestor frame has a pending cross-document navigation. If
// so, log the sad frame visibility differently, since the sad frame is
// expected to go away shortly. Note that this also handles the common case
// of a hidden tab with a sad frame being auto-reloaded when it becomes
// shown.
bool has_pending_navigation = false;
for (auto* parent = current_child_frame_host()->GetParentOrOuterDocument();
parent; parent = parent->GetParentOrOuterDocument()) {
if (parent->frame_tree_node()->HasPendingCrossDocumentNavigation()) {
has_pending_navigation = true;
break;
}
}
auto crash_visibility = has_pending_navigation
? CrashVisibility::kShownWhileAncestorIsLoading
: CrashVisibility::kShownAfterCrashing;
if (!MaybeLogCrash(crash_visibility))
return;
// Identify cases where the sad frame was initially in a hidden tab, then the
// tab became visible, and finally the sad frame became visible because it
// was scrolled into view or its visibility changed. Record these cases
// separately, since they might be avoided by reloading the tab when it
// becomes visible.
if (delegate_was_shown_after_crash_) {
if (reason == ShownAfterCrashingReason::kViewportIntersection)
reason = ShownAfterCrashingReason::kViewportIntersectionAfterTabWasShown;
else if (reason == ShownAfterCrashingReason::kVisibility)
reason = ShownAfterCrashingReason::kVisibilityAfterTabWasShown;
}
UMA_HISTOGRAM_ENUMERATION(
"Stability.ChildFrameCrash.ShownAfterCrashingReason", reason);
}
void CrossProcessFrameConnector::DelegateWasShown() {
if (IsVisible()) {
// MaybeLogShownCrash will check 1) if there was a crash or not and 2) if
// the crash might have been already logged earlier as
// kCrashedWhileVisible.
MaybeLogShownCrash(
CrossProcessFrameConnector::ShownAfterCrashingReason::kTabWasShown);
}
if (has_crashed_)
delegate_was_shown_after_crash_ = true;
}
bool CrossProcessFrameConnector::IsVisible() {
if (visibility_ == blink::mojom::FrameVisibility::kNotRendered ||
intersection_state().viewport_intersection.IsEmpty()) {
return false;
}
if (!current_child_frame_host()) {
return true;
}
if (EmbedderVisibility() != Visibility::VISIBLE) {
return false;
}
return true;
}
Visibility CrossProcessFrameConnector::EmbedderVisibility() {
return current_child_frame_host()->delegate()->GetVisibility();
}
RenderFrameHostImpl* CrossProcessFrameConnector::current_child_frame_host()
const {
return frame_proxy_in_parent_renderer_
? frame_proxy_in_parent_renderer_->frame_tree_node()
->current_frame_host()
: nullptr;
}
} // namespace content