blob: 52b297b6a380409494ec9c0383fb0a2ea9489769 [file] [log] [blame]
// Copyright 2015 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/paint_timing.h"
#include <memory>
#include <utility>
#include "third_party/blink/public/platform/web_layer_tree_view.h"
#include "third_party/blink/renderer/core/dom/document.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/local_frame_view.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/loader/interactive_detector.h"
#include "third_party/blink/renderer/core/loader/progress_tracker.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/timing/dom_window_performance.h"
#include "third_party/blink/renderer/core/timing/window_performance.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/scheduler/public/frame_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
namespace blink {
namespace {
WindowPerformance* GetPerformanceInstance(LocalFrame* frame) {
WindowPerformance* performance = nullptr;
if (frame && frame->DomWindow()) {
performance = DOMWindowPerformance::performance(*frame->DomWindow());
}
return performance;
}
} // namespace
// static
const char PaintTiming::kSupplementName[] = "PaintTiming";
// static
PaintTiming& PaintTiming::From(Document& document) {
PaintTiming* timing = Supplement<Document>::From<PaintTiming>(document);
if (!timing) {
timing = MakeGarbageCollected<PaintTiming>(document);
ProvideTo(document, timing);
}
return *timing;
}
void PaintTiming::MarkFirstPaint() {
// Test that m_firstPaint is non-zero here, as well as in setFirstPaint, so
// we avoid invoking monotonicallyIncreasingTime() on every call to
// markFirstPaint().
if (!first_paint_.is_null())
return;
SetFirstPaint(CurrentTimeTicks());
}
void PaintTiming::MarkFirstContentfulPaint() {
// Test that m_firstContentfulPaint is non-zero here, as well as in
// setFirstContentfulPaint, so we avoid invoking
// monotonicallyIncreasingTime() on every call to
// markFirstContentfulPaint().
if (!first_contentful_paint_.is_null())
return;
SetFirstContentfulPaint(CurrentTimeTicks());
}
void PaintTiming::MarkFirstImagePaint() {
if (!first_image_paint_.is_null())
return;
first_image_paint_ = CurrentTimeTicks();
SetFirstContentfulPaint(first_image_paint_);
RegisterNotifySwapTime(PaintEvent::kFirstImagePaint);
}
void PaintTiming::SetFirstMeaningfulPaintCandidate(TimeTicks timestamp) {
if (!first_meaningful_paint_candidate_.is_null())
return;
first_meaningful_paint_candidate_ = timestamp;
if (GetFrame() && GetFrame()->View() && !GetFrame()->View()->IsAttached()) {
GetFrame()->GetFrameScheduler()->OnFirstMeaningfulPaint();
}
}
void PaintTiming::SetFirstMeaningfulPaint(
TimeTicks swap_stamp,
FirstMeaningfulPaintDetector::HadUserInput had_input) {
DCHECK(first_meaningful_paint_swap_.is_null());
DCHECK(!swap_stamp.is_null());
TRACE_EVENT_MARK_WITH_TIMESTAMP2(
"loading,rail,devtools.timeline", "firstMeaningfulPaint", swap_stamp,
"frame", ToTraceValue(GetFrame()), "afterUserInput", had_input);
InteractiveDetector* interactive_detector(
InteractiveDetector::From(*GetSupplementable()));
if (interactive_detector) {
interactive_detector->OnFirstMeaningfulPaintDetected(swap_stamp, had_input);
}
// Notify FMP for UMA only if there's no user input before FMP, so that layout
// changes caused by user interactions wouldn't be considered as FMP.
if (had_input == FirstMeaningfulPaintDetector::kNoUserInput) {
first_meaningful_paint_swap_ = swap_stamp;
NotifyPaintTimingChanged();
}
ReportUserInputHistogram(had_input);
}
void PaintTiming::ReportUserInputHistogram(
FirstMeaningfulPaintDetector::HadUserInput had_input) {
DEFINE_STATIC_LOCAL(EnumerationHistogram, had_user_input_histogram,
("PageLoad.Internal.PaintTiming."
"HadUserInputBeforeFirstMeaningfulPaint",
FirstMeaningfulPaintDetector::kHadUserInputEnumMax));
if (GetFrame() && GetFrame()->IsMainFrame())
had_user_input_histogram.Count(had_input);
}
void PaintTiming::NotifyPaint(bool is_first_paint,
bool text_painted,
bool image_painted) {
if (is_first_paint)
MarkFirstPaint();
if (text_painted)
MarkFirstContentfulPaint();
if (image_painted)
MarkFirstImagePaint();
fmp_detector_->NotifyPaint();
}
void PaintTiming::Trace(blink::Visitor* visitor) {
visitor->Trace(fmp_detector_);
Supplement<Document>::Trace(visitor);
}
PaintTiming::PaintTiming(Document& document)
: Supplement<Document>(document),
fmp_detector_(MakeGarbageCollected<FirstMeaningfulPaintDetector>(this)) {}
LocalFrame* PaintTiming::GetFrame() const {
return GetSupplementable()->GetFrame();
}
void PaintTiming::NotifyPaintTimingChanged() {
if (GetSupplementable()->Loader())
GetSupplementable()->Loader()->DidChangePerformanceTiming();
}
void PaintTiming::SetFirstPaint(TimeTicks stamp) {
if (!first_paint_.is_null())
return;
first_paint_ = stamp;
RegisterNotifySwapTime(PaintEvent::kFirstPaint);
}
void PaintTiming::SetFirstContentfulPaint(TimeTicks stamp) {
if (!first_contentful_paint_.is_null())
return;
SetFirstPaint(stamp);
first_contentful_paint_ = stamp;
RegisterNotifySwapTime(PaintEvent::kFirstContentfulPaint);
}
void PaintTiming::RegisterNotifySwapTime(PaintEvent event) {
RegisterNotifySwapTime(
event, CrossThreadBind(&PaintTiming::ReportSwapTime,
WrapCrossThreadWeakPersistent(this), event));
}
void PaintTiming::RegisterNotifySwapTime(PaintEvent event,
ReportTimeCallback callback) {
// ReportSwapTime on layerTreeView will queue a swap-promise, the callback is
// called when the swap for current render frame completes or fails to happen.
if (!GetFrame() || !GetFrame()->GetPage())
return;
if (WebLayerTreeView* layerTreeView =
GetFrame()->GetPage()->GetChromeClient().GetWebLayerTreeView(
GetFrame())) {
layerTreeView->NotifySwapTime(ConvertToBaseCallback(std::move(callback)));
}
}
void PaintTiming::ReportSwapTime(PaintEvent event,
WebLayerTreeView::SwapResult result,
base::TimeTicks timestamp) {
DCHECK(IsMainThread());
// 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.
ReportSwapResultHistogram(result);
switch (event) {
case PaintEvent::kFirstPaint:
SetFirstPaintSwap(timestamp);
return;
case PaintEvent::kFirstContentfulPaint:
SetFirstContentfulPaintSwap(timestamp);
return;
case PaintEvent::kFirstImagePaint:
SetFirstImagePaintSwap(timestamp);
return;
default:
NOTREACHED();
}
}
void PaintTiming::SetFirstPaintSwap(TimeTicks stamp) {
DCHECK(first_paint_swap_.is_null());
first_paint_swap_ = stamp;
probe::paintTiming(GetSupplementable(), "firstPaint",
TimeTicksInSeconds(first_paint_swap_));
WindowPerformance* performance = GetPerformanceInstance(GetFrame());
if (performance)
performance->AddFirstPaintTiming(first_paint_swap_);
NotifyPaintTimingChanged();
}
void PaintTiming::SetFirstContentfulPaintSwap(TimeTicks stamp) {
DCHECK(first_contentful_paint_swap_.is_null());
first_contentful_paint_swap_ = stamp;
probe::paintTiming(GetSupplementable(), "firstContentfulPaint",
TimeTicksInSeconds(first_contentful_paint_swap_));
WindowPerformance* performance = GetPerformanceInstance(GetFrame());
if (performance)
performance->AddFirstContentfulPaintTiming(first_contentful_paint_swap_);
if (GetFrame())
GetFrame()->Loader().Progress().DidFirstContentfulPaint();
NotifyPaintTimingChanged();
fmp_detector_->NotifyFirstContentfulPaint(first_contentful_paint_swap_);
}
void PaintTiming::SetFirstImagePaintSwap(TimeTicks stamp) {
DCHECK(first_image_paint_swap_.is_null());
first_image_paint_swap_ = stamp;
probe::paintTiming(GetSupplementable(), "firstImagePaint",
TimeTicksInSeconds(first_image_paint_swap_));
NotifyPaintTimingChanged();
}
void PaintTiming::ReportSwapResultHistogram(
const WebLayerTreeView::SwapResult result) {
DEFINE_STATIC_LOCAL(EnumerationHistogram, did_swap_histogram,
("PageLoad.Internal.Renderer.PaintTiming.SwapResult",
WebLayerTreeView::SwapResult::kSwapResultMax));
did_swap_histogram.Count(result);
}
} // namespace blink