blob: 8614bf79f0bb1719f6df5e98b64c1d23b0b3bd3d [file] [log] [blame]
// Copyright 2019 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/largest_contentful_paint_calculator.h"
#include "base/test/simple_test_tick_clock.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
#include "third_party/blink/renderer/core/paint/paint_timing_test_helper.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace blink {
class LargestContentfulPaintCalculatorTest : public RenderingTest {
public:
void SetUp() override {
// Advance the clock so we do not assign null TimeTicks.
simulated_clock_.Advance(base::TimeDelta::FromMilliseconds(100));
EnableCompositing();
RenderingTest::SetUp();
mock_text_callback_manager_ =
MakeGarbageCollected<MockPaintTimingCallbackManager>();
GetTextPaintTimingDetector()->ResetCallbackManager(
mock_text_callback_manager_);
mock_image_callback_manager_ =
MakeGarbageCollected<MockPaintTimingCallbackManager>();
GetImagePaintTimingDetector()->ResetCallbackManager(
mock_image_callback_manager_);
trace_event::EnableTracing(TRACE_DISABLED_BY_DEFAULT("loading"));
}
ImagePaintTimingDetector* GetImagePaintTimingDetector() {
return GetFrame()
.View()
->GetPaintTimingDetector()
.GetImagePaintTimingDetector();
}
TextPaintTimingDetector* GetTextPaintTimingDetector() {
return GetFrame()
.View()
->GetPaintTimingDetector()
.GetTextPaintTimingDetector();
}
void SetImage(const char* id, int width, int height) {
To<HTMLImageElement>(GetDocument().getElementById(id))
->SetImageForTest(CreateImageForTest(width, height));
}
ImageResourceContent* CreateImageForTest(int width, int height) {
sk_sp<SkColorSpace> src_rgb_color_space = SkColorSpace::MakeSRGB();
SkImageInfo raster_image_info =
SkImageInfo::MakeN32Premul(width, height, src_rgb_color_space);
sk_sp<SkSurface> surface(SkSurface::MakeRaster(raster_image_info));
sk_sp<SkImage> image = surface->makeImageSnapshot();
ImageResourceContent* original_image_content =
ImageResourceContent::CreateLoaded(
UnacceleratedStaticBitmapImage::Create(image).get());
return original_image_content;
}
uint64_t LargestReportedSize() {
return GetLargestContentfulPaintCalculator()->largest_reported_size_;
}
uint64_t CountCandidates() {
return GetLargestContentfulPaintCalculator()->count_candidates_;
}
void UpdateLargestContentfulPaintCandidate() {
GetFrame()
.View()
->GetPaintTimingDetector()
.UpdateLargestContentfulPaintCandidate();
}
void SimulateContentPresentationPromise() {
mock_text_callback_manager_->InvokePresentationTimeCallback(
simulated_clock_.NowTicks());
mock_image_callback_manager_->InvokePresentationTimeCallback(
simulated_clock_.NowTicks());
// Outside the tests, this is invoked by
// |PaintTimingCallbackManagerImpl::ReportPaintTime|.
UpdateLargestContentfulPaintCandidate();
}
// Outside the tests, the text callback and the image callback are run
// together, as in |SimulateContentPresentationPromise|.
void SimulateImagePresentationPromise() {
mock_image_callback_manager_->InvokePresentationTimeCallback(
simulated_clock_.NowTicks());
// Outside the tests, this is invoked by
// |PaintTimingCallbackManagerImpl::ReportPaintTime|.
UpdateLargestContentfulPaintCandidate();
}
// Outside the tests, the text callback and the image callback are run
// together, as in |SimulateContentPresentationPromise|.
void SimulateTextPresentationPromise() {
mock_text_callback_manager_->InvokePresentationTimeCallback(
simulated_clock_.NowTicks());
// Outside the tests, this is invoked by
// |PaintTimingCallbackManagerImpl::ReportPaintTime|.
UpdateLargestContentfulPaintCandidate();
}
private:
LargestContentfulPaintCalculator* GetLargestContentfulPaintCalculator() {
return GetFrame()
.View()
->GetPaintTimingDetector()
.GetLargestContentfulPaintCalculator();
}
base::SimpleTestTickClock simulated_clock_;
Persistent<MockPaintTimingCallbackManager> mock_text_callback_manager_;
Persistent<MockPaintTimingCallbackManager> mock_image_callback_manager_;
};
TEST_F(LargestContentfulPaintCalculatorTest, SingleImage) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<img id='target'/>
)HTML");
SetImage("target", 100, 150);
UpdateAllLifecyclePhasesForTest();
SimulateImagePresentationPromise();
EXPECT_EQ(LargestReportedSize(), 15000u);
EXPECT_EQ(CountCandidates(), 1u);
}
TEST_F(LargestContentfulPaintCalculatorTest, SingleText) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<p>This is some text</p>
)HTML");
UpdateAllLifecyclePhasesForTest();
SimulateTextPresentationPromise();
EXPECT_GT(LargestReportedSize(), 0u);
EXPECT_EQ(CountCandidates(), 1u);
}
TEST_F(LargestContentfulPaintCalculatorTest, ImageLargerText) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<img id='target'/>
<p>This text should be larger than the image!!!!</p>
)HTML");
SetImage("target", 3, 3);
UpdateAllLifecyclePhasesForTest();
SimulateImagePresentationPromise();
EXPECT_EQ(LargestReportedSize(), 9u);
EXPECT_EQ(CountCandidates(), 1u);
SimulateTextPresentationPromise();
EXPECT_GT(LargestReportedSize(), 9u);
EXPECT_EQ(CountCandidates(), 2u);
}
TEST_F(LargestContentfulPaintCalculatorTest, ImageSmallerText) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<img id='target'/>
<p>.</p>
)HTML");
SetImage("target", 100, 200);
UpdateAllLifecyclePhasesForTest();
SimulateImagePresentationPromise();
EXPECT_EQ(LargestReportedSize(), 20000u);
EXPECT_EQ(CountCandidates(), 1u);
SimulateTextPresentationPromise();
// Text should not be reported, since it is smaller than the image.
EXPECT_EQ(LargestReportedSize(), 20000u);
EXPECT_EQ(CountCandidates(), 1u);
}
TEST_F(LargestContentfulPaintCalculatorTest, TextLargerImage) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<img id='target'/>
<p>.</p>
)HTML");
SetImage("target", 100, 200);
UpdateAllLifecyclePhasesForTest();
SimulateContentPresentationPromise();
EXPECT_EQ(LargestReportedSize(), 20000u);
EXPECT_EQ(CountCandidates(), 1u);
}
TEST_F(LargestContentfulPaintCalculatorTest, TextSmallerImage) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<img id='target'/>
<p>This text should be larger than the image!!!!</p>
)HTML");
SetImage("target", 3, 3);
UpdateAllLifecyclePhasesForTest();
SimulateContentPresentationPromise();
// Image should not be reported, since it is smaller than the text.
EXPECT_GT(LargestReportedSize(), 9u);
EXPECT_EQ(CountCandidates(), 1u);
}
TEST_F(LargestContentfulPaintCalculatorTest, LargestImageRemoved) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<img id='large'/>
<img id='small'/>
<p>Larger than the second image</p>
)HTML");
SetImage("large", 100, 200);
SetImage("small", 3, 3);
UpdateAllLifecyclePhasesForTest();
SimulateImagePresentationPromise();
SimulateTextPresentationPromise();
// Image is larger than the text.
EXPECT_EQ(LargestReportedSize(), 20000u);
EXPECT_EQ(CountCandidates(), 1u);
GetDocument().getElementById("large")->remove();
UpdateAllLifecyclePhasesForTest();
// The LCP does not move after the text is removed.
EXPECT_EQ(LargestReportedSize(), 20000u);
EXPECT_EQ(CountCandidates(), 1u);
}
TEST_F(LargestContentfulPaintCalculatorTest, LargestTextRemoved) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<img id='medium'/>
<p id='large'>
This text element should be larger than than the image!\n
These words ensure that this is the case.\n
But the image will be larger than the other paragraph!
</p>
<p id='small'>.</p>
)HTML");
SetImage("medium", 10, 5);
UpdateAllLifecyclePhasesForTest();
SimulateImagePresentationPromise();
SimulateTextPresentationPromise();
// Test is larger than the image.
EXPECT_GT(LargestReportedSize(), 50u);
// Image presentation occurred first, so we have would have two candidates.
EXPECT_EQ(CountCandidates(), 2u);
GetDocument().getElementById("large")->remove();
UpdateAllLifecyclePhasesForTest();
// The LCP should not move after removal.
EXPECT_GT(LargestReportedSize(), 50u);
EXPECT_EQ(CountCandidates(), 2u);
}
} // namespace blink