blob: 574b399104919941b5b0242b78927381b421f277 [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 "third_party/blink/renderer/core/frame/remote_frame_view.h"
#include <algorithm>
#include "components/paint_preview/common/paint_preview_tracker.h"
#include "printing/buildflags/buildflags.h"
#include "third_party/blink/public/common/frame/frame_owner_element_type.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/remote_frame.h"
#include "third_party/blink/renderer/core/frame/remote_frame_client.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.h"
#include "third_party/blink/renderer/platform/widget/frame_widget.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/transform_util.h"
#if BUILDFLAG(ENABLE_PRINTING)
// nogncheck because dependency on //printing is conditional upon
// enable_printing flags.
#include "printing/metafile_skia.h" // nogncheck
#endif
namespace blink {
RemoteFrameView::RemoteFrameView(RemoteFrame* remote_frame)
: FrameView(gfx::Rect()), remote_frame_(remote_frame) {
DCHECK(remote_frame);
Show();
}
RemoteFrameView::~RemoteFrameView() = default;
LocalFrameView* RemoteFrameView::ParentFrameView() const {
if (!IsAttached())
return nullptr;
HTMLFrameOwnerElement* owner = remote_frame_->DeprecatedLocalOwner();
if (owner && owner->OwnerType() == FrameOwnerElementType::kFencedframe) {
return owner->GetDocument().GetFrame()->View();
}
// |is_attached_| is only set from AttachToLayout(), which ensures that the
// parent is a local frame.
return To<LocalFrame>(remote_frame_->Tree().Parent())->View();
}
LayoutEmbeddedContent* RemoteFrameView::GetLayoutEmbeddedContent() const {
return remote_frame_->OwnerLayoutObject();
}
LocalFrameView* RemoteFrameView::ParentLocalRootFrameView() const {
if (!IsAttached())
return nullptr;
HTMLFrameOwnerElement* owner = remote_frame_->DeprecatedLocalOwner();
if (owner && owner->OwnerType() == FrameOwnerElementType::kFencedframe) {
return owner->GetDocument().GetFrame()->LocalFrameRoot().View();
}
// |is_attached_| is only set from AttachToLayout(), which ensures that the
// parent is a local frame.
return To<LocalFrame>(remote_frame_->Tree().Parent())
->LocalFrameRoot()
.View();
}
void RemoteFrameView::AttachToLayout() {
DCHECK(!IsAttached());
SetAttached(true);
if (ParentFrameView()->IsVisible())
SetParentVisible(true);
UpdateFrameVisibility(true);
UpdateRenderThrottlingStatus(
IsHiddenForThrottling(),
ParentFrameView()->CanThrottleRenderingForPropagation(),
IsDisplayLocked());
needs_frame_rect_propagation_ = true;
ParentFrameView()->SetNeedsUpdateGeometries();
}
void RemoteFrameView::DetachFromLayout() {
DCHECK(IsAttached());
SetParentVisible(false);
SetAttached(false);
}
bool RemoteFrameView::UpdateViewportIntersectionsForSubtree(
unsigned parent_flags,
std::optional<base::TimeTicks>&) {
UpdateViewportIntersection(parent_flags, needs_occlusion_tracking_);
return needs_occlusion_tracking_;
}
void RemoteFrameView::SetViewportIntersection(
const mojom::blink::ViewportIntersectionState& intersection_state) {
mojom::blink::ViewportIntersectionState new_state(intersection_state);
new_state.compositor_visible_rect = compositing_rect_;
if (!last_intersection_state_.Equals(new_state)) {
last_intersection_state_ = new_state;
remote_frame_->SetViewportIntersection(new_state);
} else if (needs_frame_rect_propagation_) {
PropagateFrameRects();
}
}
void RemoteFrameView::SetNeedsOcclusionTracking(bool needs_tracking) {
if (needs_occlusion_tracking_ == needs_tracking)
return;
needs_occlusion_tracking_ = needs_tracking;
if (needs_tracking) {
if (LocalFrameView* parent_view = ParentLocalRootFrameView())
parent_view->ScheduleAnimation();
}
}
gfx::Rect RemoteFrameView::ComputeCompositingRect() const {
LocalFrameView* local_root_view = ParentLocalRootFrameView();
LayoutEmbeddedContent* owner_layout_object =
remote_frame_->OwnerLayoutObject();
// For main frames we constrain the rect that gets painted to the viewport.
// If the local frame root is an OOPIF itself, then we use the root's
// intersection rect. This represents a conservative maximum for the area
// that needs to be rastered by the OOPIF compositor.
gfx::Rect viewport_rect(gfx::Point(), local_root_view->Size());
if (local_root_view->GetPage()->MainFrame() != local_root_view->GetFrame()) {
viewport_rect = local_root_view->GetFrame().RemoteViewportIntersection();
}
// The viewport rect needs to account for intermediate CSS transforms before
// being compared to the frame size.
TransformState local_root_transform_state(
TransformState::kApplyTransformDirection);
local_root_transform_state.Move(
owner_layout_object->PhysicalContentBoxOffset());
owner_layout_object->MapLocalToAncestor(nullptr, local_root_transform_state,
kTraverseDocumentBoundaries);
gfx::Transform matrix =
local_root_transform_state.AccumulatedTransform().InverseOrIdentity();
PhysicalRect local_viewport_rect = PhysicalRect::EnclosingRect(
matrix.ProjectQuad(gfx::QuadF(gfx::RectF(viewport_rect))).BoundingBox());
gfx::Rect compositing_rect = ToEnclosingRect(local_viewport_rect);
gfx::Size frame_size = Size();
// Iframes that fit within the window viewport get fully rastered. For
// iframes that are larger than the window viewport, add a 30% buffer to the
// draw area to try to prevent guttering during scroll.
// TODO(kenrb): The 30% value is arbitrary, it gives 15% overdraw in both
// directions when the iframe extends beyond both edges of the viewport, and
// it seems to make guttering rare with slow to medium speed wheel scrolling.
// Can we collect UMA data to estimate how much extra rastering this causes,
// and possibly how common guttering is?
compositing_rect.Outset(
gfx::Outsets::VH(ceilf(local_viewport_rect.Height() * 0.15f),
ceilf(local_viewport_rect.Width() * 0.15f)));
compositing_rect.set_width(
std::min(frame_size.width(), compositing_rect.width()));
compositing_rect.set_height(
std::min(frame_size.height(), compositing_rect.height()));
gfx::Point compositing_rect_location = compositing_rect.origin();
compositing_rect_location.SetToMax(gfx::Point());
compositing_rect.set_origin(compositing_rect_location);
return compositing_rect;
}
void RemoteFrameView::UpdateCompositingRect() {
remote_frame_->UpdateCompositedLayerBounds();
gfx::Rect previous_rect = compositing_rect_;
compositing_rect_ = gfx::Rect();
LocalFrameView* local_root_view = ParentLocalRootFrameView();
LayoutEmbeddedContent* owner_layout_object =
remote_frame_->OwnerLayoutObject();
if (!local_root_view || !owner_layout_object) {
needs_frame_rect_propagation_ = true;
return;
}
// The |compositing_rect_| provides the child compositor the rectangle (in its
// local coordinate space) which should be rasterized/composited. Its based on
// the child frame's intersection with the viewport and an optimization to
// avoid large iframes rasterizing their complete viewport.
// Since this rectangle is dependent on the child frame's position in the
// embedding frame, updating this can be used for communication with a fenced
// frame. So if the frame size is frozen, we use the complete viewport of the
// child frame as its compositing rect.
if (frozen_size_) {
compositing_rect_ = gfx::Rect(*frozen_size_);
} else {
compositing_rect_ = ComputeCompositingRect();
}
if (compositing_rect_ != previous_rect)
needs_frame_rect_propagation_ = true;
}
void RemoteFrameView::UpdateCompositingScaleFactor() {
float previous_scale_factor = compositing_scale_factor_;
LocalFrameView* local_root_view = ParentLocalRootFrameView();
LayoutEmbeddedContent* owner_layout_object =
remote_frame_->OwnerLayoutObject();
if (!local_root_view || !owner_layout_object)
return;
TransformState local_root_transform_state(
TransformState::kApplyTransformDirection);
local_root_transform_state.Move(
owner_layout_object->PhysicalContentBoxOffset());
owner_layout_object->MapLocalToAncestor(nullptr, local_root_transform_state,
kTraverseDocumentBoundaries);
float frame_to_local_root_scale_factor = 1.0f;
gfx::Transform local_root_transform =
local_root_transform_state.AccumulatedTransform();
std::optional<gfx::Vector2dF> scale_components =
gfx::TryComputeTransform2dScaleComponents(local_root_transform);
if (!scale_components) {
frame_to_local_root_scale_factor =
gfx::ComputeApproximateMaxScale(local_root_transform);
} else {
frame_to_local_root_scale_factor =
std::max(scale_components->x(), scale_components->y());
}
// The compositing scale factor is calculated by multiplying the scale factor
// from the local root to main frame with the scale factor between child frame
// and local root.
FrameWidget* widget = local_root_view->GetFrame().GetWidgetForLocalRoot();
compositing_scale_factor_ =
widget->GetCompositingScaleFactor() * frame_to_local_root_scale_factor;
// Force compositing scale factor to be within reasonable minimum and maximum
// values to prevent dependent values such as scroll deltas in the compositor
// going to zero or extremely high memory usage due to large raster scales.
// It's possible for the calculated scale factor to become very large or very
// small since it depends on arbitrary intermediate CSS transforms.
constexpr float kMinCompositingScaleFactor = 0.25f;
constexpr float kMaxCompositingScaleFactor = 5.0f;
compositing_scale_factor_ =
std::clamp(compositing_scale_factor_, kMinCompositingScaleFactor,
kMaxCompositingScaleFactor);
if (compositing_scale_factor_ != previous_scale_factor)
remote_frame_->SynchronizeVisualProperties();
}
void RemoteFrameView::Dispose() {
HTMLFrameOwnerElement* owner_element = remote_frame_->DeprecatedLocalOwner();
// ownerElement can be null during frame swaps, because the
// RemoteFrameView is disconnected before detachment.
if (owner_element && owner_element->OwnedEmbeddedContentView() == this)
owner_element->SetEmbeddedContentView(nullptr);
SetNeedsOcclusionTracking(false);
}
void RemoteFrameView::SetFrameRect(const gfx::Rect& rect) {
UpdateFrozenSize();
EmbeddedContentView::SetFrameRect(rect);
if (needs_frame_rect_propagation_)
PropagateFrameRects();
}
void RemoteFrameView::UpdateFrozenSize() {
if (frozen_size_)
return;
auto* layout_embedded_content = GetLayoutEmbeddedContent();
if (!layout_embedded_content)
return;
std::optional<PhysicalSize> frozen_phys_size =
layout_embedded_content->FrozenFrameSize();
if (!frozen_phys_size)
return;
const gfx::Size rounded_frozen_size(frozen_phys_size->width.Ceil(),
frozen_phys_size->height.Ceil());
frozen_size_ = rounded_frozen_size;
needs_frame_rect_propagation_ = true;
}
void RemoteFrameView::ZoomChanged(float zoom_factor) {
remote_frame_->ZoomLevelChanged(PageZoomFactorToZoomLevel(zoom_factor));
}
void RemoteFrameView::PropagateFrameRects() {
// Update the rect to reflect the position of the frame relative to the
// containing local frame root. The position of the local root within
// any remote frames, if any, is accounted for by the embedder.
needs_frame_rect_propagation_ = false;
gfx::Rect frame_rect(FrameRect());
gfx::Rect rect_in_local_root = frame_rect;
if (LocalFrameView* parent = ParentFrameView()) {
rect_in_local_root = parent->ConvertToRootFrame(rect_in_local_root);
}
gfx::Size frame_size = frozen_size_.value_or(frame_rect.size());
remote_frame_->FrameRectsChanged(frame_size, rect_in_local_root);
}
void RemoteFrameView::Paint(GraphicsContext& context,
PaintFlags flags,
const CullRect& rect,
const gfx::Vector2d& paint_offset) const {
if (!rect.Intersects(FrameRect()))
return;
const auto& owner_layout_object = *GetFrame().OwnerLayoutObject();
if (owner_layout_object.GetDocument().IsPrintingOrPaintingPreview()) {
DrawingRecorder recorder(context, owner_layout_object,
DisplayItem::kDocumentBackground);
context.Save();
context.Translate(paint_offset.x(), paint_offset.y());
DCHECK(context.Canvas());
uint32_t content_id = 0;
if (owner_layout_object.GetDocument().Printing()) {
// Inform the remote frame to print.
content_id = Print(FrameRect(), context.Canvas());
} else {
DCHECK_NE(Document::kNotPaintingPreview,
owner_layout_object.GetDocument().GetPaintPreviewState());
// Inform the remote frame to capture a paint preview.
content_id = CapturePaintPreview(FrameRect(), context.Canvas());
}
// Record the place holder id on canvas.
context.Canvas()->recordCustomData(content_id);
context.Restore();
}
if (GetFrame().GetCcLayer()) {
RecordForeignLayer(
context, owner_layout_object, DisplayItem::kForeignLayerRemoteFrame,
GetFrame().GetCcLayer(), FrameRect().origin() + paint_offset);
}
}
void RemoteFrameView::UpdateGeometry() {
if (LayoutEmbeddedContent* layout = GetLayoutEmbeddedContent())
layout->UpdateGeometry(*this);
}
void RemoteFrameView::Hide() {
SetSelfVisible(false);
UpdateFrameVisibility(
!last_intersection_state_.viewport_intersection.IsEmpty());
}
void RemoteFrameView::Show() {
SetSelfVisible(true);
UpdateFrameVisibility(
!last_intersection_state_.viewport_intersection.IsEmpty());
}
void RemoteFrameView::ParentVisibleChanged() {
if (IsSelfVisible()) {
UpdateFrameVisibility(
!last_intersection_state_.viewport_intersection.IsEmpty());
}
}
void RemoteFrameView::VisibilityForThrottlingChanged() {
TRACE_EVENT0("blink", "RemoteFrameView::VisibilityForThrottlingChanged");
// TODO(szager,vmpstr): Send IsSubtreeThrottled() and IsDisplayLocked() as
// separate bits.
remote_frame_->GetRemoteFrameHostRemote().UpdateRenderThrottlingStatus(
IsHiddenForThrottling(), IsSubtreeThrottled(), IsDisplayLocked());
}
void RemoteFrameView::VisibilityChanged(
blink::mojom::FrameVisibility visibility) {
remote_frame_->GetRemoteFrameHostRemote().VisibilityChanged(visibility);
}
bool RemoteFrameView::CanThrottleRendering() const {
return IsHiddenForThrottling() || IsSubtreeThrottled() || IsDisplayLocked();
}
void RemoteFrameView::SetIntrinsicSizeInfo(
const IntrinsicSizingInfo& size_info) {
intrinsic_sizing_info_ = size_info;
has_intrinsic_sizing_info_ = true;
}
bool RemoteFrameView::GetIntrinsicSizingInfo(
IntrinsicSizingInfo& sizing_info) const {
if (!has_intrinsic_sizing_info_)
return false;
sizing_info = intrinsic_sizing_info_;
return true;
}
bool RemoteFrameView::HasIntrinsicSizingInfo() const {
return has_intrinsic_sizing_info_;
}
uint32_t RemoteFrameView::Print(const gfx::Rect& rect,
cc::PaintCanvas* canvas) const {
#if BUILDFLAG(ENABLE_PRINTING)
auto* metafile = canvas->GetPrintingMetafile();
DCHECK(metafile);
// Create a place holder content for the remote frame so it can be replaced
// with actual content later.
// TODO(crbug.com/1093929): Consider to use an embedding token which
// represents the state of the remote frame. See also comments on
// https://crrev.com/c/2245430/.
uint32_t content_id = metafile->CreateContentForRemoteFrame(
rect, remote_frame_->GetFrameToken().value());
// Inform browser to print the remote subframe.
remote_frame_->GetRemoteFrameHostRemote().PrintCrossProcessSubframe(
rect, metafile->GetDocumentCookie());
return content_id;
#else
return 0;
#endif
}
uint32_t RemoteFrameView::CapturePaintPreview(const gfx::Rect& rect,
cc::PaintCanvas* canvas) const {
auto* tracker = canvas->GetPaintPreviewTracker();
DCHECK(tracker); // |tracker| must exist or there is a bug upstream.
// RACE: there is a possibility that the embedding token will be null and
// still be in a valid state. This can occur is the frame has recently
// navigated and the embedding token hasn't propagated from the FrameTreeNode
// to this HTMLFrameOwnerElement yet (over IPC). If the token is null the
// failure can be handled gracefully by simply ignoring the subframe in the
// result.
std::optional<base::UnguessableToken> maybe_embedding_token =
remote_frame_->GetEmbeddingToken();
if (!maybe_embedding_token.has_value())
return 0;
uint32_t content_id =
tracker->CreateContentForRemoteFrame(rect, maybe_embedding_token.value());
// Send a request to the browser to trigger a capture of the remote frame.
remote_frame_->GetRemoteFrameHostRemote()
.CapturePaintPreviewOfCrossProcessSubframe(rect, tracker->Guid());
return content_id;
}
void RemoteFrameView::Trace(Visitor* visitor) const {
visitor->Trace(remote_frame_);
}
} // namespace blink