blob: eacb57190945f1517a2acce286938df613c8f088 [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/image_paint_timing_detector.h"
#include "base/bind.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/test/trace_event_analyzer.h"
#include "build/build_config.h"
#include "third_party/blink/public/web/web_performance.h"
#include "third_party/blink/public/web/web_widget_client.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.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/html/media/html_video_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/scroll/scroll_types.h"
#include "third_party/blink/renderer/core/svg/svg_image_element.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/core/timing/dom_window_performance.h"
#include "third_party/blink/renderer/core/timing/performance_timing.h"
#include "third_party/blink/renderer/core/timing/window_performance.h"
#include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace blink {
#define SIMPLE_IMAGE \
"data:image/gif;base64," \
"R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
#define LARGE_IMAGE \
"data:image/gif;base64," \
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSF" \
"lzAAAN1wAADdcBQiibeAAAAb5JREFUOMulkr1KA0EQgGdvTwwnYmER0gQsrFKmSy+pLESw9Qm0" \
"F/ICNnba+h6iEOuAEWslKJKTOyJJvIT72d1xZuOFC0giOLA77O7Mt/PnNptN+I+49Xr9GhH3f3" \
"mb0v1ht9vtLAUYYw5ItkgDL3KyD8PhcLvdbl/WarXT3DjLMnAcR/f7/YfxeKwtgC5RKQVhGILW" \
"eg4hQ6hUKjWyucmhLFEUuWR3QYBWAZABQ9i5CCmXy16pVALP80BKaaG+70MQBLvzFMjRKKXh8j" \
"6FSYKF7ITdEWLa4/ktokN74wiqjSMpnVcbQZqmEJHz+ckeCPFjWKwULpyspAqhdXVXdcnZcPjs" \
"Ign+2BsVA8jVYuWlgJ3yBj0icgq2uoK+lg4t+ZvLomSKamSQ4AI5BcMADtMhyNoSgNIISUaFNt" \
"wlazcDcBc4gjjVwCWid2usCWroYEhnaqbzFJLUzAHIXRDChXCcQP8zhkSZ5eNLgHAUzwDcRu4C" \
"oIRn/wsGUQIIy4Vr9TH6SYFCNzw4nALn5627K4vIttOUOwfa5YnrDYzt/9OLv9I5l8kk5hZ3XL" \
"O20b7tbR7zHLy/BX8G0IeBEM7ZN1NGIaFUaKLgAAAAAElFTkSuQmCC"
class ImagePaintTimingDetectorTest : public testing::Test,
public PaintTestConfigurations {
public:
ImagePaintTimingDetectorTest()
: test_task_runner_(
base::MakeRefCounted<base::TestMockTimeTaskRunner>()) {}
void SetUp() override {
web_view_helper_.Initialize();
// Enable compositing on the page before running the document lifecycle.
web_view_helper_.GetWebView()
->GetPage()
->GetSettings()
.SetAcceleratedCompositingEnabled(true);
WebLocalFrameImpl& frame_impl = *web_view_helper_.LocalMainFrame();
frame_impl.ViewImpl()->MainFrameWidget()->Resize(WebSize(640, 480));
frame_test_helpers::LoadFrame(
web_view_helper_.GetWebView()->MainFrameImpl(), "about:blank");
GetDocument().View()->SetParentVisible(true);
GetDocument().View()->SetSelfVisible(true);
}
protected:
LocalFrameView& GetFrameView() { return *GetFrame()->View(); }
LocalFrameView& GetChildFrameView() { return *GetChildFrame()->View(); }
Document& GetDocument() { return *GetFrame()->GetDocument(); }
Document* GetChildDocument() { return GetChildFrame()->GetDocument(); }
PaintTimingDetector& GetPaintTimingDetector() {
return GetFrameView().GetPaintTimingDetector();
}
PaintTimingDetector& GetChildPaintTimingDetector() {
return GetChildFrameView().GetPaintTimingDetector();
}
const PerformanceTiming& GetPerformanceTiming() {
PerformanceTiming* performance =
DOMWindowPerformance::performance(*GetFrame()->DomWindow())->timing();
return *performance;
}
IntRect GetViewportRect(LocalFrameView& view) {
ScrollableArea* scrollable_area = view.GetScrollableArea();
DCHECK(scrollable_area);
return scrollable_area->VisibleContentRect();
}
ImageRecord* FindLargestPaintCandidate() {
return GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.FindLargestPaintCandidate();
}
ImageRecord* FindChildFrameLargestPaintCandidate() {
return GetChildFrameView()
.GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.FindLargestPaintCandidate();
}
size_t CountVisibleImageRecords() {
return GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.visible_images_.size();
}
size_t CountInvisibleRecords() {
return GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.invisible_images_.size();
}
size_t ContainerTotalSize() {
return GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.invisible_images_.size() +
GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.visible_images_.size() +
GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.size_ordered_set_.size() +
GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.images_queued_for_paint_time_.size() +
GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.image_finished_times_.size();
}
size_t CountChildFrameRecords() {
return GetChildPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.visible_images_.size();
}
size_t CountRankingSetRecords() {
return GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->records_manager_.size_ordered_set_.size();
}
void UpdateCandidate() {
GetPaintTimingDetector().GetImagePaintTimingDetector()->UpdateCandidate();
}
base::TimeTicks LargestPaintTime() {
return GetPaintTimingDetector().largest_image_paint_time_;
}
uint64_t LargestPaintSize() {
return GetPaintTimingDetector().largest_image_paint_size_;
}
base::TimeTicks ExperimentalLargestPaintTime() {
return GetPaintTimingDetector().experimental_largest_image_paint_time_;
}
uint64_t ExperimentalLargestPaintSize() {
return GetPaintTimingDetector().experimental_largest_image_paint_size_;
}
static constexpr base::TimeDelta kQuantumOfTime =
base::TimeDelta::FromMilliseconds(10);
void SimulatePassOfTime() {
test_task_runner_->FastForwardBy(kQuantumOfTime);
}
void UpdateAllLifecyclePhases() {
GetDocument().View()->UpdateAllLifecyclePhases(DocumentUpdateReason::kTest);
}
void UpdateAllLifecyclePhasesAndInvokeCallbackIfAny() {
UpdateAllLifecyclePhases();
SimulatePassOfTime();
while (mock_callback_manager_->CountCallbacks() > 0)
InvokeSwapTimeCallback(mock_callback_manager_);
}
void SetBodyInnerHTML(const std::string& content) {
frame_test_helpers::LoadHTMLString(
web_view_helper_.GetWebView()->MainFrameImpl(), content,
KURL("http://test.com"));
mock_callback_manager_ =
MakeGarbageCollected<MockPaintTimingCallbackManager>();
GetPaintTimingDetector()
.GetImagePaintTimingDetector()
->ResetCallbackManager(mock_callback_manager_);
UpdateAllLifecyclePhases();
}
void SetChildBodyInnerHTML(const String& content) {
GetChildDocument()->SetBaseURLOverride(KURL("http://test.com"));
GetChildDocument()->body()->setInnerHTML(content, ASSERT_NO_EXCEPTION);
child_mock_callback_manager_ =
MakeGarbageCollected<MockPaintTimingCallbackManager>();
GetChildPaintTimingDetector()
.GetImagePaintTimingDetector()
->ResetCallbackManager(child_mock_callback_manager_);
UpdateAllLifecyclePhases();
}
void InvokeCallback() {
DCHECK_GT(mock_callback_manager_->CountCallbacks(), 0UL);
InvokeSwapTimeCallback(mock_callback_manager_);
}
void InvokeChildFrameCallback() {
DCHECK_GT(child_mock_callback_manager_->CountCallbacks(), 0UL);
InvokeSwapTimeCallback(child_mock_callback_manager_);
}
void InvokeSwapTimeCallback(
MockPaintTimingCallbackManager* image_callback_manager) {
image_callback_manager->InvokeSwapTimeCallback(
test_task_runner_->NowTicks());
UpdateCandidate();
}
void SetImageAndPaint(AtomicString id, int width, int height) {
Element* element = GetDocument().getElementById(id);
// Set image and make it loaded.
ImageResourceContent* content = CreateImageForTest(width, height);
To<HTMLImageElement>(element)->SetImageForTest(content);
}
void SetChildFrameImageAndPaint(AtomicString id, int width, int height) {
DCHECK(GetChildDocument());
Element* element = GetChildDocument()->getElementById(id);
DCHECK(element);
// Set image and make it loaded.
ImageResourceContent* content = CreateImageForTest(width, height);
To<HTMLImageElement>(element)->SetImageForTest(content);
}
void SetSVGImageAndPaint(AtomicString id, int width, int height) {
Element* element = GetDocument().getElementById(id);
// Set image and make it loaded.
ImageResourceContent* content = CreateImageForTest(width, height);
To<SVGImageElement>(element)->SetImageForTest(content);
}
void SimulateScroll() {
GetPaintTimingDetector().NotifyScroll(mojom::blink::ScrollType::kUser);
}
void SimulateKeyUp() {
GetPaintTimingDetector().NotifyInputEvent(WebInputEvent::Type::kKeyUp);
}
scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
frame_test_helpers::WebViewHelper web_view_helper_;
private:
LocalFrame* GetFrame() {
return web_view_helper_.GetWebView()->MainFrameImpl()->GetFrame();
}
LocalFrame* GetChildFrame() {
return To<LocalFrame>(GetFrame()->Tree().FirstChild());
}
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;
}
PaintTimingCallbackManager::CallbackQueue callback_queue_;
Persistent<MockPaintTimingCallbackManager> mock_callback_manager_;
Persistent<MockPaintTimingCallbackManager> child_mock_callback_manager_;
};
constexpr base::TimeDelta ImagePaintTimingDetectorTest::kQuantumOfTime;
INSTANTIATE_PAINT_TEST_SUITE_P(ImagePaintTimingDetectorTest);
TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_NoImage) {
SetBodyInnerHTML(R"HTML(
<div></div>
)HTML");
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_FALSE(record);
EXPECT_EQ(ExperimentalLargestPaintTime(), base::TimeTicks());
EXPECT_EQ(ExperimentalLargestPaintSize(), 0ul);
}
TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_OneImage) {
SetBodyInnerHTML(R"HTML(
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_EQ(record->first_size, 25ul);
EXPECT_TRUE(record->loaded);
EXPECT_EQ(ExperimentalLargestPaintSize(), 25ul);
}
TEST_P(ImagePaintTimingDetectorTest, InsertionOrderIsSecondaryRankingKey) {
SetBodyInnerHTML(R"HTML(
)HTML");
auto* image1 = MakeGarbageCollected<HTMLImageElement>(GetDocument());
image1->setAttribute("id", "image1");
GetDocument().body()->AppendChild(image1);
SetImageAndPaint("image1", 5, 5);
auto* image2 = MakeGarbageCollected<HTMLImageElement>(GetDocument());
image2->setAttribute("id", "image2");
GetDocument().body()->AppendChild(image2);
SetImageAndPaint("image2", 5, 5);
auto* image3 = MakeGarbageCollected<HTMLImageElement>(GetDocument());
image3->setAttribute("id", "image3");
GetDocument().body()->AppendChild(image3);
SetImageAndPaint("image3", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(FindLargestPaintCandidate()->node_id,
DOMNodeIds::ExistingIdForNode(image1));
EXPECT_EQ(ExperimentalLargestPaintSize(), 25ul);
}
TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_TraceEvent_Candidate) {
using trace_analyzer::Query;
trace_analyzer::Start("loading");
{
SetBodyInnerHTML(R"HTML(
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
}
auto analyzer = trace_analyzer::Stop();
trace_analyzer::TraceEventVector events;
Query q = Query::EventNameIs("LargestImagePaint::Candidate");
analyzer->FindEvents(q, &events);
EXPECT_EQ(1u, events.size());
EXPECT_EQ("loading", events[0]->category);
EXPECT_TRUE(events[0]->HasArg("frame"));
EXPECT_TRUE(events[0]->HasArg("data"));
std::unique_ptr<base::Value> arg;
EXPECT_TRUE(events[0]->GetArgAsValue("data", &arg));
base::DictionaryValue* arg_dict;
EXPECT_TRUE(arg->GetAsDictionary(&arg_dict));
DOMNodeId node_id;
EXPECT_TRUE(arg_dict->GetInteger("DOMNodeId", &node_id));
EXPECT_GT(node_id, 0);
int size;
EXPECT_TRUE(arg_dict->GetInteger("size", &size));
EXPECT_GT(size, 0);
DOMNodeId candidate_index;
EXPECT_TRUE(arg_dict->GetInteger("candidateIndex", &candidate_index));
EXPECT_EQ(candidate_index, 2);
bool isMainFrame;
EXPECT_TRUE(arg_dict->GetBoolean("isMainFrame", &isMainFrame));
EXPECT_EQ(true, isMainFrame);
bool isOOPIF;
EXPECT_TRUE(arg_dict->GetBoolean("isOOPIF", &isOOPIF));
EXPECT_EQ(false, isOOPIF);
}
TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_TraceEvent_NoCandidate) {
using trace_analyzer::Query;
trace_analyzer::Start("*");
{
SetBodyInnerHTML(R"HTML(
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
GetDocument().getElementById("target")->remove();
UpdateAllLifecyclePhases();
// Experimental size still 25, not affected by removal.
EXPECT_EQ(ExperimentalLargestPaintSize(), 25ul);
}
auto analyzer = trace_analyzer::Stop();
trace_analyzer::TraceEventVector events;
Query q = Query::EventNameIs("LargestImagePaint::NoCandidate");
analyzer->FindEvents(q, &events);
EXPECT_EQ(2u, events.size());
{
EXPECT_EQ("loading", events[0]->category);
EXPECT_TRUE(events[0]->HasArg("frame"));
EXPECT_TRUE(events[0]->HasArg("data"));
std::unique_ptr<base::Value> arg;
EXPECT_TRUE(events[0]->GetArgAsValue("data", &arg));
base::DictionaryValue* arg_dict;
EXPECT_TRUE(arg->GetAsDictionary(&arg_dict));
DOMNodeId candidate_index;
EXPECT_TRUE(arg_dict->GetInteger("candidateIndex", &candidate_index));
EXPECT_EQ(candidate_index, 1);
bool is_main_frame;
EXPECT_TRUE(arg_dict->GetBoolean("isMainFrame", &is_main_frame));
EXPECT_EQ(true, is_main_frame);
bool is_oopif;
EXPECT_TRUE(arg_dict->GetBoolean("isOOPIF", &is_oopif));
EXPECT_EQ(false, is_oopif);
}
// Use block to reuse the temp variable names.
{
EXPECT_TRUE(events[1]->HasArg("data"));
std::unique_ptr<base::Value> arg;
EXPECT_TRUE(events[1]->GetArgAsValue("data", &arg));
base::DictionaryValue* arg_dict;
EXPECT_TRUE(arg->GetAsDictionary(&arg_dict));
DOMNodeId candidate_index;
EXPECT_TRUE(arg_dict->GetInteger("candidateIndex", &candidate_index));
EXPECT_EQ(candidate_index, 3);
}
}
TEST_P(ImagePaintTimingDetectorTest, UpdatePerformanceTiming) {
EXPECT_EQ(GetPerformanceTiming().LargestImagePaintSize(), 0u);
EXPECT_EQ(GetPerformanceTiming().LargestImagePaint(), 0u);
EXPECT_EQ(GetPerformanceTiming().ExperimentalLargestImagePaintSize(), 0u);
EXPECT_EQ(GetPerformanceTiming().ExperimentalLargestImagePaint(), 0u);
SetBodyInnerHTML(R"HTML(
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(GetPerformanceTiming().LargestImagePaintSize(), 25u);
EXPECT_GT(GetPerformanceTiming().LargestImagePaint(), 0u);
EXPECT_EQ(GetPerformanceTiming().ExperimentalLargestImagePaintSize(), 25u);
EXPECT_GT(GetPerformanceTiming().ExperimentalLargestImagePaint(), 0u);
}
TEST_P(ImagePaintTimingDetectorTest,
PerformanceTimingHasZeroTimeNonZeroSizeWhenTheLargestIsNotPainted) {
EXPECT_EQ(GetPerformanceTiming().LargestImagePaintSize(), 0u);
EXPECT_EQ(GetPerformanceTiming().LargestImagePaint(), 0u);
EXPECT_EQ(GetPerformanceTiming().ExperimentalLargestImagePaintSize(), 0u);
EXPECT_EQ(GetPerformanceTiming().ExperimentalLargestImagePaint(), 0u);
SetBodyInnerHTML(R"HTML(
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhases();
EXPECT_EQ(GetPerformanceTiming().LargestImagePaintSize(), 25u);
EXPECT_EQ(GetPerformanceTiming().LargestImagePaint(), 0u);
EXPECT_EQ(GetPerformanceTiming().ExperimentalLargestImagePaintSize(), 25u);
EXPECT_EQ(GetPerformanceTiming().ExperimentalLargestImagePaint(), 0u);
}
TEST_P(ImagePaintTimingDetectorTest, UpdatePerformanceTimingToZero) {
SetBodyInnerHTML(R"HTML(
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(GetPerformanceTiming().LargestImagePaintSize(), 25u);
EXPECT_GT(GetPerformanceTiming().LargestImagePaint(), 0u);
EXPECT_EQ(GetPerformanceTiming().ExperimentalLargestImagePaintSize(), 25u);
EXPECT_GT(GetPerformanceTiming().ExperimentalLargestImagePaint(), 0u);
GetDocument().body()->RemoveChild(GetDocument().getElementById("target"));
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(GetPerformanceTiming().LargestImagePaintSize(), 0u);
EXPECT_EQ(GetPerformanceTiming().LargestImagePaint(), 0u);
// Experimental values are not reset.
EXPECT_EQ(GetPerformanceTiming().ExperimentalLargestImagePaintSize(), 25u);
EXPECT_GT(GetPerformanceTiming().ExperimentalLargestImagePaint(), 0u);
}
TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_OpacityZero) {
SetBodyInnerHTML(R"HTML(
<style>
img {
opacity: 0;
}
</style>
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 0u);
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_FALSE(record);
}
TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_VisibilityHidden) {
SetBodyInnerHTML(R"HTML(
<style>
img {
visibility: hidden;
}
</style>
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 0u);
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_FALSE(record);
}
TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_DisplayNone) {
SetBodyInnerHTML(R"HTML(
<style>
img {
display: none;
}
</style>
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 0u);
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_FALSE(record);
}
TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_OpacityNonZero) {
SetBodyInnerHTML(R"HTML(
<style>
img {
opacity: 0.01;
}
</style>
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 1u);
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
}
TEST_P(ImagePaintTimingDetectorTest,
IgnoreImageUntilInvalidatedRectSizeNonZero) {
SetBodyInnerHTML(R"HTML(
<img id="target"></img>
)HTML");
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 0u);
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_EQ(CountVisibleImageRecords(), 1u);
}
TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_Largest) {
SetBodyInnerHTML(R"HTML(
<style>img { display:block }</style>
<img id="smaller"></img>
<img id="medium"></img>
<img id="larger"></img>
)HTML");
SetImageAndPaint("smaller", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record;
record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_EQ(record->first_size, 25ul);
SetImageAndPaint("larger", 9, 9);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(LargestPaintSize(), 81ul);
EXPECT_EQ(ExperimentalLargestPaintSize(), 81ul);
}
TEST_P(ImagePaintTimingDetectorTest,
LargestImagePaint_IgnoreThoseOutsideViewport) {
SetBodyInnerHTML(R"HTML(
<style>
img {
position: fixed;
top: -100px;
}
</style>
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_FALSE(record);
}
TEST_P(ImagePaintTimingDetectorTest,
LargestImagePaint_UpdateOnRemovingTheLastImage) {
SetBodyInnerHTML(R"HTML(
<div id="parent">
<img id="target"></img>
</div>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record;
record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_NE(LargestPaintTime(), base::TimeTicks());
EXPECT_EQ(LargestPaintSize(), 25ul);
EXPECT_NE(ExperimentalLargestPaintTime(), base::TimeTicks());
EXPECT_EQ(ExperimentalLargestPaintSize(), 25ul);
GetDocument().getElementById("parent")->RemoveChild(
GetDocument().getElementById("target"));
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
record = FindLargestPaintCandidate();
EXPECT_FALSE(record);
EXPECT_EQ(LargestPaintTime(), base::TimeTicks());
EXPECT_EQ(LargestPaintSize(), 0u);
// Experimental values not reset after removal.
EXPECT_NE(ExperimentalLargestPaintTime(), base::TimeTicks());
EXPECT_EQ(ExperimentalLargestPaintSize(), 25ul);
}
TEST_P(ImagePaintTimingDetectorTest, LargestImagePaint_UpdateOnRemoving) {
SetBodyInnerHTML(R"HTML(
<div id="parent">
<img id="target1"></img>
<img id="target2"></img>
</div>
)HTML");
SetImageAndPaint("target1", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record1 = FindLargestPaintCandidate();
EXPECT_TRUE(record1);
EXPECT_NE(LargestPaintTime(), base::TimeTicks());
base::TimeTicks first_largest_image_paint = LargestPaintTime();
SetImageAndPaint("target2", 10, 10);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record2 = FindLargestPaintCandidate();
EXPECT_TRUE(record2);
EXPECT_NE(LargestPaintTime(), base::TimeTicks());
base::TimeTicks second_largest_image_paint = LargestPaintTime();
EXPECT_NE(record1, record2);
EXPECT_NE(first_largest_image_paint, second_largest_image_paint);
GetDocument().getElementById("parent")->RemoveChild(
GetDocument().getElementById("target2"));
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record1_2 = FindLargestPaintCandidate();
EXPECT_EQ(record1, record1_2);
EXPECT_EQ(first_largest_image_paint, LargestPaintTime());
EXPECT_EQ(LargestPaintSize(), 25u);
EXPECT_EQ(second_largest_image_paint, ExperimentalLargestPaintTime());
EXPECT_EQ(ExperimentalLargestPaintSize(), 100u);
}
TEST_P(ImagePaintTimingDetectorTest,
LargestImagePaint_NodeRemovedBetweenRegistrationAndInvocation) {
SetBodyInnerHTML(R"HTML(
<div id="parent">
<img id="target"></img>
</div>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhases();
GetDocument().getElementById("parent")->RemoveChild(
GetDocument().getElementById("target"));
InvokeCallback();
ImageRecord* record;
record = FindLargestPaintCandidate();
EXPECT_FALSE(record);
}
TEST_P(ImagePaintTimingDetectorTest,
RemoveRecordFromAllContainersAfterImageRemoval) {
SetBodyInnerHTML(R"HTML(
<div id="parent">
<img id="target"></img>
</div>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(ContainerTotalSize(), 3u);
GetDocument().getElementById("parent")->RemoveChild(
GetDocument().getElementById("target"));
EXPECT_EQ(ContainerTotalSize(), 0u);
}
TEST_P(ImagePaintTimingDetectorTest,
RemoveRecordFromAllContainersAfterInvisibleImageRemoved) {
// TODO(wangxianzhu): Fix this test for CompositeAfterPaint.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
SetBodyInnerHTML(R"HTML(
<style>
#target {
position: relative;
left: 100px;
}
#parent {
background-color: yellow;
height: 50px;
width: 50px;
overflow: scroll;
}
</style>
<div id='parent'>
<img id='target'></img>
</div>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(ContainerTotalSize(), 2u);
EXPECT_EQ(CountInvisibleRecords(), 1u);
GetDocument().body()->RemoveChild(GetDocument().getElementById("parent"));
EXPECT_EQ(ContainerTotalSize(), 0u);
EXPECT_EQ(CountInvisibleRecords(), 0u);
}
TEST_P(ImagePaintTimingDetectorTest,
RemoveRecordFromAllContainersAfterBackgroundImageRemoval) {
SetBodyInnerHTML(R"HTML(
<style>
#target {
background-image: url()HTML" SIMPLE_IMAGE R"HTML();
}
</style>
<div id="parent">
<div id="target">
place-holder
</div>
</div>
)HTML");
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(ContainerTotalSize(), 3u);
GetDocument().getElementById("parent")->RemoveChild(
GetDocument().getElementById("target"));
EXPECT_EQ(ContainerTotalSize(), 0u);
}
TEST_P(ImagePaintTimingDetectorTest,
RemoveRecordFromAllContainersAfterImageRemovedAndCallbackInvoked) {
SetBodyInnerHTML(R"HTML(
<div id="parent">
<img id="target"></img>
</div>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhases();
EXPECT_EQ(ContainerTotalSize(), 4u);
GetDocument().getElementById("parent")->RemoveChild(
GetDocument().getElementById("target"));
EXPECT_EQ(ContainerTotalSize(), 1u);
InvokeCallback();
EXPECT_EQ(ContainerTotalSize(), 0u);
}
TEST_P(ImagePaintTimingDetectorTest,
LargestImagePaint_ReattachedNodeTreatedAsNew) {
SetBodyInnerHTML(R"HTML(
<div id="parent">
</div>
)HTML");
auto* image = MakeGarbageCollected<HTMLImageElement>(GetDocument());
image->setAttribute("id", "target");
GetDocument().getElementById("parent")->AppendChild(image);
SetImageAndPaint("target", 5, 5);
test_task_runner_->FastForwardBy(base::TimeDelta::FromSecondsD(1));
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record;
record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
// UpdateAllLifecyclePhasesAndInvokeCallbackIfAny() moves time forward
// kQuantumOfTime so we should take that into account.
EXPECT_EQ(
record->paint_time,
base::TimeTicks() + base::TimeDelta::FromSecondsD(1) + kQuantumOfTime);
GetDocument().getElementById("parent")->RemoveChild(image);
test_task_runner_->FastForwardBy(base::TimeDelta::FromSecondsD(1));
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
record = FindLargestPaintCandidate();
EXPECT_FALSE(record);
GetDocument().getElementById("parent")->AppendChild(image);
SetImageAndPaint("target", 5, 5);
test_task_runner_->FastForwardBy(base::TimeDelta::FromSecondsD(1));
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
// UpdateAllLifecyclePhasesAndInvokeCallbackIfAny() moves time forward
// kQuantumOfTime so we should take that into account.
EXPECT_EQ(record->paint_time, base::TimeTicks() +
base::TimeDelta::FromSecondsD(3) +
3 * kQuantumOfTime);
}
// This is to prove that a swap time is assigned only to nodes of the frame who
// register the swap time. In other words, swap time A should match frame A;
// swap time B should match frame B.
TEST_P(ImagePaintTimingDetectorTest, MatchSwapTimeToNodesOfDifferentFrames) {
SetBodyInnerHTML(R"HTML(
<div id="parent">
<img height="5" width="5" id="smaller"></img>
<img height="9" width="9" id="larger"></img>
</div>
)HTML");
SetImageAndPaint("larger", 9, 9);
UpdateAllLifecyclePhases();
SimulatePassOfTime();
SetImageAndPaint("smaller", 5, 5);
UpdateAllLifecyclePhases();
SimulatePassOfTime();
InvokeCallback();
// record1 is the larger.
ImageRecord* record1 = FindLargestPaintCandidate();
const base::TimeTicks record1Time = record1->paint_time;
GetDocument().getElementById("parent")->RemoveChild(
GetDocument().getElementById("larger"));
UpdateAllLifecyclePhases();
SimulatePassOfTime();
InvokeCallback();
// record2 is the smaller.
ImageRecord* record2 = FindLargestPaintCandidate();
EXPECT_NE(record1Time, record2->paint_time);
}
TEST_P(ImagePaintTimingDetectorTest,
LargestImagePaint_UpdateResultWhenLargestChanged) {
base::TimeTicks time1 = test_task_runner_->NowTicks();
SetBodyInnerHTML(R"HTML(
<div id="parent">
<img id="target1"></img>
<img id="target2"></img>
</div>
)HTML");
SetImageAndPaint("target1", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
base::TimeTicks time2 = test_task_runner_->NowTicks();
base::TimeTicks result1 = LargestPaintTime();
EXPECT_GE(result1, time1);
EXPECT_GE(time2, result1);
EXPECT_EQ(result1, ExperimentalLargestPaintTime());
SetImageAndPaint("target2", 10, 10);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
base::TimeTicks time3 = test_task_runner_->NowTicks();
base::TimeTicks result2 = LargestPaintTime();
EXPECT_GE(result2, time2);
EXPECT_GE(time3, result2);
EXPECT_EQ(result2, ExperimentalLargestPaintTime());
}
TEST_P(ImagePaintTimingDetectorTest, OneSwapPromiseForOneFrame) {
SetBodyInnerHTML(R"HTML(
<style>img { display:block }</style>
<div id="parent">
<img id="1"></img>
<img id="2"></img>
</div>
)HTML");
SetImageAndPaint("1", 5, 5);
UpdateAllLifecyclePhases();
SimulatePassOfTime();
SetImageAndPaint("2", 9, 9);
UpdateAllLifecyclePhases();
SimulatePassOfTime();
// This callback only assigns a time to the 5x5 image.
InvokeCallback();
ImageRecord* record;
record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_EQ(record->first_size, 81ul);
EXPECT_TRUE(record->paint_time.is_null());
// This callback assigns a time to the 9x9 image.
InvokeCallback();
record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_EQ(record->first_size, 81ul);
EXPECT_FALSE(record->paint_time.is_null());
}
TEST_P(ImagePaintTimingDetectorTest, VideoImage) {
SetBodyInnerHTML(R"HTML(
<video id="target" poster=")HTML" LARGE_IMAGE R"HTML("></video>
)HTML");
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_GT(record->first_size, 0ul);
EXPECT_TRUE(record->loaded);
}
TEST_P(ImagePaintTimingDetectorTest, VideoImage_ImageNotLoaded) {
SetBodyInnerHTML("<video id='target'></video>");
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_FALSE(record);
}
TEST_P(ImagePaintTimingDetectorTest, SVGImage) {
SetBodyInnerHTML(R"HTML(
<svg>
<image id="target" width="10" height="10"/>
</svg>
)HTML");
SetSVGImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_GT(record->first_size, 0ul);
EXPECT_TRUE(record->loaded);
}
TEST_P(ImagePaintTimingDetectorTest, BackgroundImage) {
SetBodyInnerHTML(R"HTML(
<style>
div {
background-image: url()HTML" SIMPLE_IMAGE R"HTML();
}
</style>
<div>place-holder</div>
)HTML");
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_EQ(CountVisibleImageRecords(), 1u);
}
TEST_P(ImagePaintTimingDetectorTest,
BackgroundImageAndLayoutImageTrackedDifferently) {
SetBodyInnerHTML(R"HTML(
<style>
img {
background-image: url()HTML" LARGE_IMAGE R"HTML();
}
</style>
<img id="target">
place-holder
</img>
)HTML");
SetImageAndPaint("target", 1, 1);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountVisibleImageRecords(), 2u);
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_EQ(record->first_size, 1u);
}
TEST_P(ImagePaintTimingDetectorTest, BackgroundImage_IgnoreBody) {
SetBodyInnerHTML("<style>body { background-image: url(" SIMPLE_IMAGE
")}</style>");
EXPECT_EQ(CountVisibleImageRecords(), 0u);
}
TEST_P(ImagePaintTimingDetectorTest, BackgroundImage_IgnoreHtml) {
SetBodyInnerHTML("<style>html { background-image: url(" SIMPLE_IMAGE
")}</style>");
EXPECT_EQ(CountVisibleImageRecords(), 0u);
}
TEST_P(ImagePaintTimingDetectorTest, BackgroundImage_IgnoreGradient) {
SetBodyInnerHTML(R"HTML(
<style>
div {
background-image: linear-gradient(blue, yellow);
}
</style>
<div>
place-holder
</div>
)HTML");
EXPECT_EQ(CountVisibleImageRecords(), 0u);
}
// We put two background images in the same object, and test whether FCP++ can
// find two different images.
TEST_P(ImagePaintTimingDetectorTest, BackgroundImageTrackedDifferently) {
SetBodyInnerHTML(R"HTML(
<style>
#d {
width: 50px;
height: 50px;
background-image:
url()HTML" SIMPLE_IMAGE "), url(" LARGE_IMAGE R"HTML();
}
</style>
<div id="d"></div>
)HTML");
EXPECT_EQ(CountVisibleImageRecords(), 2u);
}
TEST_P(ImagePaintTimingDetectorTest, DeactivateAfterUserInput) {
SetBodyInnerHTML(R"HTML(
<div id="parent">
<img id="target"></img>
</div>
)HTML");
SimulateScroll();
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_FALSE(GetPaintTimingDetector().GetImagePaintTimingDetector());
}
TEST_P(ImagePaintTimingDetectorTest, ContinueAfterKeyUp) {
SetBodyInnerHTML(R"HTML(
<div id="parent">
<img id="target"></img>
</div>
)HTML");
SimulateKeyUp();
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_TRUE(GetPaintTimingDetector().GetImagePaintTimingDetector());
}
TEST_P(ImagePaintTimingDetectorTest, NullTimeNoCrash) {
SetBodyInnerHTML(R"HTML(
<img id="target"></img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhases();
UpdateCandidate();
}
TEST_P(ImagePaintTimingDetectorTest, Iframe) {
SetBodyInnerHTML(R"HTML(
<iframe width=100px height=100px></iframe>
)HTML");
SetChildBodyInnerHTML(R"HTML(
<style>img { display:block }</style>
<img id="target"></img>
)HTML");
SetChildFrameImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhases();
// Ensure main frame doesn't capture this image.
EXPECT_EQ(CountVisibleImageRecords(), 0u);
EXPECT_EQ(CountChildFrameRecords(), 1u);
InvokeChildFrameCallback();
ImageRecord* image = FindChildFrameLargestPaintCandidate();
EXPECT_TRUE(image);
// Ensure the image size is not clipped (5*5).
EXPECT_EQ(image->first_size, 25ul);
}
TEST_P(ImagePaintTimingDetectorTest, Iframe_ClippedByMainFrameViewport) {
SetBodyInnerHTML(R"HTML(
<style>
#f { margin-top: 1234567px }
</style>
<iframe id="f" width=100px height=100px></iframe>
)HTML");
SetChildBodyInnerHTML(R"HTML(
<style>img { display:block }</style>
<img id="target"></img>
)HTML");
// Make sure the iframe is out of main-frame's viewport.
DCHECK_LT(GetViewportRect(GetFrameView()).Height(), 1234567);
SetChildFrameImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhases();
EXPECT_EQ(CountVisibleImageRecords(), 0u);
}
TEST_P(ImagePaintTimingDetectorTest, Iframe_HalfClippedByMainFrameViewport) {
SetBodyInnerHTML(R"HTML(
<style>
#f { margin-left: -5px; }
</style>
<iframe id="f" width=10px height=10px></iframe>
)HTML");
SetChildBodyInnerHTML(R"HTML(
<style>img { display:block }</style>
<img id="target"></img>
)HTML");
SetChildFrameImageAndPaint("target", 10, 10);
UpdateAllLifecyclePhases();
EXPECT_EQ(CountVisibleImageRecords(), 0u);
EXPECT_EQ(CountChildFrameRecords(), 1u);
InvokeChildFrameCallback();
ImageRecord* image = FindChildFrameLargestPaintCandidate();
EXPECT_TRUE(image);
EXPECT_LT(image->first_size, 100ul);
}
TEST_P(ImagePaintTimingDetectorTest, SameSizeShouldNotBeIgnored) {
SetBodyInnerHTML(R"HTML(
<style>img { display:block }</style>
<img id='1'></img>
<img id='2'></img>
<img id='3'></img>
)HTML");
SetImageAndPaint("1", 5, 5);
SetImageAndPaint("2", 5, 5);
SetImageAndPaint("3", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
EXPECT_EQ(CountRankingSetRecords(), 3u);
}
TEST_P(ImagePaintTimingDetectorTest, UseIntrinsicSizeIfSmaller_Image) {
SetBodyInnerHTML(R"HTML(
<img height="300" width="300" display="block" id="target">
</img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_EQ(record->first_size, 25u);
}
TEST_P(ImagePaintTimingDetectorTest, NotUseIntrinsicSizeIfLarger_Image) {
SetBodyInnerHTML(R"HTML(
<img height="1" width="1" display="block" id="target">
</img>
)HTML");
SetImageAndPaint("target", 5, 5);
UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_EQ(record->first_size, 1u);
}
TEST_P(ImagePaintTimingDetectorTest,
UseIntrinsicSizeIfSmaller_BackgroundImage) {
SetBodyInnerHTML(R"HTML(
<style>
#d {
width: 50px;
height: 50px;
background-image: url()HTML" SIMPLE_IMAGE R"HTML();
}
</style>
<div id="d"></div>
)HTML");
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
EXPECT_EQ(record->first_size, 1u);
}
TEST_P(ImagePaintTimingDetectorTest,
NotUseIntrinsicSizeIfLarger_BackgroundImage) {
// The image is in 16x16.
SetBodyInnerHTML(R"HTML(
<style>
#d {
width: 5px;
height: 5px;
background-image: url()HTML" LARGE_IMAGE R"HTML();
}
</style>
<div id="d"></div>
)HTML");
ImageRecord* record = FindLargestPaintCandidate();
EXPECT_TRUE(record);
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