blob: fab6085c5d169a7566d779b25f1f58ff64802f89 [file] [log] [blame]
// 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/page/chrome_client.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;
static const float kRegionGranularitySteps = 60.0;
static FloatPoint LogicalStart(const FloatRect& rect,
const LayoutObject& object) {
const ComputedStyle* style = object.Style();
DCHECK(style);
auto logical =
PhysicalToLogical<float>(style->GetWritingMode(), style->Direction(),
rect.Y(), rect.MaxX(), rect.MaxY(), rect.X());
return FloatPoint(logical.InlineStart(), logical.BlockStart());
}
static float GetMoveDistance(const FloatRect& old_rect,
const FloatRect& new_rect,
const LayoutObject& object) {
FloatSize location_delta =
LogicalStart(new_rect, object) - LogicalStart(old_rect, object);
return std::max(fabs(location_delta.Width()), fabs(location_delta.Height()));
}
static float RegionGranularityScale(const IntRect& viewport) {
return kRegionGranularitySteps /
std::min(viewport.Height(), viewport.Width());
}
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),
max_distance_(0.0) {}
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 (LogicalStart(FloatRect(old_visual_rect), object) ==
LogicalStart(FloatRect(new_visual_rect), object))
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();
max_distance_ = std::max(
max_distance_,
GetMoveDistance(old_visual_rect_abs, new_visual_rect_abs, object));
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);
float scale = RegionGranularityScale(viewport);
visible_old_visual_rect.Scale(scale);
visible_new_visual_rect.Scale(scale);
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();
viewport.Scale(RegionGranularityScale(viewport));
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("loading", "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().GetChromeClient().IsSVGImageChromeClient())
return false;
if (has_fired_)
return false;
return true;
}
std::unique_ptr<TracedValue> JankTracker::TraceData() const {
std::unique_ptr<TracedValue> value = TracedValue::Create();
value->SetDouble("score", score_);
value->SetDouble("maxDistance", max_distance_);
return value;
}
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_ << " with max move distance of "
<< max_distance_;
TRACE_EVENT_INSTANT2("loading", "TotalLayoutJank", TRACE_EVENT_SCOPE_THREAD,
"data", TraceData(), "frame",
ToTraceValue(&frame_view_->GetFrame()));
}
} // namespace blink