blob: e2dcf9909f67d367ed5d37ed440706b9778cae69 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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/frame_view.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
#include "third_party/blink/renderer/core/frame/frame_client.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/remote_frame.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_geometry.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer.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 "ui/gfx/transform.h"
namespace blink {
FrameView::FrameView(const IntRect& frame_rect)
: EmbeddedContentView(frame_rect),
frame_visibility_(blink::mojom::FrameVisibility::kRenderedInViewport) {}
Frame& FrameView::GetFrame() const {
if (const LocalFrameView* lfv = DynamicTo<LocalFrameView>(this))
return lfv->GetFrame();
return DynamicTo<RemoteFrameView>(this)->GetFrame();
}
bool FrameView::CanThrottleRenderingForPropagation() const {
if (CanThrottleRendering())
return true;
Frame& frame = GetFrame();
if (!frame.IsCrossOriginToMainFrame())
return false;
if (frame.IsLocalFrame() && To<LocalFrame>(frame).IsHidden())
return true;
LocalFrame* parent_frame = DynamicTo<LocalFrame>(GetFrame().Tree().Parent());
return (parent_frame && !frame.OwnerLayoutObject());
}
bool FrameView::DisplayLockedInParentFrame() {
Frame& frame = GetFrame();
LayoutEmbeddedContent* owner = frame.OwnerLayoutObject();
if (!owner)
return false;
DCHECK(owner->GetFrameView());
if (owner->GetFrameView()->IsDisplayLocked())
return true;
// We check the inclusive ancestor to determine whether the subtree is locked,
// since the contents of the frame are in the subtree of the frame, so they
// would be locked if the frame owner is itself locked.
return DisplayLockUtilities::NearestLockedInclusiveAncestor(*owner);
}
void FrameView::UpdateViewportIntersection(unsigned flags,
bool needs_occlusion_tracking) {
if (!(flags & IntersectionObservation::kImplicitRootObserversNeedUpdate))
return;
// This should only run in child frames.
Frame& frame = GetFrame();
HTMLFrameOwnerElement* owner_element = frame.DeprecatedLocalOwner();
if (!owner_element)
return;
Document& owner_document = owner_element->GetDocument();
IntRect viewport_intersection, mainframe_intersection;
TransformationMatrix main_frame_transform_matrix;
DocumentLifecycle::LifecycleState parent_lifecycle_state =
owner_document.Lifecycle().GetState();
mojom::blink::FrameOcclusionState occlusion_state =
owner_document.GetFrame()->GetOcclusionState();
bool should_compute_occlusion =
needs_occlusion_tracking &&
occlusion_state ==
mojom::blink::FrameOcclusionState::kGuaranteedNotOccluded &&
parent_lifecycle_state >= DocumentLifecycle::kPrePaintClean;
LayoutEmbeddedContent* owner_layout_object =
owner_element->GetLayoutEmbeddedContent();
if (!owner_layout_object || owner_layout_object->ContentSize().IsEmpty()) {
// The frame is detached from layout, not visible, or zero size; leave
// viewport_intersection empty, and signal the frame as occluded if
// necessary.
occlusion_state = mojom::blink::FrameOcclusionState::kPossiblyOccluded;
} else if (parent_lifecycle_state >= DocumentLifecycle::kLayoutClean &&
!owner_document.View()->NeedsLayout()) {
unsigned geometry_flags =
IntersectionGeometry::kShouldUseReplacedContentRect;
if (should_compute_occlusion)
geometry_flags |= IntersectionGeometry::kShouldComputeVisibility;
IntersectionGeometry geometry(nullptr, *owner_element, {} /* root_margin */,
{IntersectionObserver::kMinimumThreshold},
{} /* target_margin */, geometry_flags);
PhysicalRect new_rect_in_parent = geometry.IntersectionRect();
if (new_rect_in_parent.size != rect_in_parent_.size ||
((new_rect_in_parent.X() - rect_in_parent_.X()).Abs() +
(new_rect_in_parent.Y() - rect_in_parent_.Y()).Abs() >
LayoutUnit(mojom::blink::kMaxChildFrameScreenRectMovement))) {
rect_in_parent_ = new_rect_in_parent;
if (Page* page = GetFrame().GetPage()) {
rect_in_parent_stable_since_ = page->Animator().Clock().CurrentTime();
} else {
rect_in_parent_stable_since_ = base::TimeTicks::Now();
}
}
if (should_compute_occlusion && !geometry.IsVisible())
occlusion_state = mojom::blink::FrameOcclusionState::kPossiblyOccluded;
// Generate matrix to transform from the space of the containing document
// to the space of the iframe's contents.
TransformState parent_frame_to_iframe_content_transform(
TransformState::kUnapplyInverseTransformDirection);
// First transform to box coordinates of the iframe element...
owner_layout_object->MapAncestorToLocal(
nullptr, parent_frame_to_iframe_content_transform, 0);
// ... then apply content_box_offset to translate to the coordinate of the
// child frame.
parent_frame_to_iframe_content_transform.Move(
owner_layout_object->PhysicalContentBoxOffset());
TransformationMatrix matrix =
parent_frame_to_iframe_content_transform.AccumulatedTransform()
.Inverse();
if (geometry.IsIntersecting()) {
PhysicalRect intersection_rect = PhysicalRect::EnclosingRect(
matrix.ProjectQuad(FloatRect(geometry.IntersectionRect()))
.BoundingBox());
// Don't let EnclosingRect turn an empty rect into a non-empty one.
if (intersection_rect.IsEmpty()) {
viewport_intersection =
IntRect(FlooredIntPoint(intersection_rect.offset), IntSize());
} else {
viewport_intersection = EnclosingIntRect(intersection_rect);
}
// Because the geometry code uses enclosing rects, we may end up with an
// intersection rect that is bigger than the rect we started with. Clamp
// the size of the viewport intersection to the bounds of the iframe's
// content rect.
viewport_intersection.SetLocation(
viewport_intersection.Location().ExpandedTo(IntPoint()));
viewport_intersection.SetSize(viewport_intersection.Size().ShrunkTo(
RoundedIntSize(owner_layout_object->ContentSize())));
}
PhysicalRect mainframe_intersection_rect;
if (!geometry.UnclippedIntersectionRect().IsEmpty()) {
mainframe_intersection_rect = PhysicalRect::EnclosingRect(
matrix.ProjectQuad(FloatRect(geometry.UnclippedIntersectionRect()))
.BoundingBox());
if (mainframe_intersection_rect.IsEmpty()) {
mainframe_intersection = IntRect(
FlooredIntPoint(mainframe_intersection_rect.offset), IntSize());
} else {
mainframe_intersection = EnclosingIntRect(mainframe_intersection_rect);
}
mainframe_intersection.SetLocation(
mainframe_intersection.Location().ExpandedTo(IntPoint()));
mainframe_intersection.SetSize(mainframe_intersection.Size().ShrunkTo(
RoundedIntSize(owner_layout_object->ContentSize())));
}
TransformState child_frame_to_root_frame(
TransformState::kUnapplyInverseTransformDirection);
if (owner_layout_object) {
owner_layout_object->MapAncestorToLocal(
nullptr, child_frame_to_root_frame,
kTraverseDocumentBoundaries | kApplyRemoteMainFrameTransform);
child_frame_to_root_frame.Move(
owner_layout_object->PhysicalContentBoxOffset());
}
main_frame_transform_matrix =
child_frame_to_root_frame.AccumulatedTransform();
} else if (occlusion_state ==
mojom::blink::FrameOcclusionState::kGuaranteedNotOccluded) {
// If the parent LocalFrameView is throttled and out-of-date, then we can't
// get any useful information.
occlusion_state = mojom::blink::FrameOcclusionState::kUnknown;
}
// An iframe's content is always pixel-snapped, even if the iframe element has
// non-pixel-aligned location.
gfx::Transform main_frame_gfx_transform =
TransformationMatrix::ToTransform(main_frame_transform_matrix);
main_frame_gfx_transform.RoundTranslationComponents();
SetViewportIntersection(mojom::blink::ViewportIntersectionState(
viewport_intersection, mainframe_intersection, gfx::Rect(),
occlusion_state, gfx::Size(frame.GetMainFrameViewportSize()),
gfx::Point(frame.GetMainFrameScrollOffset()), main_frame_gfx_transform));
UpdateFrameVisibility(!viewport_intersection.IsEmpty());
if (ShouldReportMainFrameIntersection()) {
IntRect projected_rect = EnclosingIntRect(PhysicalRect::EnclosingRect(
main_frame_transform_matrix
.ProjectQuad(FloatRect(IntRect(mainframe_intersection)))
.BoundingBox()));
// Return <0, 0, 0, 0> if there is no area.
if (projected_rect.IsEmpty())
projected_rect.SetLocation(IntPoint(0, 0));
GetFrame().Client()->OnMainFrameIntersectionChanged(projected_rect);
}
// We don't throttle 0x0 or display:none iframes, because in practice they are
// sometimes used to drive UI logic.
bool hidden_for_throttling = viewport_intersection.IsEmpty() &&
!FrameRect().IsEmpty() && owner_layout_object;
bool subtree_throttled = false;
Frame* parent_frame = GetFrame().Tree().Parent();
if (parent_frame && parent_frame->View()) {
subtree_throttled =
parent_frame->View()->CanThrottleRenderingForPropagation();
}
UpdateRenderThrottlingStatus(hidden_for_throttling, subtree_throttled,
DisplayLockedInParentFrame());
}
void FrameView::UpdateFrameVisibility(bool intersects_viewport) {
blink::mojom::FrameVisibility frame_visibility;
if (LifecycleUpdatesThrottled())
return;
if (IsVisible()) {
frame_visibility =
intersects_viewport
? blink::mojom::FrameVisibility::kRenderedInViewport
: blink::mojom::FrameVisibility::kRenderedOutOfViewport;
} else {
frame_visibility = blink::mojom::FrameVisibility::kNotRendered;
}
if (frame_visibility != frame_visibility_) {
frame_visibility_ = frame_visibility;
VisibilityChanged(frame_visibility);
}
}
void FrameView::UpdateRenderThrottlingStatus(bool hidden_for_throttling,
bool subtree_throttled,
bool display_locked,
bool recurse) {
bool visibility_changed =
(hidden_for_throttling_ || subtree_throttled_ || display_locked_) !=
(hidden_for_throttling || subtree_throttled || display_locked);
hidden_for_throttling_ = hidden_for_throttling;
subtree_throttled_ = subtree_throttled;
display_locked_ = display_locked;
if (visibility_changed)
VisibilityForThrottlingChanged();
if (recurse) {
for (Frame* child = GetFrame().Tree().FirstChild(); child;
child = child->Tree().NextSibling()) {
if (FrameView* child_view = child->View()) {
child_view->UpdateRenderThrottlingStatus(
child_view->IsHiddenForThrottling(),
child_view->IsAttached() && CanThrottleRenderingForPropagation(),
child_view->IsDisplayLocked(), true);
}
}
}
}
bool FrameView::RectInParentIsStable(
const base::TimeTicks& event_timestamp) const {
if (event_timestamp - rect_in_parent_stable_since_ <
base::TimeDelta::FromMilliseconds(
mojom::blink::kMinScreenRectStableTimeMs)) {
return false;
}
LocalFrameView* parent = ParentFrameView();
if (!parent)
return true;
return parent->RectInParentIsStable(event_timestamp);
}
} // namespace blink