Skip to content

Commit

Permalink
Get rid of redundant intersection computation
Browse files Browse the repository at this point in the history
Currently, we compute the intersection of a frame with the top-level
viewport in two places: once to control throttling of off-screen
iframes, and again to propagate viewport intersection information to
OOPIF's, to use for running the IntersectionObserver algorithm.

With this CL, the viewport intersection is computed once for both
consumers.

Cherry-picked from:

https://chromium-review.googlesource.com/c/chromium/src/+/1504503

BUG=926539
TBR=chrishtr@chromium.org

Change-Id: Ia7b964a59647d5efb05279f28652e07031181872
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1518715
Reviewed-by: Stefan Zager <szager@chromium.org>
Cr-Commit-Position: refs/branch-heads/3729@{#54}
Cr-Branched-From: d4a8972-refs/heads/master@{#638880}
  • Loading branch information
szager-chromium committed Mar 12, 2019
1 parent b760288 commit 3b6cf30
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 113 deletions.
19 changes: 9 additions & 10 deletions content/browser/site_per_process_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12247,22 +12247,21 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
// case, the raster area of the large iframe should be restricted to
// approximately the area of the smaller iframe in which it is embedded.
int view_height = child->current_frame_host()
->GetRenderWidgetHost()
->GetView()
->GetViewBounds()
.height() *
scale_factor +
5;
->GetRenderWidgetHost()
->GetView()
->GetViewBounds()
.height() *
scale_factor;
int expected_height = view_height * 13 / 10;
int expected_offset =
(5000 * scale_factor) - (expected_height - 185 * scale_factor) / 2;

// Allow a small amount for rounding differences from applying page and
// device scale factors at different times.
EXPECT_GE(compositing_rect.height(), expected_height - 2);
EXPECT_LE(compositing_rect.height(), expected_height + 2);
EXPECT_GE(compositing_rect.y(), expected_offset - 2);
EXPECT_LE(compositing_rect.y(), expected_offset + 2);
EXPECT_GE(compositing_rect.height(), expected_height - 3);
EXPECT_LE(compositing_rect.height(), expected_height + 3);
EXPECT_GE(compositing_rect.y(), expected_offset - 3);
EXPECT_LE(compositing_rect.y(), expected_offset + 3);
}

// Verify that OOPIF select element popup menu coordinates account for scroll
Expand Down
108 changes: 35 additions & 73 deletions third_party/blink/renderer/core/frame/remote_frame_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ void RemoteFrameView::AttachToLayout() {
SetParentVisible(true);
UpdateVisibility(true);

SetupRenderThrottling();
subtree_throttled_ = ParentFrameView()->CanThrottleRendering();

FrameRectsChanged();
Expand All @@ -90,68 +89,54 @@ void RemoteFrameView::UpdateViewportIntersectionsForSubtree(
return;
}

LayoutEmbeddedContent* owner = remote_frame_->OwnerLayoutObject();
// This should only run in child frames.
HTMLFrameOwnerElement* owner_element = remote_frame_->DeprecatedLocalOwner();
DCHECK(owner_element);
LayoutEmbeddedContent* owner = owner_element->GetLayoutEmbeddedContent();
if (!owner)
return;

LocalFrameView* local_root_view = ParentLocalRootFrameView();
if (!local_root_view)
return;

IntRect viewport_intersection;
bool occluded_or_obscured = false;
DocumentLifecycle::LifecycleState parent_state =
owner->GetDocument().Lifecycle().GetState();

// If the parent LocalFrameView is throttled and out-of-date, then we can't
// get any useful information.
DocumentLifecycle::LifecycleState parent_state =
owner_element->GetDocument().Lifecycle().GetState();
if (parent_state >= DocumentLifecycle::kLayoutClean) {
// Start with rect in remote frame's coordinate space. Then
// mapToVisualRectInAncestorSpace will move it to the local root's
// coordinate space and account for any clip from containing elements such
// as a scrollable div. Passing nullptr as an argument to
// mapToVisualRectInAncestorSpace causes it to be clipped to the viewport,
// even if there are RemoteFrame ancestors in the frame tree.
LayoutRect rect(0, 0, frame_rect_.Width(), frame_rect_.Height());
rect.Move(owner->PhysicalContentBoxOffset());
if (owner->MapToVisualRectInAncestorSpace(nullptr, rect,
kUseGeometryMapper)) {
IntRect root_visible_rect(IntPoint(), local_root_view->Size());
IntRect intersected_rect = EnclosingIntRect(rect);
intersected_rect.Intersect(root_visible_rect);

// Translate the intersection rect from the root frame's coordinate space
// to the remote frame's coordinate space.
FloatRect viewport_intersection_float =
remote_frame_->OwnerLayoutObject()
->AncestorToLocalQuad(
local_root_view->GetLayoutView(), FloatQuad(intersected_rect),
kTraverseDocumentBoundaries | kUseTransforms)
.BoundingBox();
viewport_intersection_float.Move(
-remote_frame_->OwnerLayoutObject()->PhysicalContentBoxOffset());
viewport_intersection = EnclosingIntRect(viewport_intersection_float);
unsigned geometry_flags =
IntersectionGeometry::kShouldUseReplacedContentRect;
if (parent_state >= DocumentLifecycle::kPrePaintClean &&
RuntimeEnabledFeatures::IntersectionObserverV2Enabled()) {
geometry_flags |= IntersectionGeometry::kShouldComputeVisibility;
}
}

if (parent_state >= DocumentLifecycle::kPrePaintClean &&
RuntimeEnabledFeatures::IntersectionObserverV2Enabled()) {
// TODO(layout-dev): As an optimization, we should only check for
// occlusion and effects if the remote frame needs it, i.e., if it has at
// least one active IntersectionObserver with trackVisibility:true.
if (owner->GetDocument()
.GetFrame()
->LocalFrameRoot()
.MayBeOccludedOrObscuredByRemoteAncestor() ||
owner->HasDistortingVisualEffects()) {
occluded_or_obscured = true;
IntersectionGeometry geometry(nullptr, *owner_element, {},
{IntersectionObserver::kMinimumThreshold},
geometry_flags);
// geometry.IntersectionRect() is in absolute coordinates of the owning
// document. Map it down to absolute coordinates in the child document.
LayoutRect intersection_rect = LayoutRect(
owner
->AncestorToLocalQuad(
nullptr, FloatQuad(FloatRect(geometry.IntersectionRect())),
kUseTransforms)
.BoundingBox());
// Map from the box coordinates of the owner to the inner frame.
intersection_rect.Move(-owner->PhysicalContentBoxOffset());
// Don't let EnclosingIntRect turn an empty rect into a non-empty one.
if (intersection_rect.IsEmpty()) {
viewport_intersection =
IntRect(FlooredIntPoint(intersection_rect.Location()), IntSize());
} else {
HitTestResult result(owner->HitTestForOcclusion());
occluded_or_obscured =
result.InnerNode() && result.InnerNode() != owner->GetNode();
viewport_intersection = EnclosingIntRect(intersection_rect);
}
occluded_or_obscured = !geometry.IsVisible();
}

// TODO(szager): There are some redundant IPC's here; clean them up.
bool is_visible_for_throttling = !viewport_intersection.IsEmpty();
UpdateVisibility(is_visible_for_throttling);
UpdateRenderThrottlingStatus(!is_visible_for_throttling, subtree_throttled_);

if (viewport_intersection == last_viewport_intersection_ &&
occluded_or_obscured == last_occluded_or_obscured_) {
return;
Expand Down Expand Up @@ -341,29 +326,6 @@ void RemoteFrameView::UpdateVisibility(bool scroll_visible) {
remote_frame_->Client()->VisibilityChanged(visibility);
}

void RemoteFrameView::OnViewportIntersectionChanged(
const HeapVector<Member<IntersectionObserverEntry>>& entries) {
bool is_visible = entries.back()->intersectionRatio() > 0;
UpdateVisibility(is_visible);
UpdateRenderThrottlingStatus(!is_visible, subtree_throttled_);
}

void RemoteFrameView::SetupRenderThrottling() {
if (visibility_observer_)
return;

Element* target_element = GetFrame().DeprecatedLocalOwner();
if (!target_element)
return;

visibility_observer_ = IntersectionObserver::Create(
{}, {IntersectionObserver::kMinimumThreshold},
&target_element->GetDocument(),
WTF::BindRepeating(&RemoteFrameView::OnViewportIntersectionChanged,
WrapWeakPersistent(this)));
visibility_observer_->observe(target_element);
}

void RemoteFrameView::UpdateRenderThrottlingStatus(bool hidden,
bool subtree_throttled) {
TRACE_EVENT0("blink", "RemoteFrameView::UpdateRenderThrottlingStatus");
Expand Down
1 change: 0 additions & 1 deletion third_party/blink/renderer/core/frame/remote_frame_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ class RemoteFrameView final : public GarbageCollectedFinalized<RemoteFrameView>,
const HeapVector<Member<IntersectionObserverEntry>>& entries);
void UpdateRenderThrottlingStatus(bool hidden, bool subtree_throttled);
bool CanThrottleRendering() const;
void SetupRenderThrottling();
void UpdateVisibility(bool scroll_visible);

// The properties and handling of the cycle between RemoteFrame
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer_entry.h"
#include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/page.h"
Expand Down Expand Up @@ -74,16 +75,17 @@ bool ComputeIsVisible(LayoutObject* target, const LayoutRect& rect) {
return false;
// TODO(layout-dev): This should hit-test the intersection rect, not the
// target rect; it's not helpful to know that the portion of the target that
// is clipped is also occluded. To do that, the intersection rect must be
// mapped down to the local space of the target element.
// is clipped is also occluded.
HitTestResult result(target->HitTestForOcclusion(rect));
return (!result.InnerNode() || result.InnerNode() == target->GetNode());
}

static const unsigned kConstructorFlagsMask =
IntersectionGeometry::kShouldReportRootBounds |
IntersectionGeometry::kShouldComputeVisibility |
IntersectionGeometry::kShouldTrackFractionOfRoot;
IntersectionGeometry::kShouldTrackFractionOfRoot |
IntersectionGeometry::kShouldUseReplacedContentRect |
IntersectionGeometry::kShouldConvertToCSSPixels;

} // namespace

Expand Down Expand Up @@ -127,20 +129,21 @@ void IntersectionGeometry::ComputeGeometry(Element* root_element,

DCHECK(!target_element.GetDocument().View()->NeedsLayout());

LayoutRect target_rect = InitializeTargetRect(target);
LayoutRect intersection_rect = target_rect;
LayoutRect root_rect = InitializeRootRect(root, root_margin);
bool does_intersect = ClipToRoot(root, target, root_rect, intersection_rect);
MapRectUpToDocument(target_rect, *target);
target_rect_ = InitializeTargetRect(target);
intersection_rect_ = target_rect_;
root_rect_ = InitializeRootRect(root, root_margin);
bool does_intersect =
ClipToRoot(root, target, root_rect_, intersection_rect_);
MapRectUpToDocument(target_rect_, *target);
if (does_intersect) {
if (RootIsImplicit())
MapRectDownToDocument(intersection_rect, target->GetDocument());
MapRectDownToDocument(intersection_rect_, target->GetDocument());
else
MapRectUpToDocument(intersection_rect, *root);
MapRectUpToDocument(intersection_rect_, *root);
} else {
intersection_rect = LayoutRect();
intersection_rect_ = LayoutRect();
}
MapRectUpToDocument(root_rect, *root);
MapRectUpToDocument(root_rect_, *root);

// Some corner cases for threshold index:
// - If target rect is zero area, because it has zero width and/or zero
Expand All @@ -159,11 +162,11 @@ void IntersectionGeometry::ComputeGeometry(Element* root_element,

if (does_intersect) {
const LayoutRect comparison_rect =
ShouldTrackFractionOfRoot() ? root_rect : target_rect;
ShouldTrackFractionOfRoot() ? root_rect_ : target_rect_;
if (comparison_rect.IsEmpty()) {
intersection_ratio_ = 1;
} else {
const LayoutSize& intersection_size = intersection_rect.Size();
const LayoutSize& intersection_size = intersection_rect_.Size();
const float intersection_area = intersection_size.Width().ToFloat() *
intersection_size.Height().ToFloat();
const LayoutSize& comparison_size = comparison_rect.Size();
Expand All @@ -178,22 +181,27 @@ void IntersectionGeometry::ComputeGeometry(Element* root_element,
threshold_index_ = 0;
}
if (IsIntersecting() && ShouldComputeVisibility() &&
ComputeIsVisible(target, target_rect))
ComputeIsVisible(target, target_rect_))
flags_ |= kIsVisible;

// Convert to un-zoomed CSS pixels
FloatRect target_float_rect(target_rect);
AdjustForAbsoluteZoom::AdjustFloatRect(target_float_rect, *target);
target_rect_ = LayoutRect(target_float_rect);
FloatRect intersection_float_rect(intersection_rect);
AdjustForAbsoluteZoom::AdjustFloatRect(intersection_float_rect, *target);
intersection_rect_ = LayoutRect(intersection_float_rect);
FloatRect root_float_rect(root_rect);
AdjustForAbsoluteZoom::AdjustFloatRect(root_float_rect, *root);
root_rect_ = LayoutRect(root_float_rect);
if (flags_ & kShouldConvertToCSSPixels) {
FloatRect target_float_rect(target_rect_);
AdjustForAbsoluteZoom::AdjustFloatRect(target_float_rect, *target);
target_rect_ = LayoutRect(target_float_rect);
FloatRect intersection_float_rect(intersection_rect_);
AdjustForAbsoluteZoom::AdjustFloatRect(intersection_float_rect, *target);
intersection_rect_ = LayoutRect(intersection_float_rect);
FloatRect root_float_rect(root_rect_);
AdjustForAbsoluteZoom::AdjustFloatRect(root_float_rect, *root);
root_rect_ = LayoutRect(root_float_rect);
}
}

LayoutRect IntersectionGeometry::InitializeTargetRect(LayoutObject* target) {
if ((flags_ & kShouldUseReplacedContentRect) &&
target->IsLayoutEmbeddedContent()) {
return ToLayoutEmbeddedContent(target)->ReplacedContentRect();
}
if (target->IsBox())
return LayoutRect(ToLayoutBoxModelObject(target)->BorderBoundingBox());
if (target->IsLayoutInline())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,20 @@ class CORE_EXPORT IntersectionGeometry {
kShouldReportRootBounds = 1 << 0,
kShouldComputeVisibility = 1 << 1,
kShouldTrackFractionOfRoot = 1 << 2,
kShouldUseReplacedContentRect = 1 << 3,
kShouldConvertToCSSPixels = 1 << 4,

// These flags will be computed
kRootIsImplicit = 1 << 3,
kIsVisible = 1 << 4
kRootIsImplicit = 1 << 5,
kIsVisible = 1 << 6
};

IntersectionGeometry(Element* root,
Element& target,
const Vector<Length>& root_margin,
const Vector<float>& thresholds,
unsigned flags);

IntersectionGeometry(const IntersectionGeometry&) = default;
~IntersectionGeometry();

Expand All @@ -55,7 +58,6 @@ class CORE_EXPORT IntersectionGeometry {
return flags_ & kShouldTrackFractionOfRoot;
}

// These are all in CSS pixels
LayoutRect TargetRect() const { return target_rect_; }
LayoutRect IntersectionRect() const { return intersection_rect_; }
LayoutRect RootRect() const { return root_rect_; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ void IntersectionObservation::Compute(unsigned flags) {
bool report_root_bounds = observer_->AlwaysReportRootBounds() ||
(flags & kReportImplicitRootBounds) ||
!observer_->RootIsImplicit();
unsigned geometry_flags = 0;
unsigned geometry_flags = IntersectionGeometry::kShouldConvertToCSSPixels;
if (report_root_bounds)
geometry_flags |= IntersectionGeometry::kShouldReportRootBounds;
if (Observer()->trackVisibility())
Expand Down

0 comments on commit 3b6cf30

Please sign in to comment.