blob: 4218e90351dfbef1c63122ed3fd613219825814c [file] [log] [blame]
// Copyright 2016 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/paint/first_meaningful_paint_detector.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_layer_tree_view.h"
#include "third_party/blink/renderer/core/css/font_face_set_document.h"
#include "third_party/blink/renderer/core/paint/paint_timing.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
namespace blink {
namespace {
// Web fonts that laid out more than this number of characters block First
// Meaningful Paint.
const int kBlankCharactersThreshold = 200;
} // namespace
FirstMeaningfulPaintDetector& FirstMeaningfulPaintDetector::From(
Document& document) {
return PaintTiming::From(document).GetFirstMeaningfulPaintDetector();
}
FirstMeaningfulPaintDetector::FirstMeaningfulPaintDetector(
PaintTiming* paint_timing)
: paint_timing_(paint_timing) {}
Document* FirstMeaningfulPaintDetector::GetDocument() {
return paint_timing_->GetSupplementable();
}
// Computes "layout significance" (http://goo.gl/rytlPL) of a layout operation.
// Significance of a layout is the number of layout objects newly added to the
// layout tree, weighted by page height (before and after the layout).
// A paint after the most significance layout during page load is reported as
// First Meaningful Paint.
void FirstMeaningfulPaintDetector::MarkNextPaintAsMeaningfulIfNeeded(
const LayoutObjectCounter& counter,
int contents_height_before_layout,
int contents_height_after_layout,
int visible_height) {
if (network0_quiet_reached_ && network2_quiet_reached_)
return;
unsigned delta = counter.Count() - prev_layout_object_count_;
prev_layout_object_count_ = counter.Count();
if (visible_height == 0)
return;
double ratio_before = std::max(
1.0, static_cast<double>(contents_height_before_layout) / visible_height);
double ratio_after = std::max(
1.0, static_cast<double>(contents_height_after_layout) / visible_height);
double significance = delta / ((ratio_before + ratio_after) / 2);
// If the page has many blank characters, the significance value is
// accumulated until the text become visible.
size_t approximate_blank_character_count =
FontFaceSetDocument::ApproximateBlankCharacterCount(*GetDocument());
if (approximate_blank_character_count > kBlankCharactersThreshold) {
accumulated_significance_while_having_blank_text_ += significance;
} else {
significance += accumulated_significance_while_having_blank_text_;
accumulated_significance_while_having_blank_text_ = 0;
if (significance > max_significance_so_far_) {
next_paint_is_meaningful_ = true;
max_significance_so_far_ = significance;
}
}
}
void FirstMeaningfulPaintDetector::NotifyPaint() {
if (!next_paint_is_meaningful_)
return;
// Skip document background-only paints.
if (paint_timing_->FirstPaintRendered().is_null())
return;
provisional_first_meaningful_paint_ = CurrentTimeTicks();
next_paint_is_meaningful_ = false;
if (network2_quiet_reached_)
return;
had_user_input_before_provisional_first_meaningful_paint_ = had_user_input_;
provisional_first_meaningful_paint_swap_ = TimeTicks();
RegisterNotifySwapTime(PaintEvent::kProvisionalFirstMeaningfulPaint);
}
// This is called only on FirstMeaningfulPaintDetector for main frame.
void FirstMeaningfulPaintDetector::NotifyInputEvent() {
// Ignore user inputs before first paint.
if (paint_timing_->FirstPaintRendered().is_null())
return;
had_user_input_ = kHadUserInput;
}
void FirstMeaningfulPaintDetector::OnNetwork0Quiet() {
network0_quiet_reached_ = true;
if (!GetDocument() || paint_timing_->FirstContentfulPaintRendered().is_null())
return;
if (!provisional_first_meaningful_paint_.is_null()) {
// Enforce FirstContentfulPaint <= FirstMeaningfulPaint.
first_meaningful_paint0_quiet_ =
std::max(provisional_first_meaningful_paint_,
paint_timing_->FirstContentfulPaintRendered());
}
ReportHistograms();
}
void FirstMeaningfulPaintDetector::OnNetwork2Quiet() {
if (!GetDocument() || network2_quiet_reached_ ||
paint_timing_->FirstContentfulPaintRendered().is_null())
return;
network2_quiet_reached_ = true;
if (!provisional_first_meaningful_paint_.is_null()) {
TimeTicks first_meaningful_paint2_quiet_swap;
// Enforce FirstContentfulPaint <= FirstMeaningfulPaint.
if (provisional_first_meaningful_paint_ <
paint_timing_->FirstContentfulPaintRendered()) {
first_meaningful_paint2_quiet_ =
paint_timing_->FirstContentfulPaintRendered();
first_meaningful_paint2_quiet_swap =
paint_timing_->FirstContentfulPaint();
// It's possible that this timer fires between when the first contentful
// paint is set and its SwapPromise is fulfilled. If this happens, defer
// until NotifyFirstContentfulPaint() is called.
if (first_meaningful_paint2_quiet_swap.is_null())
defer_first_meaningful_paint_ = kDeferFirstContentfulPaintNotSet;
} else {
first_meaningful_paint2_quiet_ = provisional_first_meaningful_paint_;
first_meaningful_paint2_quiet_swap =
provisional_first_meaningful_paint_swap_;
// We might still be waiting for one or more swap promises, in which case
// we want to defer reporting first meaningful paint until they complete.
// Otherwise, we would either report the wrong swap timestamp or none at
// all.
if (outstanding_swap_promise_count_ > 0)
defer_first_meaningful_paint_ = kDeferOutstandingSwapPromises;
}
if (defer_first_meaningful_paint_ == kDoNotDefer) {
// Report FirstMeaningfulPaint when the page reached network 2-quiet if
// we aren't waiting for a swap timestamp.
SetFirstMeaningfulPaint(first_meaningful_paint2_quiet_swap);
}
}
ReportHistograms();
}
void FirstMeaningfulPaintDetector::ReportHistograms() {
// This enum backs an UMA histogram, and should be treated as append-only.
enum HadNetworkQuiet {
kHadNetwork0Quiet,
kHadNetwork2Quiet,
kHadNetworkQuietEnumMax
};
DEFINE_STATIC_LOCAL(EnumerationHistogram, had_network_quiet_histogram,
("PageLoad.Internal.Renderer."
"FirstMeaningfulPaintDetector.HadNetworkQuiet",
kHadNetworkQuietEnumMax));
// This enum backs an UMA histogram, and should be treated as append-only.
enum FMPOrderingEnum {
kFMP0QuietFirst,
kFMP2QuietFirst,
kFMP0QuietEqualFMP2Quiet,
kFMPOrderingEnumMax
};
DEFINE_STATIC_LOCAL(
EnumerationHistogram, first_meaningful_paint_ordering_histogram,
("PageLoad.Internal.Renderer.FirstMeaningfulPaintDetector."
"FirstMeaningfulPaintOrdering",
kFMPOrderingEnumMax));
if (!first_meaningful_paint0_quiet_.is_null() &&
!first_meaningful_paint2_quiet_.is_null()) {
int sample;
if (first_meaningful_paint2_quiet_ < first_meaningful_paint0_quiet_) {
sample = kFMP0QuietFirst;
} else if (first_meaningful_paint2_quiet_ >
first_meaningful_paint0_quiet_) {
sample = kFMP2QuietFirst;
} else {
sample = kFMP0QuietEqualFMP2Quiet;
}
first_meaningful_paint_ordering_histogram.Count(sample);
} else if (!first_meaningful_paint0_quiet_.is_null()) {
had_network_quiet_histogram.Count(kHadNetwork0Quiet);
} else if (!first_meaningful_paint2_quiet_.is_null()) {
had_network_quiet_histogram.Count(kHadNetwork2Quiet);
}
}
void FirstMeaningfulPaintDetector::RegisterNotifySwapTime(PaintEvent event) {
++outstanding_swap_promise_count_;
paint_timing_->RegisterNotifySwapTime(
event, CrossThreadBind(&FirstMeaningfulPaintDetector::ReportSwapTime,
WrapCrossThreadWeakPersistent(this), event));
}
void FirstMeaningfulPaintDetector::ReportSwapTime(
PaintEvent event,
WebLayerTreeView::SwapResult result,
base::TimeTicks timestamp) {
DCHECK(event == PaintEvent::kProvisionalFirstMeaningfulPaint);
DCHECK_GT(outstanding_swap_promise_count_, 0U);
--outstanding_swap_promise_count_;
// If the swap fails for any reason, we use the timestamp when the SwapPromise
// was broken. |result| == WebLayerTreeView::SwapResult::kDidNotSwapSwapFails
// usually means the compositor decided not swap because there was no actual
// damage, which can happen when what's being painted isn't visible. In this
// case, the timestamp will be consistent with the case where the swap
// succeeds, as they both capture the time up to swap. In other failure cases
// (aborts during commit), this timestamp is an improvement over the blink
// paint time, but does not capture some time we're interested in, e.g. image
// decoding.
//
// TODO(crbug.com/738235): Consider not reporting any timestamp when failing
// for reasons other than kDidNotSwapSwapFails.
paint_timing_->ReportSwapResultHistogram(result);
provisional_first_meaningful_paint_swap_ = timestamp;
probe::paintTiming(GetDocument(), "firstMeaningfulPaintCandidate",
TimeTicksInSeconds(timestamp));
// Ignore the first meaningful paint candidate as this generally is the first
// contentful paint itself.
if (!seen_first_meaningful_paint_candidate_) {
seen_first_meaningful_paint_candidate_ = true;
} else {
paint_timing_->SetFirstMeaningfulPaintCandidate(
provisional_first_meaningful_paint_swap_);
}
if (defer_first_meaningful_paint_ == kDeferOutstandingSwapPromises &&
outstanding_swap_promise_count_ == 0) {
DCHECK(!first_meaningful_paint2_quiet_.is_null());
SetFirstMeaningfulPaint(provisional_first_meaningful_paint_swap_);
}
}
void FirstMeaningfulPaintDetector::NotifyFirstContentfulPaint(
TimeTicks swap_stamp) {
if (defer_first_meaningful_paint_ != kDeferFirstContentfulPaintNotSet)
return;
SetFirstMeaningfulPaint(swap_stamp);
}
void FirstMeaningfulPaintDetector::SetFirstMeaningfulPaint(
TimeTicks swap_stamp) {
DCHECK(paint_timing_->FirstMeaningfulPaint().is_null());
DCHECK(!swap_stamp.is_null());
DCHECK(network2_quiet_reached_);
double swap_time_seconds = TimeTicksInSeconds(swap_stamp);
probe::paintTiming(GetDocument(), "firstMeaningfulPaint", swap_time_seconds);
// If there's only been one contentful paint, then there won't have been
// a meaningful paint signalled to the Scheduler, so mark one now.
// This is a no-op if a FMPC has already been marked.
paint_timing_->SetFirstMeaningfulPaintCandidate(swap_stamp);
paint_timing_->SetFirstMeaningfulPaint(
swap_stamp, had_user_input_before_provisional_first_meaningful_paint_);
}
void FirstMeaningfulPaintDetector::Trace(blink::Visitor* visitor) {
visitor->Trace(paint_timing_);
}
} // namespace blink