Commit 5ce74425 authored by Steve Kobes's avatar Steve Kobes Committed by Commit Bot

Track layout jank.

This is a rough prototype of the proposal described at go/layoutjank.

Bug: 581518
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2
Change-Id: Id212437c0f187c54ad4725cf0b71ddee28288dac
Reviewed-on: https://chromium-review.googlesource.com/1046155
Commit-Queue: Steve Kobes <skobes@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarTimothy Dresser <tdresser@chromium.org>
Cr-Commit-Position: refs/heads/master@{#560283}
parent 36571039
......@@ -1895,6 +1895,7 @@ jumbo_source_set("unit_tests") {
"layout/api/selection_state_test.cc",
"layout/collapsed_border_value_test.cc",
"layout/custom/layout_worklet_test.cc",
"layout/jank_tracker_test.cc",
"layout/layout_block_test.cc",
"layout/layout_box_model_object_test.cc",
"layout/layout_box_test.cc",
......
......@@ -240,7 +240,8 @@ LocalFrameView::LocalFrameView(LocalFrame& frame, IntRect frame_rect)
needs_focus_on_fragment_(false),
main_thread_scrolling_reasons_(0),
paint_frame_count_(0),
unique_id_(NewUniqueObjectId()) {
unique_id_(NewUniqueObjectId()),
jank_tracker_(this) {
Init();
}
......
......@@ -39,6 +39,7 @@
#include "third_party/blink/renderer/core/frame/frame_view_auto_size_info.h"
#include "third_party/blink/renderer/core/frame/layout_subtree_root_list.h"
#include "third_party/blink/renderer/core/frame/root_frame_viewport.h"
#include "third_party/blink/renderer/core/layout/jank_tracker.h"
#include "third_party/blink/renderer/core/layout/map_coordinates_flags.h"
#include "third_party/blink/renderer/core/layout/scroll_anchor.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
......@@ -920,6 +921,7 @@ class CORE_EXPORT LocalFrameView final
ScrollingCoordinatorContext* GetScrollingContext() const;
void ScrollAndFocusFragmentAnchor();
JankTracker& GetJankTracker() { return jank_tracker_; }
protected:
// Scroll the content via the compositor.
......@@ -1289,6 +1291,7 @@ class CORE_EXPORT LocalFrameView final
size_t paint_frame_count_;
UniqueObjectId unique_id_;
JankTracker jank_tracker_;
FRIEND_TEST_ALL_PREFIXES(WebViewTest, DeviceEmulationResetScrollbars);
};
......
......@@ -92,6 +92,8 @@ blink_core_sources("layout") {
"hit_testing_transform_state.h",
"intersection_geometry.cc",
"intersection_geometry.h",
"jank_tracker.cc",
"jank_tracker.h",
"layout_analyzer.cc",
"layout_analyzer.h",
"layout_block.cc",
......
// Copyright 2018 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/layout/jank_tracker.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/location.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
namespace blink {
static const float kTimerDelay = 3.0;
JankTracker::JankTracker(LocalFrameView* frame_view)
: frame_view_(frame_view),
score_(0.0),
timer_(frame_view->GetFrame().GetTaskRunner(TaskType::kInternalDefault),
this,
&JankTracker::TimerFired),
has_fired_(false) {}
void JankTracker::NotifyObjectPrePaint(const LayoutObject& object,
const LayoutRect& old_visual_rect,
const PaintLayer& painting_layer) {
if (!IsActive())
return;
LayoutRect new_visual_rect = object.FirstFragment().VisualRect();
if (old_visual_rect.IsEmpty() || new_visual_rect.IsEmpty())
return;
if (old_visual_rect.Location() == new_visual_rect.Location())
return;
const auto* local_transform = painting_layer.GetLayoutObject()
.FirstFragment()
.LocalBorderBoxProperties()
.Transform();
const auto* ancestor_transform = painting_layer.GetLayoutObject()
.View()
->FirstFragment()
.LocalBorderBoxProperties()
.Transform();
FloatRect old_visual_rect_abs = FloatRect(old_visual_rect);
GeometryMapper::SourceToDestinationRect(local_transform, ancestor_transform,
old_visual_rect_abs);
FloatRect new_visual_rect_abs = FloatRect(new_visual_rect);
GeometryMapper::SourceToDestinationRect(local_transform, ancestor_transform,
new_visual_rect_abs);
// TOOD(crbug.com/842282): Consider tracking a separate jank score for each
// transform space to avoid these local-to-absolute conversions, once we have
// a better idea of how to aggregate multiple scores for a page.
// See review thread of http://crrev.com/c/1046155 for more details.
IntRect viewport = frame_view_->GetScrollableArea()->VisibleContentRect();
if (!old_visual_rect_abs.Intersects(viewport) &&
!new_visual_rect_abs.Intersects(viewport))
return;
DVLOG(2) << object.DebugName() << " moved from "
<< old_visual_rect_abs.ToString() << " to "
<< new_visual_rect_abs.ToString();
IntRect visible_old_visual_rect = RoundedIntRect(old_visual_rect_abs);
visible_old_visual_rect.Intersect(viewport);
IntRect visible_new_visual_rect = RoundedIntRect(new_visual_rect_abs);
visible_new_visual_rect.Intersect(viewport);
region_.Unite(Region(visible_old_visual_rect));
region_.Unite(Region(visible_new_visual_rect));
}
void JankTracker::NotifyPrePaintFinished() {
if (!IsActive())
return;
if (region_.IsEmpty()) {
if (!timer_.IsActive())
timer_.StartOneShot(kTimerDelay, FROM_HERE);
return;
}
IntRect viewport = frame_view_->GetScrollableArea()->VisibleContentRect();
double viewport_area = double(viewport.Width()) * double(viewport.Height());
double jank_fraction = region_.Area() / viewport_area;
score_ += jank_fraction;
DVLOG(1) << "viewport " << (jank_fraction * 100)
<< "% janked, raising score to " << score_;
TRACE_EVENT_INSTANT1("blink", "FrameLayoutJank", TRACE_EVENT_SCOPE_THREAD,
"viewportFraction", jank_fraction);
region_ = Region();
// This cancels any previously scheduled task from the same timer.
timer_.StartOneShot(kTimerDelay, FROM_HERE);
}
bool JankTracker::IsActive() {
// This eliminates noise from the private Page object created by
// SVGImage::DataChanged.
if (frame_view_->GetFrame().GetDocument()->IsSVGDocument())
return false;
if (has_fired_)
return false;
return true;
}
void JankTracker::TimerFired(TimerBase* timer) {
has_fired_ = true;
// TODO(skobes): Aggregate jank scores from iframes.
if (!frame_view_->GetFrame().IsMainFrame())
return;
DVLOG(1) << "final jank score for "
<< frame_view_->GetFrame().DomWindow()->location()->toString()
<< " is " << score_;
TRACE_EVENT_INSTANT1("blink", "TotalLayoutJank", TRACE_EVENT_SCOPE_THREAD,
"score", score_);
}
} // namespace blink
// Copyright 2018 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_JANK_TRACKER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_JANK_TRACKER_H_
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/geometry/region.h"
#include "third_party/blink/renderer/platform/timer.h"
namespace blink {
class LayoutObject;
class LayoutRect;
class LocalFrameView;
class PaintLayer;
// Tracks "jank" from layout objects changing their visual location between
// animation frames.
class CORE_EXPORT JankTracker {
public:
JankTracker(LocalFrameView*);
~JankTracker() {}
void NotifyObjectPrePaint(const LayoutObject& object,
const LayoutRect& old_visual_rect,
const PaintLayer& painting_layer);
void NotifyPrePaintFinished();
bool IsActive();
double Score() const { return score_; }
private:
void TimerFired(TimerBase*);
// This owns us.
UntracedMember<LocalFrameView> frame_view_;
// The global jank score.
double score_;
// The per-frame jank region.
Region region_;
// Timer that fires the first time we've had no layout jank for a few seconds.
TaskRunnerTimer<JankTracker> timer_;
bool has_fired_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_JANK_TRACKER_H_
// Copyright 2018 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/layout/jank_tracker.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
namespace blink {
class JankTrackerTest : public RenderingTest {
protected:
LocalFrameView& GetFrameView() { return *GetFrame().View(); }
JankTracker& GetJankTracker() { return GetFrameView().GetJankTracker(); }
};
TEST_F(JankTrackerTest, SimpleBlockMovement) {
SetBodyInnerHTML(R"HTML(
<style>
#j { position: relative; width: 300px; height: 100px; }
</style>
<div id='j'></div>
)HTML");
EXPECT_EQ(0.0, GetJankTracker().Score());
GetDocument().getElementById("j")->setAttribute(HTMLNames::styleAttr,
AtomicString("top: 60px"));
GetFrameView().UpdateAllLifecyclePhases();
// 300 * (100 + 60) / (default viewport size 800 * 600)
EXPECT_FLOAT_EQ(0.1, GetJankTracker().Score());
}
TEST_F(JankTrackerTest, SVGIgnored) {
const char data[] = "<svg></svg>";
GetFrame().ForceSynchronousDocumentInstall(
"image/svg+xml", SharedBuffer::Create(data, strlen(data)));
EXPECT_FALSE(GetJankTracker().IsActive());
}
TEST_F(JankTrackerTest, Transform) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0; }
#c { transform: translateX(-300px) translateY(-40px); }
#j { position: relative; width: 600px; height: 140px; }
</style>
<div id='c'>
<div id='j'></div>
</div>
)HTML");
GetDocument().getElementById("j")->setAttribute(HTMLNames::styleAttr,
AtomicString("top: 60px"));
GetFrameView().UpdateAllLifecyclePhases();
// (600 - 300) * (140 - 40 + 60) / (default viewport size 800 * 600)
EXPECT_FLOAT_EQ(0.1, GetJankTracker().Score());
}
} // namespace blink
......@@ -117,6 +117,7 @@ void PrePaintTreeWalk::Walk(LocalFrameView& frame_view) {
}
frame_view.ClearNeedsPaintPropertyUpdate();
frame_view.GetJankTracker().NotifyPrePaintFinished();
context_storage_.pop_back();
}
......@@ -206,6 +207,9 @@ bool PrePaintTreeWalk::NeedsTreeBuilderContextUpdate(
void PrePaintTreeWalk::WalkInternal(const LayoutObject& object,
PrePaintTreeWalkContext& context) {
PaintInvalidatorContext& paint_invalidator_context =
context.paint_invalidator_context;
// This must happen before updatePropertiesForSelf, because the latter reads
// some of the state computed here.
UpdateAuxiliaryObjectProperties(object, context);
......@@ -217,14 +221,14 @@ void PrePaintTreeWalk::WalkInternal(const LayoutObject& object,
property_changed = property_tree_builder->UpdateForSelf();
if (context.tree_builder_context->clip_changed) {
context.paint_invalidator_context.subtree_flags |=
paint_invalidator_context.subtree_flags |=
PaintInvalidatorContext::kSubtreeVisualRectUpdate;
}
}
paint_invalidator_.InvalidatePaint(
object, base::OptionalOrNullptr(context.tree_builder_context),
context.paint_invalidator_context);
paint_invalidator_context);
if (context.tree_builder_context) {
property_changed |= property_tree_builder->UpdateForChildren();
......@@ -234,8 +238,7 @@ void PrePaintTreeWalk::WalkInternal(const LayoutObject& object,
RuntimeEnabledFeatures::SlimmingPaintV175Enabled() &&
!RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
const auto* paint_invalidation_layer =
context.paint_invalidator_context.paint_invalidation_container
->Layer();
paint_invalidator_context.paint_invalidation_container->Layer();
if (!paint_invalidation_layer->NeedsRepaint()) {
auto* mapping = paint_invalidation_layer->GetCompositedLayerMapping();
if (!mapping)
......@@ -247,6 +250,10 @@ void PrePaintTreeWalk::WalkInternal(const LayoutObject& object,
}
CompositingLayerPropertyUpdater::Update(object);
object.GetFrameView()->GetJankTracker().NotifyObjectPrePaint(
object, paint_invalidator_context.old_visual_rect,
*paint_invalidator_context.painting_layer);
}
void PrePaintTreeWalk::Walk(const LayoutObject& object) {
......
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