Commit 651f631c authored by Nicolás Peña Moreno's avatar Nicolás Peña Moreno Committed by Commit Bot

[LCP] Ignore paints with opacity 0

This changes the opacity 0 paints that are ignored by the LCP algorithm.
Before, paints that would later be composited were not ignored, which
resulted in an inaccurate metric due to detecting LCP too early. After
this change, even will-change opacity paints are ignored, which could
result in elements not becoming candidates because they are never
repainted. In the special case where documentElement changes its
opacity, we consider the largest content that becomes visible as a valid
LCP candidate.

In other words this CL does the following 3 changes:
1) Ignores all LCP signals under style.opacity: 0, as before styles with
'will-change: opacity' were not ignored.
2) Saves off the LCP for ignored LCP signals if they are under
non-nested style.opacity = 0.
3.) If the document opacity style changes from opacity: 0 to non-zero,
we record the saved off LCP signal.

Bug: 1092473
Change-Id: I2a970a34fdb1db6c7c746060c286077553b816a6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2316788
Commit-Queue: Nicolás Peña Moreno <npm@chromium.org>
Reviewed-by: default avatarXianzhu Wang <wangxianzhu@chromium.org>
Reviewed-by: default avatarPhilip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#794689}
parent 1fda3ccb
...@@ -2447,6 +2447,11 @@ void LayoutObject::StyleWillChange(StyleDifference diff, ...@@ -2447,6 +2447,11 @@ void LayoutObject::StyleWillChange(StyleDifference diff,
} }
MarkEffectiveAllowedTouchActionChanged(); MarkEffectiveAllowedTouchActionChanged();
} }
if (is_document_element && style_ && style_->Opacity() == 0.0f &&
new_style.Opacity() != 0.0f) {
if (LocalFrameView* frame_view = GetFrameView())
frame_view->GetPaintTimingDetector().ReportIgnoredContent();
}
} }
void LayoutObject::ClearBaseComputedStyle() { void LayoutObject::ClearBaseComputedStyle() {
......
...@@ -74,6 +74,7 @@ ...@@ -74,6 +74,7 @@
#include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h" #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h" #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
#include "third_party/blink/renderer/platform/graphics/paint/ignore_paint_timing_scope.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h" #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
...@@ -1710,11 +1711,34 @@ void CompositedLayerMapping::DoPaintTask( ...@@ -1710,11 +1711,34 @@ void CompositedLayerMapping::DoPaintTask(
float device_scale_factor = blink::DeviceScaleFactorDeprecated( float device_scale_factor = blink::DeviceScaleFactorDeprecated(
paint_info.paint_layer->GetLayoutObject().GetFrame()); paint_info.paint_layer->GetLayoutObject().GetFrame());
context.SetDeviceScaleFactor(device_scale_factor); context.SetDeviceScaleFactor(device_scale_factor);
Settings* settings = GetLayoutObject().GetFrame()->GetSettings(); Settings* settings = GetLayoutObject().GetFrame()->GetSettings();
context.SetDarkMode( context.SetDarkMode(
BuildDarkModeSettings(*settings, *GetLayoutObject().View())); BuildDarkModeSettings(*settings, *GetLayoutObject().View()));
// As a composited layer may be painted directly, we need to traverse the
// effect tree starting from the current node all the way up through the
// parents to determine which effects are opacity 0, for the purposes of
// correctly computing paint metrics such as First Contentful Paint and
// Largest Contentful Paint. For the latter we special-case the nodes where
// the opacity:0 depth is 1, so we need to only compute up to the first two
// opacity:0 effects in here and can ignore the rest.
base::Optional<IgnorePaintTimingScope> ignore_paint_timing_scope;
int num_ignores = 0;
DCHECK_EQ(IgnorePaintTimingScope::IgnoreDepth(), 0);
for (const auto* effect_node = &paint_info.paint_layer->GetLayoutObject()
.FirstFragment()
.PreEffect()
.Unalias();
effect_node && num_ignores < 2;
effect_node = effect_node->UnaliasedParent()) {
if (effect_node->Opacity() == 0.0f) {
if (!ignore_paint_timing_scope)
ignore_paint_timing_scope.emplace();
IgnorePaintTimingScope::IncrementIgnoreDepth();
++num_ignores;
}
}
if (paint_info.paint_layer->GetCompositingState() != if (paint_info.paint_layer->GetCompositingState() !=
kPaintsIntoGroupedBacking) { kPaintsIntoGroupedBacking) {
// FIXME: GraphicsLayers need a way to split for multicol. // FIXME: GraphicsLayers need a way to split for multicol.
......
...@@ -226,9 +226,23 @@ void ImagePaintTimingDetector::RecordImage( ...@@ -226,9 +226,23 @@ void ImagePaintTimingDetector::RecordImage(
return; return;
RecordId record_id = std::make_pair(&object, &cached_image); RecordId record_id = std::make_pair(&object, &cached_image);
bool is_recored_visible_image = bool is_recorded_visible_image =
records_manager_.IsRecordedVisibleImage(record_id); records_manager_.IsRecordedVisibleImage(record_id);
if (is_recored_visible_image && if (int depth = IgnorePaintTimingScope::IgnoreDepth()) {
// Record the largest loaded image that is hidden due to documentElement
// being invisible but by no other reason (i.e. IgnoreDepth() needs to be
// 1).
if (depth == 1 && IgnorePaintTimingScope::IsDocumentElementInvisible() &&
!is_recorded_visible_image && cached_image.IsLoaded()) {
uint64_t rect_size = ComputeImageRectSize(image_border, intrinsic_size,
current_paint_chunk_properties,
object, cached_image);
records_manager_.MaybeUpdateLargestIgnoredImage(record_id, rect_size);
}
return;
}
if (is_recorded_visible_image &&
!records_manager_.IsVisibleImageLoaded(record_id) && !records_manager_.IsVisibleImageLoaded(record_id) &&
cached_image.IsLoaded()) { cached_image.IsLoaded()) {
records_manager_.OnImageLoaded(record_id, frame_index_, style_image); records_manager_.OnImageLoaded(record_id, frame_index_, style_image);
...@@ -244,13 +258,33 @@ void ImagePaintTimingDetector::RecordImage( ...@@ -244,13 +258,33 @@ void ImagePaintTimingDetector::RecordImage(
return; return;
} }
if (is_recored_visible_image || !is_recording_) if (is_recorded_visible_image || !is_recording_)
return; return;
// Before the image resource starts loading, <img> has no size info. We wait // Before the image resource starts loading, <img> has no size info. We wait
// until the size is known. // until the size is known.
if (image_border.IsEmpty()) if (image_border.IsEmpty())
return; return;
uint64_t rect_size = ComputeImageRectSize(image_border, intrinsic_size,
current_paint_chunk_properties,
object, cached_image);
if (rect_size == 0) {
records_manager_.RecordInvisible(object);
} else {
records_manager_.RecordVisible(record_id, rect_size);
if (cached_image.IsLoaded()) {
records_manager_.OnImageLoaded(record_id, frame_index_, style_image);
need_update_timing_at_frame_end_ = true;
}
}
}
uint64_t ImagePaintTimingDetector::ComputeImageRectSize(
const IntRect& image_border,
const IntSize& intrinsic_size,
const PropertyTreeStateOrAlias& current_paint_chunk_properties,
const LayoutObject& object,
const ImageResourceContent& cached_image) {
FloatRect mapped_visual_rect = FloatRect mapped_visual_rect =
frame_view_->GetPaintTimingDetector().CalculateVisualRect( frame_view_->GetPaintTimingDetector().CalculateVisualRect(
image_border, current_paint_chunk_properties); image_border, current_paint_chunk_properties);
...@@ -267,15 +301,7 @@ void ImagePaintTimingDetector::RecordImage( ...@@ -267,15 +301,7 @@ void ImagePaintTimingDetector::RecordImage(
rect_size = DownScaleIfIntrinsicSizeIsSmaller( rect_size = DownScaleIfIntrinsicSizeIsSmaller(
rect_size, intrinsic_size.Area(), rect_size, intrinsic_size.Area(),
float_visual_rect.width * float_visual_rect.height); float_visual_rect.width * float_visual_rect.height);
if (rect_size == 0) { return rect_size;
records_manager_.RecordInvisible(object);
} else {
records_manager_.RecordVisible(record_id, rect_size);
if (cached_image.IsLoaded()) {
records_manager_.OnImageLoaded(record_id, frame_index_, style_image);
need_update_timing_at_frame_end_ = true;
}
}
} }
void ImagePaintTimingDetector::NotifyImageFinished( void ImagePaintTimingDetector::NotifyImageFinished(
...@@ -285,6 +311,11 @@ void ImagePaintTimingDetector::NotifyImageFinished( ...@@ -285,6 +311,11 @@ void ImagePaintTimingDetector::NotifyImageFinished(
records_manager_.NotifyImageFinished(record_id); records_manager_.NotifyImageFinished(record_id);
} }
void ImagePaintTimingDetector::ReportLargestIgnoredImage() {
need_update_timing_at_frame_end_ = true;
records_manager_.ReportLargestIgnoredImage(frame_index_);
}
ImageRecordsManager::ImageRecordsManager(LocalFrameView* frame_view) ImageRecordsManager::ImageRecordsManager(LocalFrameView* frame_view)
: size_ordered_set_(&LargeImageFirst), frame_view_(frame_view) {} : size_ordered_set_(&LargeImageFirst), frame_view_(frame_view) {}
...@@ -306,6 +337,25 @@ void ImageRecordsManager::OnImageLoaded(const RecordId& record_id, ...@@ -306,6 +337,25 @@ void ImageRecordsManager::OnImageLoaded(const RecordId& record_id,
OnImageLoadedInternal(record, current_frame_index); OnImageLoadedInternal(record, current_frame_index);
} }
void ImageRecordsManager::ReportLargestIgnoredImage(
unsigned current_frame_index) {
if (!largest_ignored_image_)
return;
base::WeakPtr<ImageRecord> record = largest_ignored_image_->AsWeakPtr();
Node* node = DOMNodeIds::NodeForId(largest_ignored_image_->node_id);
if (!node || !node->GetLayoutObject() ||
!largest_ignored_image_->cached_image) {
// The image has been removed, so we have no content to report.
largest_ignored_image_.reset();
return;
}
RecordId record_id = std::make_pair(node->GetLayoutObject(),
largest_ignored_image_->cached_image);
size_ordered_set_.insert(record);
visible_images_.insert(record_id, std::move(largest_ignored_image_));
OnImageLoadedInternal(record, current_frame_index);
}
void ImageRecordsManager::OnImageLoadedInternal( void ImageRecordsManager::OnImageLoadedInternal(
base::WeakPtr<ImageRecord>& record, base::WeakPtr<ImageRecord>& record,
unsigned current_frame_index) { unsigned current_frame_index) {
...@@ -313,6 +363,17 @@ void ImageRecordsManager::OnImageLoadedInternal( ...@@ -313,6 +363,17 @@ void ImageRecordsManager::OnImageLoadedInternal(
QueueToMeasurePaintTime(record, current_frame_index); QueueToMeasurePaintTime(record, current_frame_index);
} }
void ImageRecordsManager::MaybeUpdateLargestIgnoredImage(
const RecordId& record_id,
const uint64_t& visual_size) {
if (visual_size && (!largest_ignored_image_ ||
visual_size > largest_ignored_image_->first_size)) {
largest_ignored_image_ =
CreateImageRecord(*record_id.first, record_id.second, visual_size);
largest_ignored_image_->load_time = base::TimeTicks::Now();
}
}
void ImageRecordsManager::RecordVisible(const RecordId& record_id, void ImageRecordsManager::RecordVisible(const RecordId& record_id,
const uint64_t& visual_size) { const uint64_t& visual_size) {
std::unique_ptr<ImageRecord> record = std::unique_ptr<ImageRecord> record =
......
...@@ -134,6 +134,13 @@ class CORE_EXPORT ImageRecordsManager { ...@@ -134,6 +134,13 @@ class CORE_EXPORT ImageRecordsManager {
void OnImageLoadedInternal(base::WeakPtr<ImageRecord>&, void OnImageLoadedInternal(base::WeakPtr<ImageRecord>&,
unsigned current_frame_index); unsigned current_frame_index);
// Receives a candidate image painted under opacity 0 but without nested
// opacity. May update |largest_ignored_image_| if the new candidate has a
// larger size.
void MaybeUpdateLargestIgnoredImage(const RecordId&,
const uint64_t& visual_size);
void ReportLargestIgnoredImage(unsigned current_frame_index);
// Compare the last frame index in queue with the last frame index that has // Compare the last frame index in queue with the last frame index that has
// registered for assigning paint time. // registered for assigning paint time.
inline bool HasUnregisteredRecordsInQueue( inline bool HasUnregisteredRecordsInQueue(
...@@ -202,6 +209,14 @@ class CORE_EXPORT ImageRecordsManager { ...@@ -202,6 +209,14 @@ class CORE_EXPORT ImageRecordsManager {
uint64_t largest_removed_image_size_ = 0u; uint64_t largest_removed_image_size_ = 0u;
base::TimeTicks largest_removed_image_paint_time_; base::TimeTicks largest_removed_image_paint_time_;
// Image paints are ignored when they (or an ancestor) have opacity 0. This
// can be a problem later on if the opacity changes to nonzero but this change
// is composited. We solve this for the special case of documentElement by
// storing a record for the largest ignored image without nested opacity. We
// consider this an LCP candidate when the documentElement's opacity changes
// from zero to nonzero.
std::unique_ptr<ImageRecord> largest_ignored_image_;
DISALLOW_COPY_AND_ASSIGN(ImageRecordsManager); DISALLOW_COPY_AND_ASSIGN(ImageRecordsManager);
}; };
...@@ -263,6 +278,11 @@ class CORE_EXPORT ImagePaintTimingDetector final ...@@ -263,6 +278,11 @@ class CORE_EXPORT ImagePaintTimingDetector final
// Return the candidate. // Return the candidate.
ImageRecord* UpdateCandidate(); ImageRecord* UpdateCandidate();
// Called when documentElement changes from zero to nonzero opacity. Makes the
// largest image that was hidden due to this a Largest Contentful Paint
// candidate.
void ReportLargestIgnoredImage();
void Trace(Visitor*) const; void Trace(Visitor*) const;
private: private:
...@@ -272,6 +292,11 @@ class CORE_EXPORT ImagePaintTimingDetector final ...@@ -272,6 +292,11 @@ class CORE_EXPORT ImagePaintTimingDetector final
void RegisterNotifySwapTime(); void RegisterNotifySwapTime();
void ReportCandidateToTrace(ImageRecord&); void ReportCandidateToTrace(ImageRecord&);
void ReportNoCandidateToTrace(); void ReportNoCandidateToTrace();
uint64_t ComputeImageRectSize(const IntRect&,
const IntSize&,
const PropertyTreeStateOrAlias&,
const LayoutObject&,
const ImageResourceContent&);
// Used to find the last candidate. // Used to find the last candidate.
unsigned count_candidates_ = 0; unsigned count_candidates_ = 0;
......
...@@ -1183,4 +1183,55 @@ TEST_P(ImagePaintTimingDetectorTest, ...@@ -1183,4 +1183,55 @@ TEST_P(ImagePaintTimingDetectorTest,
EXPECT_EQ(record->first_size, 25u); EXPECT_EQ(record->first_size, 25u);
} }
TEST_P(ImagePaintTimingDetectorTest, OpacityZeroHTML) {
SetBodyInnerHTML(R"HTML(
<style>
:root {
opacity: 0;
will-change: opacity;
}
</style>
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 0u);
// Change the opacity of documentElement, now the img should be a candidate.
GetDocument().documentElement()->setAttribute(html_names::kStyleAttr,
"opacity: 1");
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 1u);
EXPECT_EQ(GetPerformanceTiming().LargestImagePaintSize(), 25u);
EXPECT_GT(GetPerformanceTiming().LargestImagePaint(), 0u);
EXPECT_EQ(GetPerformanceTiming().LargestImagePaintSize(),
GetPerformanceTiming().ExperimentalLargestImagePaintSize());
EXPECT_EQ(GetPerformanceTiming().LargestImagePaint(),
GetPerformanceTiming().ExperimentalLargestImagePaint());
}
TEST_P(ImagePaintTimingDetectorTest, OpacityZeroHTML2) {
SetBodyInnerHTML(R"HTML(
<style>
#target {
opacity: 0;
}
</style>
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 0u);
GetDocument().documentElement()->setAttribute(html_names::kStyleAttr,
"opacity: 0");
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 0u);
GetDocument().documentElement()->setAttribute(html_names::kStyleAttr,
"opacity: 1");
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 0u);
}
} // namespace blink } // namespace blink
...@@ -344,9 +344,22 @@ PaintResult PaintLayerPainter::PaintLayerContents( ...@@ -344,9 +344,22 @@ PaintResult PaintLayerPainter::PaintLayerContents(
if (selection_drag_image_only && !paint_layer_.GetLayoutObject().IsSelected()) if (selection_drag_image_only && !paint_layer_.GetLayoutObject().IsSelected())
return result; return result;
base::Optional<IgnorePaintTimingScope> ignore_paint_timing; IgnorePaintTimingScope ignore_paint_timing;
if (PaintedOutputInvisible(paint_layer_.GetLayoutObject().StyleRef())) if (paint_layer_.GetLayoutObject().StyleRef().Opacity() == 0.0f) {
ignore_paint_timing.emplace(); IgnorePaintTimingScope::IncrementIgnoreDepth();
}
// Explicitly compute opacity of documentElement, as it is special-cased in
// Largest Contentful Paint.
bool is_document_element_invisible = false;
if (const auto* document_element =
paint_layer_.GetLayoutObject().GetDocument().documentElement()) {
if (document_element->GetLayoutObject() &&
document_element->GetLayoutObject()->StyleRef().Opacity() == 0.0f) {
is_document_element_invisible = true;
}
}
IgnorePaintTimingScope::SetIsDocumentElementInvisible(
is_document_element_invisible);
PaintLayerFlags paint_flags = paint_flags_arg; PaintLayerFlags paint_flags = paint_flags_arg;
PaintLayerPaintingInfo painting_info = painting_info_arg; PaintLayerPaintingInfo painting_info = painting_info_arg;
......
...@@ -110,9 +110,6 @@ void PaintTimingDetector::NotifyBackgroundImagePaint( ...@@ -110,9 +110,6 @@ void PaintTimingDetector::NotifyBackgroundImagePaint(
const StyleFetchedImage* style_image, const StyleFetchedImage* style_image,
const PropertyTreeStateOrAlias& current_paint_chunk_properties, const PropertyTreeStateOrAlias& current_paint_chunk_properties,
const IntRect& image_border) { const IntRect& image_border) {
if (IgnorePaintTimingScope::ShouldIgnore())
return;
DCHECK(image); DCHECK(image);
DCHECK(style_image->CachedImage()); DCHECK(style_image->CachedImage());
if (!node) if (!node)
...@@ -140,9 +137,6 @@ void PaintTimingDetector::NotifyImagePaint( ...@@ -140,9 +137,6 @@ void PaintTimingDetector::NotifyImagePaint(
const ImageResourceContent* cached_image, const ImageResourceContent* cached_image,
const PropertyTreeStateOrAlias& current_paint_chunk_properties, const PropertyTreeStateOrAlias& current_paint_chunk_properties,
const IntRect& image_border) { const IntRect& image_border) {
if (IgnorePaintTimingScope::ShouldIgnore())
return;
LocalFrameView* frame_view = object.GetFrameView(); LocalFrameView* frame_view = object.GetFrameView();
if (!frame_view) if (!frame_view)
return; return;
...@@ -385,15 +379,23 @@ void PaintTimingDetector::UpdateLargestContentfulPaintCandidate() { ...@@ -385,15 +379,23 @@ void PaintTimingDetector::UpdateLargestContentfulPaintCandidate() {
largest_image_record); largest_image_record);
} }
void PaintTimingDetector::ReportIgnoredContent() {
if (auto* text_timing_detector = GetTextPaintTimingDetector()) {
text_paint_timing_detector_->ReportLargestIgnoredText();
}
if (auto* image_timing_detector = GetImagePaintTimingDetector()) {
image_timing_detector->ReportLargestIgnoredImage();
}
}
ScopedPaintTimingDetectorBlockPaintHook* ScopedPaintTimingDetectorBlockPaintHook*
ScopedPaintTimingDetectorBlockPaintHook::top_ = nullptr; ScopedPaintTimingDetectorBlockPaintHook::top_ = nullptr;
void ScopedPaintTimingDetectorBlockPaintHook::EmplaceIfNeeded( void ScopedPaintTimingDetectorBlockPaintHook::EmplaceIfNeeded(
const LayoutBoxModelObject& aggregator, const LayoutBoxModelObject& aggregator,
const PropertyTreeStateOrAlias& property_tree_state) { const PropertyTreeStateOrAlias& property_tree_state) {
if (IgnorePaintTimingScope::ShouldIgnore()) if (IgnorePaintTimingScope::IgnoreDepth() > 1)
return; return;
// |reset_top_| is unset when |aggregator| is anonymous so that each // |reset_top_| is unset when |aggregator| is anonymous so that each
// aggregation corresponds to an element. See crbug.com/988593. When set, // aggregation corresponds to an element. See crbug.com/988593. When set,
// |top_| becomes |this|, and |top_| is restored to the previous value when // |top_| becomes |this|, and |top_| is restored to the previous value when
......
...@@ -205,6 +205,10 @@ class CORE_EXPORT PaintTimingDetector ...@@ -205,6 +205,10 @@ class CORE_EXPORT PaintTimingDetector
void UpdateLargestContentfulPaintCandidate(); void UpdateLargestContentfulPaintCandidate();
// Reports the largest image and text candidates painted under non-nested 0
// opacity layer.
void ReportIgnoredContent();
base::Optional<PaintTimingVisualizer>& Visualizer() { return visualizer_; } base::Optional<PaintTimingVisualizer>& Visualizer() { return visualizer_; }
void Trace(Visitor* visitor) const; void Trace(Visitor* visitor) const;
...@@ -308,7 +312,7 @@ class ScopedPaintTimingDetectorBlockPaintHook { ...@@ -308,7 +312,7 @@ class ScopedPaintTimingDetectorBlockPaintHook {
// static // static
inline void PaintTimingDetector::NotifyTextPaint( inline void PaintTimingDetector::NotifyTextPaint(
const IntRect& text_visual_rect) { const IntRect& text_visual_rect) {
if (IgnorePaintTimingScope::ShouldIgnore()) if (IgnorePaintTimingScope::IgnoreDepth() > 1)
return; return;
ScopedPaintTimingDetectorBlockPaintHook::AggregateTextPaint(text_visual_rect); ScopedPaintTimingDetectorBlockPaintHook::AggregateTextPaint(text_visual_rect);
} }
......
...@@ -177,6 +177,17 @@ void TextPaintTimingDetector::RecordAggregatedText( ...@@ -177,6 +177,17 @@ void TextPaintTimingDetector::RecordAggregatedText(
frame_view_->GetPaintTimingDetector().CalculateVisualRect( frame_view_->GetPaintTimingDetector().CalculateVisualRect(
aggregated_visual_rect, property_tree_state); aggregated_visual_rect, property_tree_state);
uint64_t aggregated_size = mapped_visual_rect.Size().Area(); 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);
}
return;
}
if (aggregated_size == 0) { if (aggregated_size == 0) {
records_manager_.RecordInvisibleObject(aggregator); records_manager_.RecordInvisibleObject(aggregator);
...@@ -197,6 +208,10 @@ void TextPaintTimingDetector::StopRecordingLargestTextPaint() { ...@@ -197,6 +208,10 @@ void TextPaintTimingDetector::StopRecordingLargestTextPaint() {
records_manager_.CleanUpLargestTextPaint(); records_manager_.CleanUpLargestTextPaint();
} }
void TextPaintTimingDetector::ReportLargestIgnoredText() {
records_manager_.ReportLargestIgnoredText();
}
void TextPaintTimingDetector::Trace(Visitor* visitor) const { void TextPaintTimingDetector::Trace(Visitor* visitor) const {
visitor->Trace(records_manager_); visitor->Trace(records_manager_);
visitor->Trace(frame_view_); visitor->Trace(frame_view_);
...@@ -210,6 +225,19 @@ LargestTextPaintManager::LargestTextPaintManager( ...@@ -210,6 +225,19 @@ LargestTextPaintManager::LargestTextPaintManager(
frame_view_(frame_view), frame_view_(frame_view),
paint_timing_detector_(paint_timing_detector) {} paint_timing_detector_(paint_timing_detector) {}
void LargestTextPaintManager::MaybeUpdateLargestIgnoredText(
const LayoutObject& object,
const uint64_t& size) {
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());
}
}
void LargestTextPaintManager::Trace(Visitor* visitor) const { void LargestTextPaintManager::Trace(Visitor* visitor) const {
visitor->Trace(frame_view_); visitor->Trace(frame_view_);
visitor->Trace(paint_timing_detector_); visitor->Trace(paint_timing_detector_);
...@@ -305,6 +333,23 @@ void TextRecordsManager::RecordInvisibleObject(const LayoutObject& object) { ...@@ -305,6 +333,23 @@ void TextRecordsManager::RecordInvisibleObject(const LayoutObject& object) {
size_zero_texts_queued_for_paint_time_.push_back(std::move(record)); 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() { base::WeakPtr<TextRecord> LargestTextPaintManager::FindLargestPaintCandidate() {
if (!is_result_invalidated_ && cached_largest_paint_candidate_) if (!is_result_invalidated_ && cached_largest_paint_candidate_)
return cached_largest_paint_candidate_; return cached_largest_paint_candidate_;
......
...@@ -80,6 +80,11 @@ class CORE_EXPORT LargestTextPaintManager { ...@@ -80,6 +80,11 @@ class CORE_EXPORT LargestTextPaintManager {
SetCachedResultInvalidated(true); SetCachedResultInvalidated(true);
} }
void MaybeUpdateLargestIgnoredText(const LayoutObject&, const uint64_t&);
std::unique_ptr<TextRecord> PopLargestIgnoredText() {
return std::move(largest_ignored_text_);
}
void Trace(Visitor*) const; void Trace(Visitor*) const;
private: private:
...@@ -95,6 +100,14 @@ class CORE_EXPORT LargestTextPaintManager { ...@@ -95,6 +100,14 @@ class CORE_EXPORT LargestTextPaintManager {
bool is_result_invalidated_ = false; bool is_result_invalidated_ = false;
unsigned count_candidates_ = 0; unsigned count_candidates_ = 0;
// Text paints are ignored when they (or an ancestor) have opacity 0. This can
// be a problem later on if the opacity changes to nonzero but this change is
// composited. We solve this for the special case of documentElement by
// storing a record for the largest ignored text without nested opacity. We
// consider this an LCP candidate when the documentElement's opacity changes
// from zero to nonzero.
std::unique_ptr<TextRecord> largest_ignored_text_;
Member<const LocalFrameView> frame_view_; Member<const LocalFrameView> frame_view_;
Member<PaintTimingDetector> paint_timing_detector_; Member<PaintTimingDetector> paint_timing_detector_;
DISALLOW_COPY_AND_ASSIGN(LargestTextPaintManager); DISALLOW_COPY_AND_ASSIGN(LargestTextPaintManager);
...@@ -144,6 +157,19 @@ class CORE_EXPORT TextRecordsManager { ...@@ -144,6 +157,19 @@ class CORE_EXPORT TextRecordsManager {
return ltp_manager_->UpdateCandidate(); return ltp_manager_->UpdateCandidate();
} }
// Receives a candidate text painted under opacity 0 but without nested
// opacity. May update |largest_ignored_text_| if the new candidate has a
// larger size.
void MaybeUpdateLargestIgnoredText(const LayoutObject& object,
const uint64_t& size) {
DCHECK(ltp_manager_);
ltp_manager_->MaybeUpdateLargestIgnoredText(object, size);
}
// Called when documentElement changes from zero to nonzero opacity. Makes the
// largest text that was hidden due to this a Largest Contentful Paint
// candidate.
void ReportLargestIgnoredText();
inline bool IsRecordingLargestTextPaint() const { inline bool IsRecordingLargestTextPaint() const {
return ltp_manager_.has_value(); return ltp_manager_.has_value();
} }
...@@ -215,6 +241,7 @@ class CORE_EXPORT TextPaintTimingDetector final ...@@ -215,6 +241,7 @@ class CORE_EXPORT TextPaintTimingDetector final
inline base::WeakPtr<TextRecord> UpdateCandidate() { inline base::WeakPtr<TextRecord> UpdateCandidate() {
return records_manager_.UpdateCandidate(); return records_manager_.UpdateCandidate();
} }
void ReportLargestIgnoredText();
void ReportSwapTime(base::TimeTicks timestamp); void ReportSwapTime(base::TimeTicks timestamp);
void Trace(Visitor*) const; void Trace(Visitor*) const;
......
...@@ -848,4 +848,49 @@ TEST_F(TextPaintTimingDetectorTest, VisibleTextAfterUserScroll) { ...@@ -848,4 +848,49 @@ TEST_F(TextPaintTimingDetectorTest, VisibleTextAfterUserScroll) {
EXPECT_EQ(CountVisibleTexts(), 1u); EXPECT_EQ(CountVisibleTexts(), 1u);
} }
TEST_F(TextPaintTimingDetectorTest, OpacityZeroHTML) {
SetBodyInnerHTML(R"HTML(
<style>
:root {
opacity: 0;
will-change: opacity;
}
</style>
<div>Text</div>
)HTML");
UpdateAllLifecyclePhasesAndSimulateSwapTime();
EXPECT_EQ(CountVisibleTexts(), 0u);
// Change the opacity of documentElement, now the img should be a candidate.
GetDocument().documentElement()->setAttribute(html_names::kStyleAttr,
"opacity: 1");
UpdateAllLifecyclePhasesAndSimulateSwapTime();
EXPECT_EQ(CountVisibleTexts(), 1u);
EXPECT_TRUE(TextRecordOfLargestTextPaint());
}
TEST_F(TextPaintTimingDetectorTest, OpacityZeroHTML2) {
SetBodyInnerHTML(R"HTML(
<style>
#target {
opacity: 0;
will-change: opacity;
}
</style>
<div id="target">Text</div>
)HTML");
UpdateAllLifecyclePhasesAndSimulateSwapTime();
EXPECT_EQ(CountVisibleTexts(), 0u);
GetDocument().documentElement()->setAttribute(html_names::kStyleAttr,
"opacity: 0");
UpdateAllLifecyclePhasesAndSimulateSwapTime();
EXPECT_EQ(CountVisibleTexts(), 0u);
GetDocument().documentElement()->setAttribute(html_names::kStyleAttr,
"opacity: 1");
UpdateAllLifecyclePhasesAndSimulateSwapTime();
EXPECT_EQ(CountVisibleTexts(), 0u);
}
} // namespace blink } // namespace blink
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
namespace blink { namespace blink {
bool IgnorePaintTimingScope::should_ignore_ = false; int IgnorePaintTimingScope::ignore_depth_ = 0;
bool IgnorePaintTimingScope::is_document_element_invisible_ = false;
} // namespace blink } // namespace blink
...@@ -12,19 +12,34 @@ ...@@ -12,19 +12,34 @@
namespace blink { namespace blink {
// Creates a scope to ignore paint timing, e.g. when we are painting contents // Creates a scope to ignore paint timing, e.g. when we are painting contents
// under opacity:0. // under opacity:0. Currently we store the largest content when the depth is 1
// in order to surface the LCP when the document's opacity changes from 0 to
// nonzero. Care must be taken if changing the conditions under which
// IgnorePaintTimingScope is used in order to ensure correctness.
class PLATFORM_EXPORT IgnorePaintTimingScope { class PLATFORM_EXPORT IgnorePaintTimingScope {
STACK_ALLOCATED(); STACK_ALLOCATED();
public: public:
IgnorePaintTimingScope() : auto_reset_(&should_ignore_, true) {} IgnorePaintTimingScope()
: reset_ignore_depth_(&ignore_depth_, ignore_depth_),
reset_is_document_element_invisible_(&is_document_element_invisible_,
is_document_element_invisible_) {}
~IgnorePaintTimingScope() = default; ~IgnorePaintTimingScope() = default;
static bool ShouldIgnore() { return should_ignore_; } static void SetIsDocumentElementInvisible(bool is_invisible) {
is_document_element_invisible_ = is_invisible;
}
static bool IsDocumentElementInvisible() {
return is_document_element_invisible_;
}
static void IncrementIgnoreDepth() { ++ignore_depth_; }
static int IgnoreDepth() { return ignore_depth_; }
private: private:
base::AutoReset<bool> auto_reset_; base::AutoReset<int> reset_ignore_depth_;
static bool should_ignore_; base::AutoReset<bool> reset_is_document_element_invisible_;
static int ignore_depth_;
static bool is_document_element_invisible_;
}; };
} // namespace blink } // namespace blink
......
...@@ -781,17 +781,17 @@ void PaintController::CheckUnderInvalidation() { ...@@ -781,17 +781,17 @@ void PaintController::CheckUnderInvalidation() {
} }
void PaintController::SetFirstPainted() { void PaintController::SetFirstPainted() {
if (!IgnorePaintTimingScope::ShouldIgnore()) if (!IgnorePaintTimingScope::IgnoreDepth())
frame_first_paints_.back().first_painted = true; frame_first_paints_.back().first_painted = true;
} }
void PaintController::SetTextPainted() { void PaintController::SetTextPainted() {
if (!IgnorePaintTimingScope::ShouldIgnore()) if (!IgnorePaintTimingScope::IgnoreDepth())
frame_first_paints_.back().text_painted = true; frame_first_paints_.back().text_painted = true;
} }
void PaintController::SetImagePainted() { void PaintController::SetImagePainted() {
if (!IgnorePaintTimingScope::ShouldIgnore()) if (!IgnorePaintTimingScope::IgnoreDepth())
frame_first_paints_.back().image_painted = true; frame_first_paints_.back().image_painted = true;
} }
......
...@@ -41,8 +41,6 @@ crbug.com/802915 css3/blending/isolation-should-include-non-local-background.htm ...@@ -41,8 +41,6 @@ crbug.com/802915 css3/blending/isolation-should-include-non-local-background.htm
crbug.com/918155 virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbar-over-child-layer-nested-2.html [ Pass ] crbug.com/918155 virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbar-over-child-layer-nested-2.html [ Pass ]
crbug.com/918155 virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbar-over-child-layer-nested.html [ Pass ] crbug.com/918155 virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbar-over-child-layer-nested.html [ Pass ]
paint/invalidation/compositing/subpixel-offset-scaled-transform-composited.html [ Pass ] paint/invalidation/compositing/subpixel-offset-scaled-transform-composited.html [ Pass ]
crbug.com/957674 external/wpt/largest-contentful-paint/invisible-images-composited-2.html [ Pass ]
crbug.com/957674 virtual/scalefactor200/external/wpt/largest-contentful-paint/invisible-images-composited-2.html [ Pass ]
# With CompositeAfterPaint and LayoutNGFragmentItem enabled # With CompositeAfterPaint and LayoutNGFragmentItem enabled
crbug.com/1103138 paint/invalidation/compositing/float-under-composited-inline.html [ Pass Crash ] crbug.com/1103138 paint/invalidation/compositing/float-under-composited-inline.html [ Pass Crash ]
......
...@@ -5996,7 +5996,6 @@ crbug.com/997669 [ Win ] http/tests/devtools/search/sources-search-scope-in-file ...@@ -5996,7 +5996,6 @@ crbug.com/997669 [ Win ] http/tests/devtools/search/sources-search-scope-in-file
crbug.com/626703 external/wpt/css/css-paint-api/custom-property-animation-on-main-thread.https.html [ Pass Failure ] crbug.com/626703 external/wpt/css/css-paint-api/custom-property-animation-on-main-thread.https.html [ Pass Failure ]
crbug.com/1015130 external/wpt/largest-contentful-paint/first-paint-equals-lcp-text.html [ Failure Pass ] crbug.com/1015130 external/wpt/largest-contentful-paint/first-paint-equals-lcp-text.html [ Failure Pass ]
crbug.com/957674 external/wpt/largest-contentful-paint/invisible-images-composited-2.html [ Failure ]
crbug.com/1000051 media/controls/volume-slider.html [ Failure Timeout Pass ] crbug.com/1000051 media/controls/volume-slider.html [ Failure Timeout Pass ]
......
...@@ -13,13 +13,17 @@ ...@@ -13,13 +13,17 @@
.displayNone { .displayNone {
display: none; display: none;
} }
.composited { .willChangeTransform {
will-change: transform; will-change: transform;
} }
.willChangeOpacity {
will-change: opacity;
}
</style> </style>
<img src='/images/blue.png' class='opacity0 composited' id='opacity0'/> <img src='/images/blue.png' class='opacity0 willChangeTransform' id='opacity0-willChangeTransform'/>
<img src='/images/green.png' class='visibilityHidden composited' id='visibilityHidden'/> <img src='/images/green.png' class='visibilityHidden willChangeTransform' id='visibilityHidden'/>
<img src='/images/red.png' class='displayNone composited' id='displayNone'/> <img src='/images/red.png' class='displayNone willChangeTransform' id='displayNone'/>
<img src='/images/blue.png' class='opacity0 willChangeOpacity' id='opacity0-willChangeOpacity'/>
<div class='opacity0 composited'><img src='/images/yellow.png' id='divOpacity0'/></div> <div class='opacity0 composited'><img src='/images/yellow.png' id='divOpacity0'/></div>
<div class='visibilityHidden composited'><img src='/images/yellow.png' id='divVisibilityHidden'/></div> <div class='visibilityHidden composited'><img src='/images/yellow.png' id='divVisibilityHidden'/></div>
<div class='displayNone composited'><img src='/images/yellow.png' id='divDisplayNone'/></div> <div class='displayNone composited'><img src='/images/yellow.png' id='divDisplayNone'/></div>
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