blob: c3d6c106e191a92945382211265534c44d5bac52 [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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_SHIFT_TRACKER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_SHIFT_TRACKER_H_
#include "third_party/blink/public/platform/web_vector.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
#include "third_party/blink/renderer/core/layout/layout_shift_region.h"
#include "third_party/blink/renderer/core/scroll/scroll_types.h"
#include "third_party/blink/renderer/core/timing/layout_shift.h"
#include "third_party/blink/renderer/platform/geometry/region.h"
#include "third_party/blink/renderer/platform/graphics/dom_node_id.h"
#include "third_party/blink/renderer/platform/timer.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink {
class LayoutBox;
class LayoutObject;
class LayoutText;
class LocalFrameView;
class PropertyTreeStateOrAlias;
class TracedValue;
class WebInputEvent;
// Tracks "layout shifts" from layout objects changing their visual location
// between animation frames. See https://github.com/WICG/layout-instability.
class CORE_EXPORT LayoutShiftTracker final
: public GarbageCollected<LayoutShiftTracker> {
public:
explicit LayoutShiftTracker(LocalFrameView*);
~LayoutShiftTracker() = default;
bool NeedsToTrack(const LayoutObject&) const;
// |old_rect| and |new_rect| are border box rects, united with layout overflow
// rects if the box has layout overflow and doesn't clip overflow, in the
// local transform space (property_tree_state.Transform()). |old_paint_offset|
// and |new_paint_offset| are the offsets of the border box rect in the local
// transform space, which are the same as |old_rect.offset| and
// |new_rect.offset| respectively if the rects are border box rects.
// As we don't save the old property tree state, the caller should adjust
// |old_rect| and |old_paint_offset| so that we can calculate the correct old
// visual representation and old starting point in the initial containing
// block and the viewport with the new property tree state in most cases.
// The adjustment should include the deltas of 2d translations and scrolls,
// and LayoutShiftTracker can determine stability by including (by default)
// or excluding |translation_delta| and/or |scroll_delta|.
//
// See renderer/core/layout/layout-shift-tracker-old-paint-offset.md for
// more details about |old_paint_offset|.
void NotifyBoxPrePaint(const LayoutBox& box,
const PropertyTreeStateOrAlias& property_tree_state,
const PhysicalRect& old_rect,
const PhysicalRect& new_rect,
const PhysicalOffset& old_paint_offset,
const FloatSize& translation_delta,
const FloatSize& scroll_delta,
const PhysicalOffset& new_paint_offset);
void NotifyTextPrePaint(const LayoutText& text,
const PropertyTreeStateOrAlias& property_tree_state,
const LogicalOffset& old_starting_point,
const LogicalOffset& new_starting_point,
const PhysicalOffset& old_paint_offset,
const FloatSize& translation_delta,
const FloatSize& scroll_delta,
const PhysicalOffset& new_paint_offset,
const LayoutUnit logical_height);
void NotifyPrePaintFinished();
void NotifyInput(const WebInputEvent&);
void NotifyScroll(mojom::blink::ScrollType, ScrollOffset delta);
void NotifyViewportSizeChanged();
void NotifyFindInPageInput();
void NotifyChangeEvent();
bool IsActive() const { return is_active_; }
double Score() const { return score_; }
double WeightedScore() const { return weighted_score_; }
float OverallMaxDistance() const { return overall_max_distance_; }
bool ObservedInputOrScroll() const { return observed_input_or_scroll_; }
void Dispose() { timer_.Stop(); }
base::TimeTicks MostRecentInputTimestamp() {
return most_recent_input_timestamp_;
}
void Trace(Visitor* visitor) const;
// Saves and restores geometry on layout boxes when a layout tree is rebuilt
// by Node::ReattachLayoutTree.
class ReattachHookScope {
STACK_ALLOCATED();
public:
explicit ReattachHookScope(const Node&);
~ReattachHookScope();
ReattachHookScope(const ReattachHookScope&) = delete;
ReattachHookScope& operator=(const ReattachHookScope&) = delete;
static void NotifyDetach(const Node&);
static void NotifyAttach(const Node&);
private:
ReattachHookScope* outer_;
static ReattachHookScope* top_;
struct Geometry {
PhysicalOffset paint_offset;
LayoutSize size;
PhysicalRect visual_overflow_rect;
bool has_paint_offset_translation;
};
HeapHashMap<Member<const Node>, Geometry> geometries_before_detach_;
};
class CORE_EXPORT ContainingBlockScope {
USING_FAST_MALLOC(ContainingBlockScope);
public:
// |old_size| and |new_size| are the border box sizes.
// |old_rect| and |new_rect| have the same definition as in
// NotifyBoxPrePaint().
ContainingBlockScope(const PhysicalSize& old_size,
const PhysicalSize& new_size,
const PhysicalRect& old_rect,
const PhysicalRect& new_rect)
: outer_(top_),
old_size_(old_size),
new_size_(new_size),
old_rect_(old_rect),
new_rect_(new_rect) {
top_ = this;
}
~ContainingBlockScope() {
DCHECK_EQ(top_, this);
top_ = outer_;
}
ContainingBlockScope(const ContainingBlockScope&) = delete;
ContainingBlockScope& operator=(const ContainingBlockScope&) = delete;
private:
friend class LayoutShiftTracker;
ContainingBlockScope* outer_;
static ContainingBlockScope* top_;
PhysicalSize old_size_;
PhysicalSize new_size_;
PhysicalRect old_rect_;
PhysicalRect new_rect_;
};
private:
void ObjectShifted(const LayoutObject&,
const PropertyTreeStateOrAlias&,
const PhysicalRect& old_rect,
const PhysicalRect& new_rect,
const FloatPoint& old_starting_point,
const FloatSize& translation_delta,
const FloatSize& scroll_offset_delta,
const FloatPoint& new_starting_point);
void ReportShift(double score_delta, double weighted_score_delta);
void TimerFired(TimerBase*) {}
std::unique_ptr<TracedValue> PerFrameTraceData(double score_delta,
double weighted_score_delta,
bool input_detected) const;
void AttributionsToTracedValue(TracedValue&) const;
double SubframeWeightingFactor() const;
void SetLayoutShiftRects(const Vector<IntRect>& int_rects);
void UpdateInputTimestamp(base::TimeTicks timestamp);
LayoutShift::AttributionList CreateAttributionList() const;
void SubmitPerformanceEntry(double score_delta, bool input_detected) const;
void NotifyPrePaintFinishedInternal();
double LastInputTimestamp() const;
void UpdateTimerAndInputTimestamp();
Member<LocalFrameView> frame_view_;
bool is_active_;
bool enable_m90_improvements_;
// The document cumulative layout shift (DCLS) score for this LocalFrame,
// unweighted, with move distance applied.
double score_;
// The cumulative layout shift score for this LocalFrame, with each increase
// weighted by the extent to which the LocalFrame visibly occupied the main
// frame at the time the shift occurred, e.g. x0.5 if the subframe occupied
// half of the main frame's reported size; see SubframeWeightingFactor().
double weighted_score_;
// Stores information related to buffering layout shifts after pointerdown.
// We accumulate score deltas in this object until we know whether the
// pointerdown should be treated as a tap (triggering layout shift exclusion)
// or a scroll (not triggering layout shift exclusion). Once the correct
// treatment is known, the pending layout shifts are reported appropriately
// and the PointerdownPendingData object is reset.
struct PointerdownPendingData {
PointerdownPendingData()
: saw_pointerdown(false), score_delta(0), weighted_score_delta(0) {}
bool saw_pointerdown;
double score_delta;
double weighted_score_delta;
};
PointerdownPendingData pointerdown_pending_data_;
// The per-animation-frame impact region.
LayoutShiftRegion region_;
// Tracks the short period after an input event during which we ignore shifts
// for the purpose of cumulative scoring, and report them to the web perf API
// with hadRecentInput == true.
HeapTaskRunnerTimer<LayoutShiftTracker> timer_;
// The maximum distance any layout object has moved in the current animation
// frame.
float frame_max_distance_;
// The maximum distance any layout object has moved, across all animation
// frames.
float overall_max_distance_;
// Sum of all scroll deltas that occurred in the current animation frame.
// TODO(wangxianzhu): Remove when enabling CLSM90Improvements permanently.
ScrollOffset frame_scroll_delta_;
// Whether either a user input or document scroll have been observed during
// the session. (This is only tracked so UkmPageLoadMetricsObserver to report
// LayoutInstability.CumulativeShiftScore.MainFrame.BeforeInputOrScroll. It's
// not related to input exclusion or the LayoutShift::had_recent_input_ bit.)
bool observed_input_or_scroll_;
// Most recent timestamp of a user input event that has been observed.
// User input includes window resizing but not scrolling.
base::TimeTicks most_recent_input_timestamp_;
bool most_recent_input_timestamp_initialized_;
struct Attribution {
DOMNodeId node_id;
IntRect old_visual_rect;
IntRect new_visual_rect;
Attribution();
Attribution(DOMNodeId node_id,
IntRect old_visual_rect,
IntRect new_visual_rect);
explicit operator bool() const;
bool Encloses(const Attribution&) const;
bool MoreImpactfulThan(const Attribution&) const;
int Area() const;
};
void MaybeRecordAttribution(const Attribution&);
// Nodes that have contributed to the impact region for the current frame.
std::array<Attribution, LayoutShift::kMaxAttributions> attributions_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_SHIFT_TRACKER_H_