Commit 9ccf3418 authored by Scott Violet's avatar Scott Violet Committed by Chromium LUCI CQ

blink: makes display lock use overflow-clip-rect when appropriate

Specifically, if the box has non visible overflow and
an overflow-clip-margin. Without this the overflow from
contain:paint doesn't take into the account the overflow-clip-margin,
and we may end up not painting when we should.

BUG=1157843
TEST=third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-007.html

Change-Id: Iba11a683f8cc1eb595ef26984c0dce38133fc760
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2626027Reviewed-by: default avatarStefan Zager <szager@chromium.org>
Reviewed-by: default avatarXianzhu Wang <wangxianzhu@chromium.org>
Reviewed-by: default avatarvmpstr <vmpstr@chromium.org>
Commit-Queue: Scott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#844846}
parent 89e4a67b
...@@ -91,6 +91,9 @@ IntersectionObserver& DisplayLockDocumentState::EnsureIntersectionObserver() { ...@@ -91,6 +91,9 @@ IntersectionObserver& DisplayLockDocumentState::EnsureIntersectionObserver() {
// //
// Note that we use 150% margin (on the viewport) so that we get the // Note that we use 150% margin (on the viewport) so that we get the
// observation before the element enters the viewport. // observation before the element enters the viewport.
//
// Paint containment requires using the overflow clip edge. To do otherwise
// results in overflow-clip-margin not being painted in certain scenarios.
intersection_observer_ = IntersectionObserver::Create( intersection_observer_ = IntersectionObserver::Create(
{Length::Percent(150.f)}, {std::numeric_limits<float>::min()}, {Length::Percent(150.f)}, {std::numeric_limits<float>::min()},
document_, document_,
...@@ -101,7 +104,8 @@ IntersectionObserver& DisplayLockDocumentState::EnsureIntersectionObserver() { ...@@ -101,7 +104,8 @@ IntersectionObserver& DisplayLockDocumentState::EnsureIntersectionObserver() {
IntersectionObserver::kDeliverDuringPostLayoutSteps, IntersectionObserver::kDeliverDuringPostLayoutSteps,
IntersectionObserver::kFractionOfTarget, 0 /* delay */, IntersectionObserver::kFractionOfTarget, 0 /* delay */,
false /* track_visibility */, false /* always report_root_bounds */, false /* track_visibility */, false /* always report_root_bounds */,
IntersectionObserver::kApplyMarginToTarget); IntersectionObserver::kApplyMarginToTarget,
true /* use_overflow_clip_edge */);
} }
return *intersection_observer_; return *intersection_observer_;
} }
......
...@@ -97,6 +97,17 @@ PhysicalRect InitializeRootRect(const LayoutObject* root, ...@@ -97,6 +97,17 @@ PhysicalRect InitializeRootRect(const LayoutObject* root,
return result; return result;
} }
PhysicalRect GetBoxBounds(const LayoutBox* box, bool use_overflow_clip_edge) {
PhysicalRect bounds = PhysicalRect(box->BorderBoundingBox());
// OverflowClipMargin() should only apply if clipping occurs on both axis.
if (use_overflow_clip_edge && box->ShouldClipOverflowAlongBothAxis() &&
box->StyleRef().OverflowClipMargin() != LayoutUnit()) {
// OverflowClipRect() may be smaller than BorderBoundingBox().
bounds.Unite(box->OverflowClipRect(PhysicalOffset()));
}
return bounds;
}
// Return the bounding box of target in target's own coordinate system, also // Return the bounding box of target in target's own coordinate system, also
// return a bool indicating whether the target rect before margin application // return a bool indicating whether the target rect before margin application
// was empty. // was empty.
...@@ -109,7 +120,10 @@ std::pair<PhysicalRect, bool> InitializeTargetRect(const LayoutObject* target, ...@@ -109,7 +120,10 @@ std::pair<PhysicalRect, bool> InitializeTargetRect(const LayoutObject* target,
target->IsLayoutEmbeddedContent()) { target->IsLayoutEmbeddedContent()) {
result.first = To<LayoutEmbeddedContent>(target)->ReplacedContentRect(); result.first = To<LayoutEmbeddedContent>(target)->ReplacedContentRect();
} else if (target->IsBox()) { } else if (target->IsBox()) {
result.first = PhysicalRect(To<LayoutBox>(target)->BorderBoundingBox()); result.first =
GetBoxBounds(To<LayoutBox>(target),
(flags & IntersectionGeometry::kUseOverflowClipEdge) ==
IntersectionGeometry::kUseOverflowClipEdge);
} else if (target->IsLayoutInline()) { } else if (target->IsLayoutInline()) {
result.first = target->AbsoluteToLocalRect( result.first = target->AbsoluteToLocalRect(
PhysicalRect::EnclosingRect(target->AbsoluteBoundingBoxFloatRect())); PhysicalRect::EnclosingRect(target->AbsoluteBoundingBoxFloatRect()));
...@@ -186,7 +200,8 @@ static const unsigned kConstructorFlagsMask = ...@@ -186,7 +200,8 @@ static const unsigned kConstructorFlagsMask =
IntersectionGeometry::kShouldTrackFractionOfRoot | IntersectionGeometry::kShouldTrackFractionOfRoot |
IntersectionGeometry::kShouldUseReplacedContentRect | IntersectionGeometry::kShouldUseReplacedContentRect |
IntersectionGeometry::kShouldConvertToCSSPixels | IntersectionGeometry::kShouldConvertToCSSPixels |
IntersectionGeometry::kShouldUseCachedRects; IntersectionGeometry::kShouldUseCachedRects |
IntersectionGeometry::kUseOverflowClipEdge;
} // namespace } // namespace
......
...@@ -36,10 +36,13 @@ class CORE_EXPORT IntersectionGeometry { ...@@ -36,10 +36,13 @@ class CORE_EXPORT IntersectionGeometry {
kShouldUseReplacedContentRect = 1 << 3, kShouldUseReplacedContentRect = 1 << 3,
kShouldConvertToCSSPixels = 1 << 4, kShouldConvertToCSSPixels = 1 << 4,
kShouldUseCachedRects = 1 << 5, kShouldUseCachedRects = 1 << 5,
// Applies to boxes. If true, OverflowClipRect() is used if necessary
// instead of BorderBoundingBox().
kUseOverflowClipEdge = 1 << 6,
// These flags will be computed // These flags will be computed
kRootIsImplicit = 1 << 6, kRootIsImplicit = 1 << 7,
kIsVisible = 1 << 7 kIsVisible = 1 << 8
}; };
struct RootGeometry { struct RootGeometry {
......
...@@ -200,6 +200,8 @@ unsigned IntersectionObservation::GetIntersectionGeometryFlags( ...@@ -200,6 +200,8 @@ unsigned IntersectionObservation::GetIntersectionGeometryFlags(
geometry_flags |= IntersectionGeometry::kShouldTrackFractionOfRoot; geometry_flags |= IntersectionGeometry::kShouldTrackFractionOfRoot;
if (CanUseCachedRects()) if (CanUseCachedRects())
geometry_flags |= IntersectionGeometry::kShouldUseCachedRects; geometry_flags |= IntersectionGeometry::kShouldUseCachedRects;
if (Observer()->UseOverflowClipEdge())
geometry_flags |= IntersectionGeometry::kUseOverflowClipEdge;
return geometry_flags; return geometry_flags;
} }
......
...@@ -46,6 +46,8 @@ class CORE_EXPORT IntersectionObservation final ...@@ -46,6 +46,8 @@ class CORE_EXPORT IntersectionObservation final
// If this bit is set, we only process intersection observations that // If this bit is set, we only process intersection observations that
// require post-layout delivery. // require post-layout delivery.
kPostLayoutDeliveryOnly = 1 << 5, kPostLayoutDeliveryOnly = 1 << 5,
// If this is set, the overflow clip edge is used.
kUseOverflowClipEdge = 1 << 6,
}; };
IntersectionObservation(IntersectionObserver&, Element&); IntersectionObservation(IntersectionObserver&, Element&);
...@@ -87,7 +89,7 @@ class CORE_EXPORT IntersectionObservation final ...@@ -87,7 +89,7 @@ class CORE_EXPORT IntersectionObservation final
unsigned last_is_visible_ : 1; unsigned last_is_visible_ : 1;
unsigned needs_update_ : 1; unsigned needs_update_ : 1;
unsigned last_threshold_index_ : 30; unsigned last_threshold_index_ : 30;
static const unsigned kMaxThresholdIndex = (unsigned)0x40000000; static const unsigned kMaxThresholdIndex = static_cast<unsigned>(0x40000000);
}; };
} // namespace blink } // namespace blink
......
...@@ -208,7 +208,7 @@ IntersectionObserver* IntersectionObserver::Create( ...@@ -208,7 +208,7 @@ IntersectionObserver* IntersectionObserver::Create(
return MakeGarbageCollected<IntersectionObserver>( return MakeGarbageCollected<IntersectionObserver>(
delegate, root, margin, thresholds, kFractionOfTarget, delay, delegate, root, margin, thresholds, kFractionOfTarget, delay,
track_visibility, false, kApplyMarginToRoot); track_visibility, false, kApplyMarginToRoot, false);
} }
IntersectionObserver* IntersectionObserver::Create( IntersectionObserver* IntersectionObserver::Create(
...@@ -238,6 +238,7 @@ IntersectionObserver* IntersectionObserver::Create( ...@@ -238,6 +238,7 @@ IntersectionObserver* IntersectionObserver::Create(
bool track_visibility, bool track_visibility,
bool always_report_root_bounds, bool always_report_root_bounds,
MarginTarget margin_target, MarginTarget margin_target,
bool use_overflow_clip_edge,
ExceptionState& exception_state) { ExceptionState& exception_state) {
IntersectionObserverDelegateImpl* intersection_observer_delegate = IntersectionObserverDelegateImpl* intersection_observer_delegate =
MakeGarbageCollected<IntersectionObserverDelegateImpl>( MakeGarbageCollected<IntersectionObserverDelegateImpl>(
...@@ -245,7 +246,8 @@ IntersectionObserver* IntersectionObserver::Create( ...@@ -245,7 +246,8 @@ IntersectionObserver* IntersectionObserver::Create(
behavior); behavior);
return MakeGarbageCollected<IntersectionObserver>( return MakeGarbageCollected<IntersectionObserver>(
*intersection_observer_delegate, nullptr, margin, thresholds, semantics, *intersection_observer_delegate, nullptr, margin, thresholds, semantics,
delay, track_visibility, always_report_root_bounds, margin_target); delay, track_visibility, always_report_root_bounds, margin_target,
use_overflow_clip_edge);
} }
IntersectionObserver::IntersectionObserver( IntersectionObserver::IntersectionObserver(
...@@ -257,7 +259,8 @@ IntersectionObserver::IntersectionObserver( ...@@ -257,7 +259,8 @@ IntersectionObserver::IntersectionObserver(
DOMHighResTimeStamp delay, DOMHighResTimeStamp delay,
bool track_visibility, bool track_visibility,
bool always_report_root_bounds, bool always_report_root_bounds,
MarginTarget margin_target) MarginTarget margin_target,
bool use_overflow_clip_edge)
: ExecutionContextClient(delegate.GetExecutionContext()), : ExecutionContextClient(delegate.GetExecutionContext()),
delegate_(&delegate), delegate_(&delegate),
root_(root), root_(root),
...@@ -270,7 +273,8 @@ IntersectionObserver::IntersectionObserver( ...@@ -270,7 +273,8 @@ IntersectionObserver::IntersectionObserver(
track_fraction_of_root_(semantics == kFractionOfRoot), track_fraction_of_root_(semantics == kFractionOfRoot),
always_report_root_bounds_(always_report_root_bounds), always_report_root_bounds_(always_report_root_bounds),
needs_delivery_(0), needs_delivery_(0),
can_use_cached_rects_(0) { can_use_cached_rects_(0),
use_overflow_clip_edge_(use_overflow_clip_edge) {
switch (margin.size()) { switch (margin.size()) {
case 0: case 0:
break; break;
...@@ -358,7 +362,9 @@ void IntersectionObserver::observe(Element* target, ...@@ -358,7 +362,9 @@ void IntersectionObserver::observe(Element* target,
observation->ComputeIntersection( observation->ComputeIntersection(
IntersectionObservation::kImplicitRootObserversNeedUpdate | IntersectionObservation::kImplicitRootObserversNeedUpdate |
IntersectionObservation::kExplicitRootObserversNeedUpdate | IntersectionObservation::kExplicitRootObserversNeedUpdate |
IntersectionObservation::kIgnoreDelay); IntersectionObservation::kIgnoreDelay |
(use_overflow_clip_edge_ ? IntersectionObservation::kUseOverflowClipEdge
: 0));
} }
} }
...@@ -456,6 +462,9 @@ bool IntersectionObserver::ComputeIntersections(unsigned flags) { ...@@ -456,6 +462,9 @@ bool IntersectionObserver::ComputeIntersections(unsigned flags) {
if (post_layout_delivery_only != is_post_layout_delivery_observer) if (post_layout_delivery_only != is_post_layout_delivery_observer)
return false; return false;
if (use_overflow_clip_edge_)
flags |= IntersectionObservation::kUseOverflowClipEdge;
IntersectionGeometry::RootGeometry root_geometry( IntersectionGeometry::RootGeometry root_geometry(
IntersectionGeometry::GetRootLayoutObjectForTarget(root(), nullptr, IntersectionGeometry::GetRootLayoutObjectForTarget(root(), nullptr,
false), false),
......
...@@ -98,6 +98,8 @@ class CORE_EXPORT IntersectionObserver final ...@@ -98,6 +98,8 @@ class CORE_EXPORT IntersectionObserver final
// the given |callback|. |thresholds| should be in the range [0,1], and are // the given |callback|. |thresholds| should be in the range [0,1], and are
// interpreted according to the given |semantics|. |delay| specifies the // interpreted according to the given |semantics|. |delay| specifies the
// minimum period between change notifications. // minimum period between change notifications.
// `use_overflow_clip_edge` indicates whether the overflow clip edge
// should be used instead of the bounding box if appropriate.
static IntersectionObserver* Create( static IntersectionObserver* Create(
const Vector<Length>& margin, const Vector<Length>& margin,
const Vector<float>& thresholds, const Vector<float>& thresholds,
...@@ -110,6 +112,7 @@ class CORE_EXPORT IntersectionObserver final ...@@ -110,6 +112,7 @@ class CORE_EXPORT IntersectionObserver final
bool track_visbility = false, bool track_visbility = false,
bool always_report_root_bounds = false, bool always_report_root_bounds = false,
MarginTarget margin_target = kApplyMarginToRoot, MarginTarget margin_target = kApplyMarginToRoot,
bool use_overflow_clip_edge = false,
ExceptionState& = ASSERT_NO_EXCEPTION); ExceptionState& = ASSERT_NO_EXCEPTION);
static void ResumeSuspendedObservers(); static void ResumeSuspendedObservers();
...@@ -122,7 +125,8 @@ class CORE_EXPORT IntersectionObserver final ...@@ -122,7 +125,8 @@ class CORE_EXPORT IntersectionObserver final
DOMHighResTimeStamp delay, DOMHighResTimeStamp delay,
bool track_visibility, bool track_visibility,
bool always_report_root_bounds, bool always_report_root_bounds,
MarginTarget margin_target); MarginTarget margin_target,
bool use_overflow_clip_edge);
// API methods. // API methods.
void observe(Element*, ExceptionState& = ASSERT_NO_EXCEPTION); void observe(Element*, ExceptionState& = ASSERT_NO_EXCEPTION);
...@@ -175,6 +179,8 @@ class CORE_EXPORT IntersectionObserver final ...@@ -175,6 +179,8 @@ class CORE_EXPORT IntersectionObserver final
bool CanUseCachedRects() const { return can_use_cached_rects_; } bool CanUseCachedRects() const { return can_use_cached_rects_; }
void InvalidateCachedRects() { can_use_cached_rects_ = 0; } void InvalidateCachedRects() { can_use_cached_rects_ = 0; }
bool UseOverflowClipEdge() const { return use_overflow_clip_edge_ == 1; }
// ScriptWrappable override: // ScriptWrappable override:
bool HasPendingActivity() const override; bool HasPendingActivity() const override;
...@@ -203,6 +209,7 @@ class CORE_EXPORT IntersectionObserver final ...@@ -203,6 +209,7 @@ class CORE_EXPORT IntersectionObserver final
unsigned always_report_root_bounds_ : 1; unsigned always_report_root_bounds_ : 1;
unsigned needs_delivery_ : 1; unsigned needs_delivery_ : 1;
unsigned can_use_cached_rects_ : 1; unsigned can_use_cached_rects_ : 1;
unsigned use_overflow_clip_edge_ : 1;
}; };
} // namespace blink } // namespace blink
......
...@@ -189,7 +189,8 @@ TEST_F(IntersectionObserverTest, ReportsFractionOfTargetOrRoot) { ...@@ -189,7 +189,8 @@ TEST_F(IntersectionObserverTest, ReportsFractionOfTargetOrRoot) {
*target_observer_delegate, nullptr, Vector<Length>(), *target_observer_delegate, nullptr, Vector<Length>(),
Vector<float>{kExpectedFractionOfTarget / 2}, Vector<float>{kExpectedFractionOfTarget / 2},
IntersectionObserver::kFractionOfTarget, 0, false, false, IntersectionObserver::kFractionOfTarget, 0, false, false,
IntersectionObserver::kApplyMarginToRoot); IntersectionObserver::kApplyMarginToRoot,
/* use_overflow_clip_edge */ false);
DummyExceptionStateForTesting exception_state; DummyExceptionStateForTesting exception_state;
target_observer->observe(target, exception_state); target_observer->observe(target, exception_state);
ASSERT_FALSE(exception_state.HadException()); ASSERT_FALSE(exception_state.HadException());
...@@ -201,7 +202,8 @@ TEST_F(IntersectionObserverTest, ReportsFractionOfTargetOrRoot) { ...@@ -201,7 +202,8 @@ TEST_F(IntersectionObserverTest, ReportsFractionOfTargetOrRoot) {
*root_observer_delegate, nullptr, Vector<Length>(), *root_observer_delegate, nullptr, Vector<Length>(),
Vector<float>{kExpectedFractionOfRoot / 2}, Vector<float>{kExpectedFractionOfRoot / 2},
IntersectionObserver::kFractionOfRoot, 0, false, false, IntersectionObserver::kFractionOfRoot, 0, false, false,
IntersectionObserver::kApplyMarginToRoot); IntersectionObserver::kApplyMarginToRoot,
/* use_overflow_clip_edge */ false);
root_observer->observe(target, exception_state); root_observer->observe(target, exception_state);
ASSERT_FALSE(exception_state.HadException()); ASSERT_FALSE(exception_state.HadException());
...@@ -257,7 +259,8 @@ TEST_F(IntersectionObserverTest, TargetRectIsEmptyAfterMapping) { ...@@ -257,7 +259,8 @@ TEST_F(IntersectionObserverTest, TargetRectIsEmptyAfterMapping) {
*target_observer_delegate, nullptr, Vector<Length>(), *target_observer_delegate, nullptr, Vector<Length>(),
Vector<float>{std::numeric_limits<float>::min()}, Vector<float>{std::numeric_limits<float>::min()},
IntersectionObserver::kFractionOfTarget, 0, false, false, IntersectionObserver::kFractionOfTarget, 0, false, false,
IntersectionObserver::kApplyMarginToRoot); IntersectionObserver::kApplyMarginToRoot,
/* use_overflow_clip_edge */ false);
DummyExceptionStateForTesting exception_state; DummyExceptionStateForTesting exception_state;
target_observer->observe(target, exception_state); target_observer->observe(target, exception_state);
ASSERT_FALSE(exception_state.HadException()); ASSERT_FALSE(exception_state.HadException());
...@@ -979,7 +982,8 @@ TEST_F(IntersectionObserverTest, ApplyMarginToTarget) { ...@@ -979,7 +982,8 @@ TEST_F(IntersectionObserverTest, ApplyMarginToTarget) {
*root_margin_delegate, nullptr, Vector<Length>{Length::Fixed(10)}, *root_margin_delegate, nullptr, Vector<Length>{Length::Fixed(10)},
Vector<float>{std::numeric_limits<float>::min()}, Vector<float>{std::numeric_limits<float>::min()},
IntersectionObserver::kFractionOfTarget, 0, false, false, IntersectionObserver::kFractionOfTarget, 0, false, false,
IntersectionObserver::kApplyMarginToRoot); IntersectionObserver::kApplyMarginToRoot,
/* use_overflow_clip_edge */ false);
DummyExceptionStateForTesting exception_state; DummyExceptionStateForTesting exception_state;
root_margin_observer->observe(target, exception_state); root_margin_observer->observe(target, exception_state);
...@@ -993,7 +997,8 @@ TEST_F(IntersectionObserverTest, ApplyMarginToTarget) { ...@@ -993,7 +997,8 @@ TEST_F(IntersectionObserverTest, ApplyMarginToTarget) {
*target_margin_delegate, nullptr, Vector<Length>{Length::Fixed(10)}, *target_margin_delegate, nullptr, Vector<Length>{Length::Fixed(10)},
Vector<float>{std::numeric_limits<float>::min()}, Vector<float>{std::numeric_limits<float>::min()},
IntersectionObserver::kFractionOfTarget, 0, false, false, IntersectionObserver::kFractionOfTarget, 0, false, false,
IntersectionObserver::kApplyMarginToTarget); IntersectionObserver::kApplyMarginToTarget,
/* use_overflow_clip_edge */ false);
target_margin_observer->observe(target, exception_state); target_margin_observer->observe(target, exception_state);
ASSERT_FALSE(exception_state.HadException()); ASSERT_FALSE(exception_state.HadException());
...@@ -1046,7 +1051,8 @@ TEST_F(IntersectionObserverTest, TargetMarginPercentResolvesAgainstRoot) { ...@@ -1046,7 +1051,8 @@ TEST_F(IntersectionObserverTest, TargetMarginPercentResolvesAgainstRoot) {
*target_margin_delegate, nullptr, Vector<Length>{Length::Percent(10)}, *target_margin_delegate, nullptr, Vector<Length>{Length::Percent(10)},
Vector<float>{std::numeric_limits<float>::min()}, Vector<float>{std::numeric_limits<float>::min()},
IntersectionObserver::kFractionOfTarget, 0, false, false, IntersectionObserver::kFractionOfTarget, 0, false, false,
IntersectionObserver::kApplyMarginToTarget); IntersectionObserver::kApplyMarginToTarget,
/* use_overflow_clip_edge */ false);
DummyExceptionStateForTesting exception_state; DummyExceptionStateForTesting exception_state;
target_margin_observer->observe(target, exception_state); target_margin_observer->observe(target, exception_state);
......
<!doctype html>
<html class="reftest">
<meta charset="utf-8">
<title>Overflow-clip-margin: is shown with content-visibility: auto</title>
<link rel="help" href="https://www.w3.org/TR/css-overflow-3/#propdef-overflow-clip-margin">
<link rel="author" title="Scott Violet" href="mailto:sky@chromium.org">
<style>
body {
height: 15000px;
}
.big {
width: 10px;
height: 20000px;
position: relative;
top: -10000px;
background: green;
}
</style>
<div class=big></div>
<script>
onload = function() {
document.documentElement.scrollTop = window.innerHeight * 3;
}
</script>
<!doctype html>
<html class="reftest">
<meta charset="utf-8">
<title>Overflow-clip-margin: is shown with content-visibility: auto</title>
<link rel="help" href="https://www.w3.org/TR/css-overflow-3/#propdef-overflow-clip-margin">
<link rel="author" title="Scott Violet" href="mailto:sky@chromium.org">
<link rel="match" href="overflow-clip-margin-007-ref.html">
<style>
body {
height: 15000px;
}
.auto {
content-visibility: auto;
width: 100px;
height: 100px;
overflow-clip-margin: 10000px;
background: lightblue;
}
.big {
width: 10px;
height: 20000px;
position: relative;
top: -10000px;
background: green;
}
</style>
<div class=auto>
<div class=big></div>
</div>
<script>
onload = function() {
document.documentElement.scrollTop = window.innerHeight * 3;
}
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment