Commit 9aaafe22 authored by Nicolás Peña Moreno's avatar Nicolás Peña Moreno Committed by Commit Bot

[LCP] Use first input or scroll on the page

This CL allows LCP to use the first input or scroll on the page to
invalidate LCP values from any frame. Doc describing the logic:
https://docs.google.com/document/d/1uOLW-uAZwvBLrH5ST0OgdAJMmqY37MqBJEZRzTBKgp4/edit

Bug: 1050727
Change-Id: Id9ad7c06750af110d6b7f3eec8637a9615f270b8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2083861Reviewed-by: default avatarEmily Stark <estark@chromium.org>
Reviewed-by: default avatarXianzhu Wang <wangxianzhu@chromium.org>
Reviewed-by: default avatarBryan McQuade <bmcquade@chromium.org>
Commit-Queue: Nicolás Peña Moreno <npm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#748550}
parent a0edfdef
...@@ -53,27 +53,14 @@ void MergeForSubframesWithAdjustedTime( ...@@ -53,27 +53,14 @@ void MergeForSubframesWithAdjustedTime(
inout_timing->Reset(merged_candidate.Time(), merged_candidate.Size()); inout_timing->Reset(merged_candidate.Time(), merged_candidate.Size());
} }
void MergeForSubframes(
ContentfulPaintTimingInfo* inout_timing,
const base::Optional<base::TimeDelta>& candidate_new_time,
const uint64_t& candidate_new_size,
base::TimeDelta navigation_start_offset) {
base::Optional<base::TimeDelta> new_time = base::nullopt;
if (candidate_new_time) {
// If |candidate_new_time| is TimeDelta(), this means that the candidate is
// an image that has not finished loading. Preserve its meaning by not
// adding the |navigation_start_offset|.
new_time = *candidate_new_time > base::TimeDelta()
? navigation_start_offset + candidate_new_time.value()
: base::TimeDelta();
}
MergeForSubframesWithAdjustedTime(inout_timing, new_time, candidate_new_size);
}
bool IsSubframe(content::RenderFrameHost* subframe_rfh) { bool IsSubframe(content::RenderFrameHost* subframe_rfh) {
return subframe_rfh != nullptr && subframe_rfh->GetParent() != nullptr; return subframe_rfh != nullptr && subframe_rfh->GetParent() != nullptr;
} }
void Reset(ContentfulPaintTimingInfo& timing) {
timing.Reset(base::nullopt, 0u);
}
} // namespace } // namespace
ContentfulPaintTimingInfo::ContentfulPaintTimingInfo( ContentfulPaintTimingInfo::ContentfulPaintTimingInfo(
...@@ -180,6 +167,9 @@ LargestContentfulPaintHandler::MergeMainFrameAndSubframes() { ...@@ -180,6 +167,9 @@ LargestContentfulPaintHandler::MergeMainFrameAndSubframes() {
void LargestContentfulPaintHandler::RecordSubframeTiming( void LargestContentfulPaintHandler::RecordSubframeTiming(
const mojom::PaintTimingPtr& timing, const mojom::PaintTimingPtr& timing,
const base::TimeDelta& navigation_start_offset) { const base::TimeDelta& navigation_start_offset) {
UpdateFirstInputOrScrollNotified(
timing->first_input_or_scroll_notified_timestamp,
navigation_start_offset);
MergeForSubframes(&subframe_contentful_paint_.Text(), MergeForSubframes(&subframe_contentful_paint_.Text(),
timing->largest_text_paint, timing->largest_text_paint_size, timing->largest_text_paint, timing->largest_text_paint_size,
navigation_start_offset); navigation_start_offset);
...@@ -190,10 +180,42 @@ void LargestContentfulPaintHandler::RecordSubframeTiming( ...@@ -190,10 +180,42 @@ void LargestContentfulPaintHandler::RecordSubframeTiming(
void LargestContentfulPaintHandler::RecordMainFrameTiming( void LargestContentfulPaintHandler::RecordMainFrameTiming(
const mojom::PaintTimingPtr& timing) { const mojom::PaintTimingPtr& timing) {
main_frame_contentful_paint_.Text().Reset(timing->largest_text_paint, UpdateFirstInputOrScrollNotified(
timing->largest_text_paint_size); timing->first_input_or_scroll_notified_timestamp,
main_frame_contentful_paint_.Image().Reset(timing->largest_image_paint, /* navigation_start_offset */ base::TimeDelta());
timing->largest_image_paint_size); if (IsValid(timing->largest_text_paint)) {
main_frame_contentful_paint_.Text().Reset(timing->largest_text_paint,
timing->largest_text_paint_size);
}
if (IsValid(timing->largest_image_paint)) {
main_frame_contentful_paint_.Image().Reset(
timing->largest_image_paint, timing->largest_image_paint_size);
}
}
void LargestContentfulPaintHandler::UpdateFirstInputOrScrollNotified(
const base::Optional<base::TimeDelta>& candidate_new_time,
const base::TimeDelta& navigation_start_offset) {
if (!candidate_new_time.has_value())
return;
if (first_input_or_scroll_notified_ >
navigation_start_offset + *candidate_new_time) {
first_input_or_scroll_notified_ =
navigation_start_offset + *candidate_new_time;
// Consider candidates after input to be invalid. This is needed because
// IPCs from different frames can arrive out of order. For example, this is
// consistently the case when a click on the main frame produces a new
// iframe which contains the largest content so far.
if (!IsValid(main_frame_contentful_paint_.Text().Time()))
Reset(main_frame_contentful_paint_.Text());
if (!IsValid(main_frame_contentful_paint_.Image().Time()))
Reset(main_frame_contentful_paint_.Image());
if (!IsValid(subframe_contentful_paint_.Text().Time()))
Reset(subframe_contentful_paint_.Text());
if (!IsValid(subframe_contentful_paint_.Image().Time()))
Reset(subframe_contentful_paint_.Image());
}
} }
void LargestContentfulPaintHandler::OnDidFinishSubFrameNavigation( void LargestContentfulPaintHandler::OnDidFinishSubFrameNavigation(
...@@ -223,4 +245,23 @@ void LargestContentfulPaintHandler::OnDidFinishSubFrameNavigation( ...@@ -223,4 +245,23 @@ void LargestContentfulPaintHandler::OnDidFinishSubFrameNavigation(
navigation_handle->GetFrameTreeNodeId(), navigation_delta)); navigation_handle->GetFrameTreeNodeId(), navigation_delta));
} }
void LargestContentfulPaintHandler::MergeForSubframes(
ContentfulPaintTimingInfo* inout_timing,
const base::Optional<base::TimeDelta>& candidate_new_time,
const uint64_t& candidate_new_size,
base::TimeDelta navigation_start_offset) {
base::Optional<base::TimeDelta> new_time = base::nullopt;
if (candidate_new_time) {
// If |candidate_new_time| is TimeDelta(), this means that the candidate is
// an image that has not finished loading. Preserve its meaning by not
// adding the |navigation_start_offset|.
new_time = *candidate_new_time > base::TimeDelta()
? navigation_start_offset + candidate_new_time.value()
: base::TimeDelta();
}
if (IsValid(new_time))
MergeForSubframesWithAdjustedTime(inout_timing, new_time,
candidate_new_size);
}
} // namespace page_load_metrics } // namespace page_load_metrics
...@@ -105,6 +105,24 @@ class LargestContentfulPaintHandler { ...@@ -105,6 +105,24 @@ class LargestContentfulPaintHandler {
void RecordSubframeTiming(const mojom::PaintTimingPtr& timing, void RecordSubframeTiming(const mojom::PaintTimingPtr& timing,
const base::TimeDelta& navigation_start_offset); const base::TimeDelta& navigation_start_offset);
void RecordMainFrameTiming(const page_load_metrics::mojom::PaintTimingPtr&); void RecordMainFrameTiming(const page_load_metrics::mojom::PaintTimingPtr&);
void UpdateFirstInputOrScrollNotified(
const base::Optional<base::TimeDelta>& candidate_new_time,
const base::TimeDelta& navigation_start_offset);
void MergeForSubframes(
ContentfulPaintTimingInfo* inout_timing,
const base::Optional<base::TimeDelta>& candidate_new_time,
const uint64_t& candidate_new_size,
base::TimeDelta navigation_start_offset);
bool IsValid(const base::Optional<base::TimeDelta>& time) {
// When |time| is not present, this means that there is no current
// candidate. If |time| is 0, it corresponds to an image that has not
// finished loading. In both cases, we do not know the timestamp at which
// |time| was determned. Therefore, we just assume that the time is valid
// only if we have not yet received a notification for first input or scroll
if (!time.has_value() || (*time).is_zero())
return first_input_or_scroll_notified_ == base::TimeDelta::Max();
return *time < first_input_or_scroll_notified_;
}
ContentfulPaint main_frame_contentful_paint_; ContentfulPaint main_frame_contentful_paint_;
ContentfulPaint subframe_contentful_paint_; ContentfulPaint subframe_contentful_paint_;
...@@ -112,6 +130,10 @@ class LargestContentfulPaintHandler { ...@@ -112,6 +130,10 @@ class LargestContentfulPaintHandler {
// navigations. // navigations.
base::Optional<int> main_frame_tree_node_id_; base::Optional<int> main_frame_tree_node_id_;
// The first input or scroll across all frames in the page. Used to filter out
// paints that occur on other frames but after this time.
base::TimeDelta first_input_or_scroll_notified_ = base::TimeDelta::Max();
// Navigation start offsets for the most recently committed document in each // Navigation start offsets for the most recently committed document in each
// frame. // frame.
std::map<FrameTreeNodeId, base::TimeDelta> subframe_navigation_start_offset_; std::map<FrameTreeNodeId, base::TimeDelta> subframe_navigation_start_offset_;
......
...@@ -335,6 +335,8 @@ class PageLoadTimingMerger { ...@@ -335,6 +335,8 @@ class PageLoadTimingMerger {
new_paint_timing.largest_text_paint; new_paint_timing.largest_text_paint;
target_paint_timing->largest_text_paint_size = target_paint_timing->largest_text_paint_size =
new_paint_timing.largest_text_paint_size; new_paint_timing.largest_text_paint_size;
target_paint_timing->first_input_or_scroll_notified_timestamp =
new_paint_timing.first_input_or_scroll_notified_timestamp;
} }
} }
......
...@@ -45,6 +45,10 @@ struct PaintTiming { ...@@ -45,6 +45,10 @@ struct PaintTiming {
// (Experimental) Size of the largest text of the largest text paint, by // (Experimental) Size of the largest text of the largest text paint, by
// Size = Height * Width. // Size = Height * Width.
uint64 largest_text_paint_size; uint64 largest_text_paint_size;
// (Experimental) Time when first input or scroll is received, causing the
// largest contentful paint algorithm to stop.
mojo_base.mojom.TimeDelta? first_input_or_scroll_notified_timestamp;
}; };
// TimeDeltas below represent durations of time during the page load. // TimeDeltas below represent durations of time during the page load.
......
...@@ -440,14 +440,16 @@ MetricsRenderFrameObserver::Timing MetricsRenderFrameObserver::GetTiming() ...@@ -440,14 +440,16 @@ MetricsRenderFrameObserver::Timing MetricsRenderFrameObserver::GetTiming()
: ClampDelta(perf.LargestImagePaint(), start); : ClampDelta(perf.LargestImagePaint(), start);
} }
if (perf.LargestTextPaintSize() > 0) { if (perf.LargestTextPaintSize() > 0) {
timing->paint_timing->largest_text_paint =
perf.LargestTextPaint() == 0.0
? base::TimeDelta()
: ClampDelta(perf.LargestTextPaint(), start);
timing->paint_timing->largest_text_paint_size = perf.LargestTextPaintSize();
// LargestTextPaint and LargestTextPaintSize should be available at the // LargestTextPaint and LargestTextPaintSize should be available at the
// same time. This is a renderer side DCHECK to ensure this. // same time. This is a renderer side DCHECK to ensure this.
DCHECK(perf.LargestTextPaint()); DCHECK(perf.LargestTextPaint());
timing->paint_timing->largest_text_paint =
ClampDelta(perf.LargestTextPaint(), start);
timing->paint_timing->largest_text_paint_size = perf.LargestTextPaintSize();
}
if (perf.FirstInputOrScrollNotifiedTimestamp() > 0) {
timing->paint_timing->first_input_or_scroll_notified_timestamp =
ClampDelta(perf.FirstInputOrScrollNotifiedTimestamp(), start);
} }
if (perf.ParseStart() > 0.0) if (perf.ParseStart() > 0.0)
timing->parse_timing->parse_start = ClampDelta(perf.ParseStart(), start); timing->parse_timing->parse_start = ClampDelta(perf.ParseStart(), start);
......
...@@ -97,6 +97,7 @@ class WebPerformance { ...@@ -97,6 +97,7 @@ class WebPerformance {
BLINK_EXPORT uint64_t LargestImagePaintSize() const; BLINK_EXPORT uint64_t LargestImagePaintSize() const;
BLINK_EXPORT double LargestTextPaint() const; BLINK_EXPORT double LargestTextPaint() const;
BLINK_EXPORT uint64_t LargestTextPaintSize() const; BLINK_EXPORT uint64_t LargestTextPaintSize() const;
BLINK_EXPORT double FirstInputOrScrollNotifiedTimestamp() const;
BLINK_EXPORT double FirstInputDelay() const; BLINK_EXPORT double FirstInputDelay() const;
BLINK_EXPORT double FirstInputTimestamp() const; BLINK_EXPORT double FirstInputTimestamp() const;
BLINK_EXPORT double LongestInputDelay() const; BLINK_EXPORT double LongestInputDelay() const;
......
...@@ -191,6 +191,11 @@ uint64_t WebPerformance::LargestTextPaintSize() const { ...@@ -191,6 +191,11 @@ uint64_t WebPerformance::LargestTextPaintSize() const {
return private_->timing()->LargestTextPaintSize(); return private_->timing()->LargestTextPaintSize();
} }
double WebPerformance::FirstInputOrScrollNotifiedTimestamp() const {
return MillisecondsToSeconds(
private_->timing()->FirstInputOrScrollNotifiedTimestamp());
}
double WebPerformance::FirstInputDelay() const { double WebPerformance::FirstInputDelay() const {
return MillisecondsToSeconds(private_->timing()->FirstInputDelay()); return MillisecondsToSeconds(private_->timing()->FirstInputDelay());
} }
......
...@@ -168,8 +168,11 @@ void PaintTimingDetector::NotifyImageRemoved( ...@@ -168,8 +168,11 @@ void PaintTimingDetector::NotifyImageRemoved(
} }
} }
void PaintTimingDetector::StopRecordingLargestContentfulPaint() { void PaintTimingDetector::OnInputOrScroll() {
DCHECK(frame_view_); // If we have already stopped, then abort.
if (!is_recording_largest_contentful_paint_)
return;
// TextPaintTimingDetector is used for both Largest Contentful Paint and for // TextPaintTimingDetector is used for both Largest Contentful Paint and for
// Element Timing. Therefore, here we only want to stop recording Largest // Element Timing. Therefore, here we only want to stop recording Largest
// Contentful Paint. // Contentful Paint.
...@@ -179,6 +182,11 @@ void PaintTimingDetector::StopRecordingLargestContentfulPaint() { ...@@ -179,6 +182,11 @@ void PaintTimingDetector::StopRecordingLargestContentfulPaint() {
if (image_paint_timing_detector_) if (image_paint_timing_detector_)
image_paint_timing_detector_->StopRecordEntries(); image_paint_timing_detector_->StopRecordEntries();
largest_contentful_paint_calculator_ = nullptr; largest_contentful_paint_calculator_ = nullptr;
DCHECK_EQ(first_input_or_scroll_notified_timestamp_, base::TimeTicks());
first_input_or_scroll_notified_timestamp_ = base::TimeTicks::Now();
DidChangePerformanceTiming();
is_recording_largest_contentful_paint_ = false;
} }
void PaintTimingDetector::NotifyInputEvent(WebInputEvent::Type type) { void PaintTimingDetector::NotifyInputEvent(WebInputEvent::Type type) {
...@@ -189,14 +197,14 @@ void PaintTimingDetector::NotifyInputEvent(WebInputEvent::Type type) { ...@@ -189,14 +197,14 @@ void PaintTimingDetector::NotifyInputEvent(WebInputEvent::Type type) {
WebInputEvent::IsPinchGestureEventType(type)) { WebInputEvent::IsPinchGestureEventType(type)) {
return; return;
} }
StopRecordingLargestContentfulPaint(); OnInputOrScroll();
} }
void PaintTimingDetector::NotifyScroll(mojom::blink::ScrollType scroll_type) { void PaintTimingDetector::NotifyScroll(mojom::blink::ScrollType scroll_type) {
if (scroll_type != mojom::blink::ScrollType::kUser && if (scroll_type != mojom::blink::ScrollType::kUser &&
scroll_type != mojom::blink::ScrollType::kCompositor) scroll_type != mojom::blink::ScrollType::kCompositor)
return; return;
StopRecordingLargestContentfulPaint(); OnInputOrScroll();
} }
bool PaintTimingDetector::NeedToNotifyInputOrScroll() const { bool PaintTimingDetector::NeedToNotifyInputOrScroll() const {
......
...@@ -172,6 +172,9 @@ class CORE_EXPORT PaintTimingDetector ...@@ -172,6 +172,9 @@ class CORE_EXPORT PaintTimingDetector
uint64_t LargestImagePaintSize() const { return largest_image_paint_size_; } uint64_t LargestImagePaintSize() const { return largest_image_paint_size_; }
base::TimeTicks LargestTextPaint() const { return largest_text_paint_time_; } base::TimeTicks LargestTextPaint() const { return largest_text_paint_time_; }
uint64_t LargestTextPaintSize() const { return largest_text_paint_size_; } uint64_t LargestTextPaintSize() const { return largest_text_paint_size_; }
base::TimeTicks FirstInputOrScrollNotifiedTimestamp() const {
return first_input_or_scroll_notified_timestamp_;
}
void UpdateLargestContentfulPaintCandidate(); void UpdateLargestContentfulPaintCandidate();
...@@ -179,7 +182,8 @@ class CORE_EXPORT PaintTimingDetector ...@@ -179,7 +182,8 @@ class CORE_EXPORT PaintTimingDetector
void Trace(Visitor* visitor); void Trace(Visitor* visitor);
private: private:
void StopRecordingLargestContentfulPaint(); // Method called to stop recording the Largest Contentful Paint.
void OnInputOrScroll();
bool HasLargestImagePaintChanged(base::TimeTicks, uint64_t size) const; bool HasLargestImagePaintChanged(base::TimeTicks, uint64_t size) const;
bool HasLargestTextPaintChanged(base::TimeTicks, uint64_t size) const; bool HasLargestTextPaintChanged(base::TimeTicks, uint64_t size) const;
Member<LocalFrameView> frame_view_; Member<LocalFrameView> frame_view_;
...@@ -189,7 +193,14 @@ class CORE_EXPORT PaintTimingDetector ...@@ -189,7 +193,14 @@ class CORE_EXPORT PaintTimingDetector
// image paint is found. // image paint is found.
Member<ImagePaintTimingDetector> image_paint_timing_detector_; Member<ImagePaintTimingDetector> image_paint_timing_detector_;
// This member lives for as long as the largest contentful paint is being
// computed. However, it is initialized lazily, so it may be nullptr because
// it has not yet been initialized or because we have stopped computing LCP.
Member<LargestContentfulPaintCalculator> largest_contentful_paint_calculator_; Member<LargestContentfulPaintCalculator> largest_contentful_paint_calculator_;
// Time at which the first input or scroll is notified to PaintTimingDetector,
// hence causing LCP to stop being recorded. This is the same time at which
// |largest_contentful_paint_calculator_| is set to nullptr.
base::TimeTicks first_input_or_scroll_notified_timestamp_;
Member<PaintTimingCallbackManagerImpl> callback_manager_; Member<PaintTimingCallbackManagerImpl> callback_manager_;
...@@ -201,6 +212,7 @@ class CORE_EXPORT PaintTimingDetector ...@@ -201,6 +212,7 @@ class CORE_EXPORT PaintTimingDetector
// Largest text information. // Largest text information.
base::TimeTicks largest_text_paint_time_; base::TimeTicks largest_text_paint_time_;
uint64_t largest_text_paint_size_ = 0; uint64_t largest_text_paint_size_ = 0;
bool is_recording_largest_contentful_paint_ = true;
}; };
// Largest Text Paint and Text Element Timing aggregate text nodes by these // Largest Text Paint and Text Element Timing aggregate text nodes by these
......
...@@ -402,6 +402,15 @@ uint64_t PerformanceTiming::LargestTextPaintSize() const { ...@@ -402,6 +402,15 @@ uint64_t PerformanceTiming::LargestTextPaintSize() const {
return paint_timing_detector->LargestTextPaintSize(); return paint_timing_detector->LargestTextPaintSize();
} }
uint64_t PerformanceTiming::FirstInputOrScrollNotifiedTimestamp() const {
PaintTimingDetector* paint_timing_detector = GetPaintTimingDetector();
if (!paint_timing_detector)
return 0;
return MonotonicTimeToIntegerMilliseconds(
paint_timing_detector->FirstInputOrScrollNotifiedTimestamp());
}
uint64_t PerformanceTiming::FirstInputDelay() const { uint64_t PerformanceTiming::FirstInputDelay() const {
const InteractiveDetector* interactive_detector = GetInteractiveDetector(); const InteractiveDetector* interactive_detector = GetInteractiveDetector();
if (!interactive_detector) if (!interactive_detector)
......
...@@ -124,6 +124,9 @@ class CORE_EXPORT PerformanceTiming final : public ScriptWrappable, ...@@ -124,6 +124,9 @@ class CORE_EXPORT PerformanceTiming final : public ScriptWrappable,
// are the time and size of it. // are the time and size of it.
uint64_t LargestTextPaint() const; uint64_t LargestTextPaint() const;
uint64_t LargestTextPaintSize() const; uint64_t LargestTextPaintSize() const;
// The time at which we are notified of the first input or scroll event which
// causes the largest contentful paint algorithm to stop.
uint64_t FirstInputOrScrollNotifiedTimestamp() const;
// The duration between the hardware timestamp and being queued on the main // The duration between the hardware timestamp and being queued on the main
// thread for the first click, tap, key press, cancellable touchstart, or // thread for the first click, tap, key press, cancellable touchstart, or
// pointer down followed by a pointer up. // pointer down followed by a pointer up.
......
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