blob: e8317a474087c72dd0f124be7a31ef3002a17483 [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/paint/text_paint_timing_detector.h"
#include <memory>
#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/paint/largest_contentful_paint_calculator.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
namespace blink {
namespace {
bool LargeTextFirst(const base::WeakPtr<TextRecord>& a,
const base::WeakPtr<TextRecord>& b) {
DCHECK(a);
DCHECK(b);
if (a->first_size != b->first_size)
return a->first_size > b->first_size;
// This make sure that two different nodes with the same |first_size| wouldn't
// be merged in the set.
return a->insertion_index_ < b->insertion_index_;
}
} // namespace
TextPaintTimingDetector::TextPaintTimingDetector(
LocalFrameView* frame_view,
PaintTimingDetector* paint_timing_detector,
PaintTimingCallbackManager* callback_manager)
: records_manager_(frame_view, paint_timing_detector),
callback_manager_(callback_manager),
frame_view_(frame_view) {}
void LargestTextPaintManager::PopulateTraceValue(
TracedValue& value,
const TextRecord& first_text_paint) {
// TODO(crbug.com/976893): Remove DOMNodeId.
value.SetInteger("DOMNodeId", static_cast<int>(first_text_paint.node_id));
value.SetInteger("size", static_cast<int>(first_text_paint.first_size));
value.SetInteger("candidateIndex", ++count_candidates_);
value.SetBoolean("isMainFrame", frame_view_->GetFrame().IsMainFrame());
value.SetBoolean("isOOPIF",
!frame_view_->GetFrame().LocalFrameRoot().IsMainFrame());
if (first_text_paint.lcp_rect_info_) {
first_text_paint.lcp_rect_info_->OutputToTraceValue(value);
}
}
void LargestTextPaintManager::ReportCandidateToTrace(
const TextRecord& largest_text_record) {
if (!PaintTimingDetector::IsTracing())
return;
auto value = std::make_unique<TracedValue>();
PopulateTraceValue(*value, largest_text_record);
TRACE_EVENT_MARK_WITH_TIMESTAMP2("loading", "LargestTextPaint::Candidate",
largest_text_record.paint_time, "data",
std::move(value), "frame",
ToTraceValue(&frame_view_->GetFrame()));
}
void LargestTextPaintManager::ReportNoCandidateToTrace() {
if (!PaintTimingDetector::IsTracing())
return;
auto value = std::make_unique<TracedValue>();
value->SetInteger("candidateIndex", ++count_candidates_);
value->SetBoolean("isMainFrame", frame_view_->GetFrame().IsMainFrame());
value->SetBoolean("isOOPIF",
!frame_view_->GetFrame().LocalFrameRoot().IsMainFrame());
TRACE_EVENT2("loading", "LargestTextPaint::NoCandidate", "data",
std::move(value), "frame",
ToTraceValue(&frame_view_->GetFrame()));
}
base::WeakPtr<TextRecord> LargestTextPaintManager::UpdateCandidate() {
base::WeakPtr<TextRecord> largest_text_record = FindLargestPaintCandidate();
const base::TimeTicks time =
largest_text_record ? largest_text_record->paint_time : base::TimeTicks();
const uint64_t size =
largest_text_record ? largest_text_record->first_size : 0;
DCHECK(paint_timing_detector_);
bool changed =
paint_timing_detector_->NotifyIfChangedLargestTextPaint(time, size);
if (changed) {
if (!time.is_null())
ReportCandidateToTrace(*largest_text_record);
else
ReportNoCandidateToTrace();
}
return largest_text_record;
}
void TextPaintTimingDetector::OnPaintFinished() {
if (need_update_timing_at_frame_end_) {
need_update_timing_at_frame_end_ = false;
frame_view_->GetPaintTimingDetector()
.UpdateLargestContentfulPaintCandidate();
}
if (records_manager_.NeedMeausuringPaintTime()) {
if (!awaiting_presentation_promise_) {
// |WrapCrossThreadWeakPersistent| guarantees that when |this| is killed,
// the callback function will not be invoked.
RegisterNotifyPresentationTime(
WTF::Bind(&TextPaintTimingDetector::ReportPresentationTime,
WrapCrossThreadWeakPersistent(this)));
}
}
}
void TextPaintTimingDetector::LayoutObjectWillBeDestroyed(
const LayoutObject& object) {
if (records_manager_.IsKnownVisible(object)) {
records_manager_.RemoveVisibleRecord(object);
need_update_timing_at_frame_end_ = true;
} else if (records_manager_.IsKnownInvisible(object)) {
records_manager_.RemoveInvisibleRecord(object);
need_update_timing_at_frame_end_ = true;
}
}
void TextPaintTimingDetector::RegisterNotifyPresentationTime(
PaintTimingCallbackManager::LocalThreadCallback callback) {
callback_manager_->RegisterCallback(std::move(callback));
awaiting_presentation_promise_ = true;
}
void TextPaintTimingDetector::ReportPresentationTime(
base::TimeTicks timestamp) {
if (!records_manager_.HasTextElementTiming()) {
Document* document = frame_view_->GetFrame().GetDocument();
if (document) {
LocalDOMWindow* window = document->domWindow();
if (window) {
records_manager_.SetTextElementTiming(
&TextElementTiming::From(*window));
}
}
}
records_manager_.AssignPaintTimeToQueuedRecords(timestamp);
if (IsRecordingLargestTextPaint())
UpdateCandidate();
awaiting_presentation_promise_ = false;
}
bool TextPaintTimingDetector::ShouldWalkObject(
const LayoutBoxModelObject& object) const {
// TODO(crbug.com/933479): Use LayoutObject::GeneratingNode() to include
// anonymous objects' rect.
Node* node = object.GetNode();
if (!node)
return false;
// If we have finished recording Largest Text Paint and the element is a
// shadow element or has no elementtiming attribute, then we should not record
// its text.
if (!records_manager_.IsRecordingLargestTextPaint() &&
!TextElementTiming::NeededForElementTiming(*node)) {
return false;
}
// This metric defines the size of a text block by its first size, so we
// should not walk the object if it has been recorded.
return !records_manager_.HasRecorded(object);
}
void TextPaintTimingDetector::RecordAggregatedText(
const LayoutBoxModelObject& aggregator,
const IntRect& aggregated_visual_rect,
const PropertyTreeStateOrAlias& property_tree_state) {
DCHECK(ShouldWalkObject(aggregator));
// The caller should check this.
DCHECK(!aggregated_visual_rect.IsEmpty());
FloatRect mapped_visual_rect =
frame_view_->GetPaintTimingDetector().CalculateVisualRect(
aggregated_visual_rect, property_tree_state);
uint64_t aggregated_size = mapped_visual_rect.Size().Area();
DCHECK_LE(IgnorePaintTimingScope::IgnoreDepth(), 1);
// Record the largest aggregated text that is hidden due to documentElement
// being invisible but by no other reason (i.e. IgnoreDepth() needs to be 1).
if (IgnorePaintTimingScope::IgnoreDepth() == 1) {
if (IgnorePaintTimingScope::IsDocumentElementInvisible() &&
records_manager_.IsRecordingLargestTextPaint()) {
records_manager_.MaybeUpdateLargestIgnoredText(
aggregator, aggregated_size, aggregated_visual_rect,
mapped_visual_rect);
}
return;
}
if (aggregated_size == 0) {
records_manager_.RecordInvisibleObject(aggregator);
} else {
records_manager_.RecordVisibleObject(
aggregator, aggregated_size,
TextElementTiming::ComputeIntersectionRect(
aggregator, aggregated_visual_rect, property_tree_state,
frame_view_),
aggregated_visual_rect, mapped_visual_rect);
if (base::Optional<PaintTimingVisualizer>& visualizer =
frame_view_->GetPaintTimingDetector().Visualizer()) {
visualizer->DumpTextDebuggingRect(aggregator, mapped_visual_rect);
}
}
}
void TextPaintTimingDetector::StopRecordingLargestTextPaint() {
records_manager_.CleanUpLargestTextPaint();
}
void TextPaintTimingDetector::ReportLargestIgnoredText() {
records_manager_.ReportLargestIgnoredText();
}
void TextPaintTimingDetector::Trace(Visitor* visitor) const {
visitor->Trace(records_manager_);
visitor->Trace(frame_view_);
visitor->Trace(callback_manager_);
}
LargestTextPaintManager::LargestTextPaintManager(
LocalFrameView* frame_view,
PaintTimingDetector* paint_timing_detector)
: size_ordered_set_(&LargeTextFirst),
frame_view_(frame_view),
paint_timing_detector_(paint_timing_detector) {}
void LargestTextPaintManager::MaybeUpdateLargestIgnoredText(
const LayoutObject& object,
const uint64_t& size,
const IntRect& frame_visual_rect,
const FloatRect& root_visual_rect) {
if (size &&
(!largest_ignored_text_ || size > largest_ignored_text_->first_size)) {
Node* node = object.GetNode();
DCHECK(node);
DOMNodeId node_id = DOMNodeIds::IdForNode(node);
largest_ignored_text_ = std::make_unique<TextRecord>(
node_id, size, FloatRect(), frame_visual_rect, root_visual_rect);
}
}
void LargestTextPaintManager::Trace(Visitor* visitor) const {
visitor->Trace(frame_view_);
visitor->Trace(paint_timing_detector_);
}
void TextRecordsManager::RemoveVisibleRecord(const LayoutObject& object) {
DCHECK(visible_objects_.Contains(&object));
if (ltp_manager_) {
ltp_manager_->RemoveVisibleRecord(
visible_objects_.at(&object)->AsWeakPtr());
}
visible_objects_.erase(&object);
// We don't need to remove elements in |texts_queued_for_paint_time_| and
// |cached_largest_paint_candidate_| as they are weak ptr.
}
void TextRecordsManager::CleanUpLargestTextPaint() {
ltp_manager_.Clear();
}
void TextRecordsManager::RemoveInvisibleRecord(const LayoutObject& object) {
DCHECK(invisible_objects_.Contains(&object));
invisible_objects_.erase(&object);
}
void TextRecordsManager::AssignPaintTimeToQueuedRecords(
const base::TimeTicks& timestamp) {
// If the number of TextRecords to be processed is 0, it means they have been
// consumed in a callback earlier than this one. That violates the assumption
// that only one or zero callback will be called after one OnPaintFinished.
DCHECK_GT(texts_queued_for_paint_time_.size() +
size_zero_texts_queued_for_paint_time_.size(),
0UL);
bool can_report_element_timing =
text_element_timing_ ? text_element_timing_->CanReportElements() : false;
for (auto iterator = texts_queued_for_paint_time_.begin();
iterator != texts_queued_for_paint_time_.end(); ++iterator) {
// The record may have been removed between the callback registration and
// invoking.
base::WeakPtr<TextRecord>& record = *iterator;
if (!record) {
texts_queued_for_paint_time_.erase(iterator);
continue;
}
DCHECK_EQ(record->paint_time, base::TimeTicks());
record->paint_time = timestamp;
if (can_report_element_timing)
text_element_timing_->OnTextObjectPainted(*record);
}
if (can_report_element_timing) {
for (const auto& record : size_zero_texts_queued_for_paint_time_) {
record->paint_time = timestamp;
text_element_timing_->OnTextObjectPainted(*record);
}
}
texts_queued_for_paint_time_.clear();
size_zero_texts_queued_for_paint_time_.clear();
if (ltp_manager_)
ltp_manager_->SetCachedResultInvalidated(true);
}
void TextRecordsManager::RecordVisibleObject(
const LayoutObject& object,
const uint64_t& visual_size,
const FloatRect& element_timing_rect,
const IntRect& frame_visual_rect,
const FloatRect& root_visual_rect) {
DCHECK_GT(visual_size, 0u);
Node* node = object.GetNode();
DCHECK(node);
DOMNodeId node_id = DOMNodeIds::IdForNode(node);
DCHECK_NE(node_id, kInvalidDOMNodeId);
std::unique_ptr<TextRecord> record =
std::make_unique<TextRecord>(node_id, visual_size, element_timing_rect,
frame_visual_rect, root_visual_rect);
base::WeakPtr<TextRecord> record_weak_ptr = record->AsWeakPtr();
if (ltp_manager_)
ltp_manager_->InsertRecord(record_weak_ptr);
QueueToMeasurePaintTime(record_weak_ptr);
visible_objects_.insert(&object, std::move(record));
}
void TextRecordsManager::RecordInvisibleObject(const LayoutObject& object) {
invisible_objects_.insert(&object);
Node* node = object.GetNode();
DCHECK(node);
if (!TextElementTiming::NeededForElementTiming(*node))
return;
DOMNodeId node_id = DOMNodeIds::IdForNode(node);
DCHECK_NE(node_id, kInvalidDOMNodeId);
// Since it is invisible, the record will have a size of 0 and an empty rect.
std::unique_ptr<TextRecord> record = std::make_unique<TextRecord>(
node_id, 0, FloatRect(), IntRect(), FloatRect());
size_zero_texts_queued_for_paint_time_.push_back(std::move(record));
}
void TextRecordsManager::ReportLargestIgnoredText() {
if (!ltp_manager_)
return;
std::unique_ptr<TextRecord> record = ltp_manager_->PopLargestIgnoredText();
if (!record)
return;
Node* node = DOMNodeIds::NodeForId(record->node_id);
// If the content has been removed, abort. It was never visible.
if (!node || !node->GetLayoutObject())
return;
base::WeakPtr<TextRecord> record_weak_ptr = record->AsWeakPtr();
ltp_manager_->InsertRecord(record_weak_ptr);
QueueToMeasurePaintTime(record_weak_ptr);
visible_objects_.insert(node->GetLayoutObject(), std::move(record));
}
base::WeakPtr<TextRecord> LargestTextPaintManager::FindLargestPaintCandidate() {
if (!is_result_invalidated_ && cached_largest_paint_candidate_)
return cached_largest_paint_candidate_;
base::WeakPtr<TextRecord> new_largest_paint_candidate = nullptr;
for (const auto& text_record : size_ordered_set_) {
DCHECK(text_record);
if (text_record->paint_time.is_null())
continue;
new_largest_paint_candidate = text_record;
break;
}
cached_largest_paint_candidate_ = new_largest_paint_candidate;
is_result_invalidated_ = false;
return new_largest_paint_candidate;
}
TextRecordsManager::TextRecordsManager(
LocalFrameView* frame_view,
PaintTimingDetector* paint_timing_detector)
: ltp_manager_(MakeGarbageCollected<LargestTextPaintManager>(
frame_view,
paint_timing_detector)) {}
void TextRecordsManager::Trace(Visitor* visitor) const {
visitor->Trace(text_element_timing_);
visitor->Trace(ltp_manager_);
}
} // namespace blink