Commit 26e25729 authored by Philip Rogers's avatar Philip Rogers Committed by Commit Bot

[RLS] Avoid full tree rebuilds on hit testing clip changes

This is a revert of [1] which caused performance regressions. This patch
reimplements a solution that avoids full tree rebuilds when only hit
testing clip values change in the clip tree.

Typically, when property nodes are updated, we force a full tree
rebuild if nodes are created or destroyed, but not if node values are
simply updated. Clip nodes are an exception to this because changing
their values ends up causing a full tree rebuild. The hit testing clip
rect (aka clip_rect_excluding_overlay_scrollbars) is different though,
and does not need to force a full tree rebuild when it changes. This
patch special-cases the hit testing clip rects to avoid a full tree
rebuild. When overlay scrollbars hide or appear, the hit testing
clip values change but a full tree rebuild is not needed.

[1] https://crrev.com/86d1cc1b33260b95367afdedb6195c842dc8e5c0

Bug: 823999, 830746, 830771
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2
Change-Id: I7ab426aaead0c87a1aedf0980648505d82d40215
Reviewed-on: https://chromium-review.googlesource.com/1026941Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Commit-Queue: Philip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#553652}
parent 7af3897b
......@@ -144,6 +144,9 @@ class CORE_EXPORT ObjectPaintProperties {
const ClipPaintPropertyNode* InnerBorderRadiusClip() const {
return inner_border_radius_clip_.get();
}
const ClipPaintPropertyNode* OverflowClip() const {
return overflow_clip_.get();
}
const ClipPaintPropertyNode* OverflowOrInnerBorderRadiusClip() const {
return overflow_clip_ ? overflow_clip_.get()
: inner_border_radius_clip_.get();
......
......@@ -540,25 +540,6 @@ void PaintLayerClipper::CalculateBackgroundClipRectWithGeometryMapper(
// TODO(chrishtr): generalize to multiple fragments.
output.MoveBy(
-context.root_layer->GetLayoutObject().FirstFragment().PaintOffset());
// The root LayoutView clip node does not store a rect excluding scrollbars
// for hit testing (see FragmentPaintPropertyTreeBuilder::UpdateOverflowClip).
// To calculate the correct clip at the root, we need to apply the LayoutView
// overflow clip excluding scrollbars here.
auto& root = context.root_layer->GetLayoutObject();
bool is_root_layout_view =
root.IsLayoutView() && !root.GetFrame()->Tree().Parent();
bool root_should_clip = HasOverflowClip(*context.root_layer) &&
!is_clipping_root &&
context.ShouldRespectRootLayerClip();
if (context.overlay_scrollbar_clip_behavior ==
kExcludeOverlayScrollbarSizeForHitTesting &&
is_root_layout_view && root_should_clip) {
ClipRect root_overflow_clip = ToLayoutView(root).OverflowClipRect(
LayoutPoint(), kExcludeOverlayScrollbarSizeForHitTesting);
output.Intersect(root_overflow_clip);
}
output.Move(context.sub_pixel_accumulation);
}
......
......@@ -359,9 +359,12 @@ class FragmentPaintPropertyTreeBuilder {
property_added_or_removed_ |= result.NewNodeCreated();
property_changed_ |= !result.Unchanged();
}
void OnUpdateClip(const ObjectPaintProperties::UpdateResult& result) {
// Like |OnUpdate| but sets |clip_changed| if the clip values change.
void OnUpdateClip(const ObjectPaintProperties::UpdateResult& result,
bool only_updated_hit_test_values = false) {
OnUpdate(result);
full_context_.clip_changed |= !result.Unchanged();
full_context_.clip_changed |=
!(result.Unchanged() || only_updated_hit_test_values);
}
void OnClear(bool cleared) {
property_added_or_removed_ |= cleared;
......@@ -1242,26 +1245,17 @@ void FragmentPaintPropertyTreeBuilder::UpdateOverflowClip() {
if (NeedsOverflowClip(object_) && !CanOmitOverflowClip(object_)) {
ClipPaintPropertyNode::State state;
state.local_transform_space = context_.current.transform;
if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled() &&
object_.IsSVGForeignObject()) {
state.clip_rect = ToClipRect(ToLayoutBox(object_).FrameRect());
} else if (object_.IsBox()) {
state.clip_rect = ToClipRect(ToLayoutBox(object_).OverflowClipRect(
context_.current.paint_offset));
bool is_root_layout_view =
object_.IsLayoutView() && !object_.GetFrame()->Tree().Parent();
// Overlay scrollbar hiding frequently changes clip rects which requires
// invalidating all clip nodes. To avoid this expensive operation at the
// root, we do not set |clip_rect_excluding_overlay_scrollbars| and
// instead special-case hit test clipping at the root (see:
// PaintLayerClipper::CalculateBackgroundClipRectWithGeometryMapper).
if (!is_root_layout_view) {
state.clip_rect_excluding_overlay_scrollbars =
ToClipRect(ToLayoutBox(object_).OverflowClipRect(
context_.current.paint_offset,
kExcludeOverlayScrollbarSizeForHitTesting));
}
state.clip_rect_excluding_overlay_scrollbars =
ToClipRect(ToLayoutBox(object_).OverflowClipRect(
context_.current.paint_offset,
kExcludeOverlayScrollbarSizeForHitTesting));
} else {
DCHECK(object_.IsSVGViewportContainer());
const auto& viewport_container = ToLayoutSVGViewportContainer(object_);
......@@ -1278,8 +1272,13 @@ void FragmentPaintPropertyTreeBuilder::UpdateOverflowClip() {
should_create_overflow_clip = false;
}
if (should_create_overflow_clip) {
const ClipPaintPropertyNode* existing = properties_->OverflowClip();
bool equal_ignoring_hit_test_rects =
!!existing &&
existing->EqualIgnoringHitTestRects(context_.current.clip, state);
OnUpdateClip(properties_->UpdateOverflowClip(context_.current.clip,
std::move(state)));
std::move(state)),
equal_ignoring_hit_test_rects);
} else {
OnClearClip(properties_->ClearOverflowClip());
}
......
......@@ -5477,4 +5477,28 @@ TEST_P(PaintPropertyTreeBuilderTest, OmitOverflowClip) {
EXPECT_TRUE(OverflowClip(*PaintPropertiesForElement("button")));
}
TEST_P(PaintPropertyTreeBuilderTest, ClipHitTestChangeDoesNotCauseFullRepaint) {
SetBodyInnerHTML(R"HTML(
<html>
<body>
<style>
.noscrollbars::-webkit-scrollbar { display: none; }
</style>
<div id="child" style="width: 10px; height: 10px; position: absolute;">
</div>
<div id="forcescroll" style="height: 1000px;"></div>
</body>
</html>
)HTML");
CHECK(GetDocument().GetPage()->GetScrollbarTheme().UsesOverlayScrollbars());
GetDocument().View()->UpdateAllLifecyclePhases();
auto* child_layer = ToLayoutBox(GetLayoutObjectByElementId("child"))->Layer();
EXPECT_FALSE(child_layer->NeedsRepaint());
GetDocument().body()->setAttribute(HTMLNames::classAttr, "noscrollbars");
GetDocument().View()->UpdateAllLifecyclePhasesExceptPaint();
EXPECT_FALSE(child_layer->NeedsRepaint());
}
} // namespace blink
......@@ -35,14 +35,21 @@ class PLATFORM_EXPORT ClipPaintPropertyNode
scoped_refptr<const RefCountedPath> clip_path;
CompositingReasons direct_compositing_reasons = CompositingReason::kNone;
bool operator==(const State& o) const {
// Returns true if the states are equal, ignoring the clip rect excluding
// overlay scrollbars which is only used for hit testing.
bool EqualIgnoringHitTestRects(const State& o) const {
return local_transform_space == o.local_transform_space &&
clip_rect == o.clip_rect &&
clip_rect_excluding_overlay_scrollbars ==
o.clip_rect_excluding_overlay_scrollbars &&
clip_path == o.clip_path &&
direct_compositing_reasons == o.direct_compositing_reasons;
}
bool operator==(const State& o) const {
if (!EqualIgnoringHitTestRects(o))
return false;
return clip_rect_excluding_overlay_scrollbars ==
o.clip_rect_excluding_overlay_scrollbars;
}
};
// This node is really a sentinel, and does not represent a real clip space.
......@@ -66,6 +73,12 @@ class PLATFORM_EXPORT ClipPaintPropertyNode
return true;
}
bool EqualIgnoringHitTestRects(
scoped_refptr<const ClipPaintPropertyNode> parent,
const State& state) const {
return parent == Parent() && state_.EqualIgnoringHitTestRects(state);
}
const TransformPaintPropertyNode* LocalTransformSpace() const {
return state_.local_transform_space.get();
}
......
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