blob: 0d3686cde7bf0595e43328f3ebe21f966bb45e42 [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/html/lazy_load_image_observer.h"
#include <tuple>
#include "base/optional.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/pseudo_element.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style/style_image.h"
#include "third_party/blink/renderer/core/testing/sim/sim_compositor.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/network/http_header_map.h"
#include "third_party/blink/renderer/platform/network/network_state_notifier.h"
#include "third_party/blink/renderer/platform/testing/histogram_tester.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
namespace {
const char* kLazyLoadEventsDeferredMessage =
"Images loaded lazily and replaced with placeholders. Load events are "
"deferred. See https://crbug.com/954323";
Vector<char> ReadTestImage() {
return test::ReadFromFile(test::CoreTestDataPath("notifications/500x500.png"))
->CopyAs<Vector<char>>();
}
class LazyLoadImagesSimTest : public ::testing::WithParamInterface<bool>,
public SimTest {
protected:
LazyLoadImagesSimTest()
: scoped_lazy_image_loading_for_test_(GetParam()),
scoped_automatic_lazy_image_loading_for_test_(GetParam()) {}
void SetLazyLoadEnabled(bool enabled) {
WebView().GetPage()->GetSettings().SetLazyLoadEnabled(enabled);
}
void LoadMainResource(const String& html_body) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(html_body);
GetDocument().UpdateStyleAndLayoutTree();
}
const ComputedStyle* GetElementComputedStyle(const Element& element,
PseudoId pseudo_id) {
if (pseudo_id == kPseudoIdNone)
return element.GetComputedStyle();
return element.GetPseudoElement(pseudo_id)->GetComputedStyle();
}
void ExpectCSSBackgroundImageDeferredState(const char* element_id,
PseudoId pseudo_id,
bool deferred) {
const ComputedStyle* deferred_image_style = GetElementComputedStyle(
*GetDocument().getElementById(element_id), pseudo_id);
EXPECT_TRUE(deferred_image_style->HasBackgroundImage());
bool is_background_image_found = false;
for (const FillLayer* background_layer =
&deferred_image_style->BackgroundLayers();
background_layer; background_layer = background_layer->Next()) {
if (StyleImage* deferred_image = background_layer->GetImage()) {
EXPECT_TRUE(deferred_image->IsImageResource());
EXPECT_EQ(deferred, deferred_image->IsLazyloadPossiblyDeferred());
EXPECT_NE(deferred, deferred_image->IsLoaded());
is_background_image_found = true;
}
}
EXPECT_TRUE(is_background_image_found);
EXPECT_FALSE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageMissingDimensionsForLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeEager));
}
void VerifyCSSBackgroundImageInPseudoStyleDeferred(
const char* style,
const char* deferred_div_classes,
const Vector<PseudoId>& background_pseudo_ids) {
bool is_lazyload_image_enabled = GetParam();
SetLazyLoadEnabled(is_lazyload_image_enabled);
SimRequest image_resource("https://example.com/img.png", "image/png");
LoadMainResource(String::Format(R"HTML(
<html>
<head>
<style>
%s
</style>
</head>
<body>
<div style='height:10000px;'></div>
<div id="deferred_image" class="%s"></div>
</body>
</html>
)HTML",
style, deferred_div_classes));
if (!is_lazyload_image_enabled)
image_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
for (const auto& pseudo_id : background_pseudo_ids) {
ExpectCSSBackgroundImageDeferredState("deferred_image", pseudo_id,
is_lazyload_image_enabled);
}
if (is_lazyload_image_enabled) {
// Scroll down until the background image is visible.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 10000), mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
image_resource.Complete(ReadTestImage());
for (const auto& pseudo_id : background_pseudo_ids) {
ExpectCSSBackgroundImageDeferredState("deferred_image", pseudo_id,
false);
}
}
EXPECT_FALSE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageMissingDimensionsForLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeEager));
}
void VerifyImageElementWithDimensionDeferred(const char* img_attribute) {
bool is_lazyload_image_enabled = GetParam();
SetLazyLoadEnabled(is_lazyload_image_enabled);
SimRequest image_resource("https://example.com/img.png", "image/png");
LoadMainResource(String::Format(R"HTML(
<body onload='console.log("main body onload");'>
<div style='height:10000px;'></div>
<img src="img.png" %s
onload= 'console.log("deferred_image onload");'>
</body>)HTML",
img_attribute));
if (!is_lazyload_image_enabled)
image_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
if (!is_lazyload_image_enabled)
EXPECT_TRUE(ConsoleMessages().Contains("deferred_image onload"));
if (is_lazyload_image_enabled) {
// Scroll down until the image element is visible.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 10000), mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
image_resource.Complete(ReadTestImage());
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("deferred_image onload"));
}
EXPECT_EQ(is_lazyload_image_enabled,
ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageMissingDimensionsForLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeEager));
}
private:
ScopedLazyImageLoadingForTest scoped_lazy_image_loading_for_test_;
ScopedAutomaticLazyImageLoadingForTest
scoped_automatic_lazy_image_loading_for_test_;
ScopedRestrictAutomaticLazyImageLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_image_loading_to_data_saver_for_test_ =
false;
};
TEST_P(LazyLoadImagesSimTest, CSSBackgroundImage) {
bool is_lazyload_image_enabled = GetParam();
SetLazyLoadEnabled(is_lazyload_image_enabled);
SimRequest image_resource("https://example.com/img.png", "image/png");
LoadMainResource(R"HTML(
<style>
#deferred_image {
height:200px;
background-image: url('img.png');
}
</style>
<div style='height:10000px;'></div>
<div id="deferred_image"></div>
)HTML");
if (!is_lazyload_image_enabled)
image_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
ExpectCSSBackgroundImageDeferredState("deferred_image", kPseudoIdNone,
is_lazyload_image_enabled);
if (is_lazyload_image_enabled) {
// Scroll down until the background image is visible.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 10000), mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
image_resource.Complete(ReadTestImage());
ExpectCSSBackgroundImageDeferredState("deferred_image", kPseudoIdNone,
false);
}
}
TEST_P(LazyLoadImagesSimTest, CSSBackgroundImagePseudoStyleBefore) {
VerifyCSSBackgroundImageInPseudoStyleDeferred(R"HTML(
.pseudo-element::before {
content: '';
height: 50px;
background-image: url('img.png');
})HTML",
"pseudo-element",
{kPseudoIdBefore});
}
TEST_P(LazyLoadImagesSimTest, CSSBackgroundImagePseudoStyleAfter) {
VerifyCSSBackgroundImageInPseudoStyleDeferred(R"HTML(
.pseudo-element::after {
content: '';
height: 50px;
background-image: url('img.png');
})HTML",
"pseudo-element",
{kPseudoIdAfter});
}
TEST_P(LazyLoadImagesSimTest, CSSBackgroundImagePseudoStyleBeforeBlock) {
VerifyCSSBackgroundImageInPseudoStyleDeferred(R"HTML(
.pseudo-element::before {
content: '';
display: block;
height: 50px;
width: 50px;
background-image: url('img.png');
})HTML",
"pseudo-element",
{kPseudoIdBefore});
}
TEST_P(LazyLoadImagesSimTest,
CSSBackgroundImagePseudoStyleBeforeAndAfterBlock) {
VerifyCSSBackgroundImageInPseudoStyleDeferred(R"HTML(
.pseudo-element::before {
content: '';
display: block;
height: 50px;
width: 50px;
background-image: url('img.png');
})HTML",
"pseudo-element",
{kPseudoIdBefore});
}
TEST_P(LazyLoadImagesSimTest, LargeImageHeight100Width100) {
VerifyImageElementWithDimensionDeferred("height='100px' width='100px'");
}
TEST_P(LazyLoadImagesSimTest, LargeImageHeight1Width100) {
VerifyImageElementWithDimensionDeferred("height='1px' width='100px'");
}
TEST_P(LazyLoadImagesSimTest, LargeImageHeight100Width1) {
VerifyImageElementWithDimensionDeferred("height='100px' width='1px'");
}
TEST_P(LazyLoadImagesSimTest, LargeImageStyleHeight100Width100) {
VerifyImageElementWithDimensionDeferred(
"style='height: 100px; width: 100px;'");
}
TEST_P(LazyLoadImagesSimTest, LargeImageStyleHeight100Width1) {
VerifyImageElementWithDimensionDeferred("style='height: 100px; width: 1px;'");
}
TEST_P(LazyLoadImagesSimTest, LargeImageStyleHeight1Width100) {
VerifyImageElementWithDimensionDeferred("style='height: 1px; width: 100px;'");
}
TEST_P(LazyLoadImagesSimTest, ImgSrcset) {
if (!GetParam()) // Only test when LazyImage is enabled.
return;
SetLazyLoadEnabled(true);
WebView().Resize(gfx::Size(100, 1));
LoadMainResource(R"HTML(
<body onload='console.log("main body onload");'>
<div style='height:10000px;'></div>
<img src="img.png" srcset="img.png?100w 100w, img.png?200w 200w"
loading="lazy" onload= 'console.log("deferred_image onload");'>
</body>)HTML");
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("deferred_image onload"));
// Resizing should not load the image.
WebView().MainFrameViewWidget()->Resize(gfx::Size(200, 1));
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_FALSE(ConsoleMessages().Contains("deferred_image onload"));
// Scrolling down should load the larger image.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 10000), mojom::blink::ScrollType::kProgrammatic);
SimRequest image_resource("https://example.com/img.png?200w", "image/png");
Compositor().BeginFrame();
test::RunPendingTasks();
image_resource.Complete(ReadTestImage());
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("deferred_image onload"));
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
}
INSTANTIATE_TEST_SUITE_P(All,
LazyLoadImagesSimTest,
::testing::Bool() /*is_lazyload_image_enabled*/);
class ScopedDataSaverSetting {
public:
explicit ScopedDataSaverSetting(bool is_data_saver_enabled)
: was_data_saver_previously_enabled_(
GetNetworkStateNotifier().SaveDataEnabled()) {
GetNetworkStateNotifier().SetSaveDataEnabledOverride(is_data_saver_enabled);
}
~ScopedDataSaverSetting() {
GetNetworkStateNotifier().SetSaveDataEnabledOverride(
was_data_saver_previously_enabled_);
}
private:
const bool was_data_saver_previously_enabled_;
};
enum class LazyImageLoadingFeatureStatus {
// LazyImageLoading is disabled.
kDisabled = 0,
// LazyImageLoading is enabled, but AutomaticLazyImageLoading is disabled.
kEnabledExplicit,
// Both LazyImageLoading and AutomaticLazyImageLoading are enabled.
kEnabledAutomatic,
// LazyImageLoading, AutomaticLazyImageLoading, and
// RestrictAutomaticLazyImageLoadingToDataSaver are enabled, while data saver
// is off.
kEnabledAutomaticRestrictedAndDataSaverOff,
// LazyImageLoading, AutomaticLazyImageLoading, and
// RestrictAutomaticLazyImageLoadingToDataSaver are enabled, while data saver
// is on.
kEnabledAutomaticRestrictedAndDataSaverOn,
};
class LazyLoadImagesParamsTest : public SimTest,
public ::testing::WithParamInterface<
std::tuple<LazyImageLoadingFeatureStatus,
WebEffectiveConnectionType>> {
public:
static constexpr int kViewportWidth = 800;
static constexpr int kViewportHeight = 600;
LazyLoadImagesParamsTest()
: lazy_image_loading_feature_status_(
std::get<LazyImageLoadingFeatureStatus>(GetParam())),
scoped_lazy_image_loading_for_test_(
lazy_image_loading_feature_status_ !=
LazyImageLoadingFeatureStatus::kDisabled),
scoped_automatic_lazy_image_loading_for_test_(
static_cast<int>(lazy_image_loading_feature_status_) >=
static_cast<int>(LazyImageLoadingFeatureStatus::kEnabledAutomatic)),
scoped_restrict_automatic_lazy_image_loading_to_data_saver_for_test_(
static_cast<int>(lazy_image_loading_feature_status_) >=
static_cast<int>(LazyImageLoadingFeatureStatus::
kEnabledAutomaticRestrictedAndDataSaverOff)),
scoped_data_saver_setting_(
lazy_image_loading_feature_status_ ==
LazyImageLoadingFeatureStatus::
kEnabledAutomaticRestrictedAndDataSaverOn) {}
void SetUp() override {
GetNetworkStateNotifier().SetNetworkConnectionInfoOverride(
true /*on_line*/, kWebConnectionTypeWifi,
std::get<WebEffectiveConnectionType>(GetParam()),
1000 /*http_rtt_msec*/, 100 /*max_bandwidth_mbps*/);
SimTest::SetUp();
WebView().MainFrameViewWidget()->Resize(
gfx::Size(kViewportWidth, kViewportHeight));
Settings& settings = WebView().GetPage()->GetSettings();
// These should match the values that would be returned by
// GetLoadingDistanceThreshold().
settings.SetLazyImageLoadingDistanceThresholdPxUnknown(200);
settings.SetLazyImageLoadingDistanceThresholdPxOffline(300);
settings.SetLazyImageLoadingDistanceThresholdPxSlow2G(400);
settings.SetLazyImageLoadingDistanceThresholdPx2G(500);
settings.SetLazyImageLoadingDistanceThresholdPx3G(600);
settings.SetLazyImageLoadingDistanceThresholdPx4G(700);
settings.SetLazyLoadEnabled(
RuntimeEnabledFeatures::LazyImageLoadingEnabled());
settings.SetLazyImageFirstKFullyLoadUnknown(0);
settings.SetLazyImageFirstKFullyLoadSlow2G(0);
settings.SetLazyImageFirstKFullyLoad2G(0);
settings.SetLazyImageFirstKFullyLoad3G(0);
settings.SetLazyImageFirstKFullyLoad4G(0);
}
int GetLoadingDistanceThreshold() const {
static constexpr int kDistanceThresholdByEffectiveConnectionType[] = {
200, 300, 400, 500, 600, 700};
return kDistanceThresholdByEffectiveConnectionType[static_cast<int>(
std::get<WebEffectiveConnectionType>(GetParam()))];
}
bool IsAutomaticLazyImageLoadingExpected() const {
switch (lazy_image_loading_feature_status_) {
case LazyImageLoadingFeatureStatus::kDisabled:
case LazyImageLoadingFeatureStatus::kEnabledExplicit:
case LazyImageLoadingFeatureStatus::
kEnabledAutomaticRestrictedAndDataSaverOff:
return false;
case LazyImageLoadingFeatureStatus::kEnabledAutomatic:
case LazyImageLoadingFeatureStatus::
kEnabledAutomaticRestrictedAndDataSaverOn:
return true;
}
NOTREACHED();
return false;
}
private:
LazyImageLoadingFeatureStatus lazy_image_loading_feature_status_;
ScopedLazyImageLoadingForTest scoped_lazy_image_loading_for_test_;
ScopedAutomaticLazyImageLoadingForTest
scoped_automatic_lazy_image_loading_for_test_;
ScopedRestrictAutomaticLazyImageLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_image_loading_to_data_saver_for_test_;
ScopedDataSaverSetting scoped_data_saver_setting_;
};
TEST_P(LazyLoadImagesParamsTest, NearViewport) {
SimRequest main_resource("https://example.com/", "text/html");
SimSubresourceRequest css_resource("https://example.com/style.css",
"text/css");
SimSubresourceRequest eager_resource("https://example.com/eager.png",
"image/png");
base::Optional<SimSubresourceRequest> lazy_resource, auto_resource,
unset_resource;
lazy_resource.emplace("https://example.com/lazy.png", "image/png");
auto_resource.emplace("https://example.com/auto.png", "image/png");
unset_resource.emplace("https://example.com/unset.png", "image/png");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<head>
<link rel='stylesheet' href='https://example.com/style.css' />
</head>
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<img src='https://example.com/eager.png' loading='eager'
onload='console.log("eager onload");' />
<img src='https://example.com/lazy.png' loading='lazy'
onload='console.log("lazy onload");' />
<img src='https://example.com/auto.png' loading='auto'
onload='console.log("auto onload");' />
<img src='https://example.com/unset.png'
onload='console.log("unset onload");' />
</body>)HTML",
kViewportHeight + GetLoadingDistanceThreshold() - 100));
css_resource.Complete("img { width: 50px; height: 50px; }");
test::RunPendingTasks();
Vector<char> full_image = ReadTestImage();
ASSERT_LT(2048U, full_image.size());
Vector<char> partial_image;
partial_image.Append(full_image.data(), 2048U);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_FALSE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("eager onload"));
EXPECT_FALSE(ConsoleMessages().Contains("lazy onload"));
EXPECT_FALSE(ConsoleMessages().Contains("auto onload"));
EXPECT_FALSE(ConsoleMessages().Contains("unset onload"));
eager_resource.Complete(full_image);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_FALSE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("eager onload"));
EXPECT_FALSE(ConsoleMessages().Contains("lazy onload"));
// When automatic lazy image loading is enabled, images that are not
// explicitly `loading=lazy` will still block the window load event.
// Therefore, the following two images are either:
// a.) Fetched eagerly, when automatic lazy image loading is disabled
// - And therefore block the window load event
// b.) Fetched lazily, when automatic lazy image loading is enabled
// - And still block the window load event, if fetched before it fires.
EXPECT_FALSE(ConsoleMessages().Contains("auto onload"));
EXPECT_FALSE(ConsoleMessages().Contains("unset onload"));
auto_resource->Complete(full_image);
unset_resource->Complete(full_image);
Compositor().BeginFrame();
test::RunPendingTasks();
// The explicitly `loading=lazy` image never blocks the window load event.
if (RuntimeEnabledFeatures::LazyImageLoadingEnabled())
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
else
EXPECT_FALSE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("eager onload"));
EXPECT_FALSE(ConsoleMessages().Contains("lazy onload"));
EXPECT_TRUE(ConsoleMessages().Contains("auto onload"));
EXPECT_TRUE(ConsoleMessages().Contains("unset onload"));
lazy_resource->Complete(full_image);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("eager onload"));
EXPECT_TRUE(ConsoleMessages().Contains("lazy onload"));
EXPECT_TRUE(ConsoleMessages().Contains("auto onload"));
EXPECT_TRUE(ConsoleMessages().Contains("unset onload"));
switch (std::get<LazyImageLoadingFeatureStatus>(GetParam())) {
case LazyImageLoadingFeatureStatus::kEnabledAutomatic:
case LazyImageLoadingFeatureStatus::
kEnabledAutomaticRestrictedAndDataSaverOn:
EXPECT_TRUE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageMissingDimensionsForLazy));
break;
default:
break;
}
}
TEST_P(LazyLoadImagesParamsTest, FarFromViewport) {
SimRequest main_resource("https://example.com/", "text/html");
SimSubresourceRequest css_resource("https://example.com/style.css",
"text/css");
SimSubresourceRequest eager_resource("https://example.com/eager.png",
"image/png");
base::Optional<SimSubresourceRequest> lazy_resource, auto_resource,
unset_resource;
lazy_resource.emplace("https://example.com/lazy.png", "image/png");
auto_resource.emplace("https://example.com/auto.png", "image/png");
unset_resource.emplace("https://example.com/unset.png", "image/png");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<head>
<link rel='stylesheet' href='https://example.com/style.css' />
</head>
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<img src='https://example.com/eager.png' loading='eager'
onload='console.log("eager onload");' />
<img src='https://example.com/lazy.png' loading='lazy'
onload='console.log("lazy onload");' />
<img src='https://example.com/auto.png' loading='auto'
onload='console.log("auto onload");' />
<img src='https://example.com/unset.png'
onload='console.log("unset onload");' />
</body>)HTML",
kViewportHeight + GetLoadingDistanceThreshold() + 100));
css_resource.Complete("img { width: 50px; height: 50px; }");
test::RunPendingTasks();
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_FALSE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("eager onload"));
EXPECT_FALSE(ConsoleMessages().Contains("lazy onload"));
EXPECT_FALSE(ConsoleMessages().Contains("auto onload"));
EXPECT_FALSE(ConsoleMessages().Contains("unset onload"));
Vector<char> full_image = ReadTestImage();
ASSERT_LT(2048U, full_image.size());
Vector<char> partial_image;
partial_image.Append(full_image.data(), 2048U);
eager_resource.Complete(full_image);
Compositor().BeginFrame();
test::RunPendingTasks();
LazyImageLoadingFeatureStatus lazy_image_loading_feature_status =
std::get<LazyImageLoadingFeatureStatus>(GetParam());
if (lazy_image_loading_feature_status !=
LazyImageLoadingFeatureStatus::kEnabledAutomatic &&
lazy_image_loading_feature_status !=
LazyImageLoadingFeatureStatus::
kEnabledAutomaticRestrictedAndDataSaverOn) {
EXPECT_FALSE(ConsoleMessages().Contains("main body onload"));
}
EXPECT_TRUE(ConsoleMessages().Contains("eager onload"));
EXPECT_FALSE(ConsoleMessages().Contains("lazy onload"));
EXPECT_FALSE(ConsoleMessages().Contains("auto onload"));
EXPECT_FALSE(ConsoleMessages().Contains("unset onload"));
if (!RuntimeEnabledFeatures::LazyImageLoadingEnabled()) {
lazy_resource->Complete(full_image);
}
if (!IsAutomaticLazyImageLoadingExpected()) {
auto_resource->Complete(full_image);
unset_resource->Complete(full_image);
}
if (lazy_image_loading_feature_status !=
LazyImageLoadingFeatureStatus::kEnabledAutomatic &&
lazy_image_loading_feature_status !=
LazyImageLoadingFeatureStatus::
kEnabledAutomaticRestrictedAndDataSaverOn) {
Compositor().BeginFrame();
test::RunPendingTasks();
}
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("eager onload"));
if (RuntimeEnabledFeatures::LazyImageLoadingEnabled()) {
// Scroll down so that the images are near the viewport.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 150), mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_FALSE(ConsoleMessages().Contains("lazy onload"));
lazy_resource->Complete(full_image);
if (IsAutomaticLazyImageLoadingExpected()) {
EXPECT_FALSE(ConsoleMessages().Contains("auto onload"));
EXPECT_FALSE(ConsoleMessages().Contains("unset onload"));
auto_resource->Complete(full_image);
unset_resource->Complete(full_image);
}
Compositor().BeginFrame();
test::RunPendingTasks();
}
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("eager onload"));
EXPECT_TRUE(ConsoleMessages().Contains("lazy onload"));
EXPECT_TRUE(ConsoleMessages().Contains("auto onload"));
EXPECT_TRUE(ConsoleMessages().Contains("unset onload"));
switch (std::get<LazyImageLoadingFeatureStatus>(GetParam())) {
case LazyImageLoadingFeatureStatus::kEnabledAutomatic:
case LazyImageLoadingFeatureStatus::
kEnabledAutomaticRestrictedAndDataSaverOn:
EXPECT_TRUE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageMissingDimensionsForLazy));
break;
default:
break;
}
}
INSTANTIATE_TEST_SUITE_P(
LazyImageLoading,
LazyLoadImagesParamsTest,
::testing::Combine(
::testing::Values(LazyImageLoadingFeatureStatus::kDisabled,
LazyImageLoadingFeatureStatus::kEnabledExplicit,
LazyImageLoadingFeatureStatus::kEnabledAutomatic,
LazyImageLoadingFeatureStatus::
kEnabledAutomaticRestrictedAndDataSaverOff,
LazyImageLoadingFeatureStatus::
kEnabledAutomaticRestrictedAndDataSaverOn),
::testing::Values(WebEffectiveConnectionType::kTypeUnknown,
WebEffectiveConnectionType::kTypeOffline,
WebEffectiveConnectionType::kTypeSlow2G,
WebEffectiveConnectionType::kType2G,
WebEffectiveConnectionType::kType3G,
WebEffectiveConnectionType::kType4G)));
class LazyLoadAutomaticImagesTest : public SimTest {
public:
static constexpr int kViewportWidth = 800;
static constexpr int kViewportHeight = 600;
static constexpr int kLoadingDistanceThreshold = 300;
LazyLoadAutomaticImagesTest()
: scoped_lazy_image_loading_for_test_(true),
scoped_automatic_lazy_image_loading_for_test_(true),
scoped_restrict_automatic_lazy_image_loading_to_data_saver_for_test_(
false) {}
void SetUp() override {
GetNetworkStateNotifier().SetNetworkConnectionInfoOverride(
true /*on_line*/, kWebConnectionTypeWifi,
WebEffectiveConnectionType::kType4G, 1000 /*http_rtt_msec*/,
100 /*max_bandwidth_mbps*/);
SimTest::SetUp();
WebView().MainFrameViewWidget()->Resize(
gfx::Size(kViewportWidth, kViewportHeight));
Settings& settings = WebView().GetPage()->GetSettings();
settings.SetLazyImageLoadingDistanceThresholdPx4G(
kLoadingDistanceThreshold);
settings.SetLazyFrameLoadingDistanceThresholdPx4G(
kLoadingDistanceThreshold);
settings.SetLazyLoadEnabled(
RuntimeEnabledFeatures::LazyImageLoadingEnabled());
settings.SetLazyImageFirstKFullyLoad4G(0);
}
void SetLazyImageFirstKFullyLoad(int count) {
Settings& settings = WebView().GetPage()->GetSettings();
settings.SetLazyImageFirstKFullyLoad4G(count);
}
void LoadMainResourceWithImageFarFromViewport(const char* image_attributes) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<img src='https://example.com/image.png' %s
onload='console.log("image onload");' />
</body>)HTML",
kViewportHeight + kLoadingDistanceThreshold + 100, image_attributes));
Compositor().BeginFrame();
test::RunPendingTasks();
}
void TestLoadImageExpectingLazyLoad(const char* image_attributes) {
LoadMainResourceWithImageFarFromViewport(image_attributes);
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("image onload"));
}
void TestLoadImageExpectingLazyLoadWithoutPlaceholder(
const char* image_attributes) {
SimSubresourceRequest full_resource("https://example.com/image.png",
"image/png");
LoadMainResourceWithImageFarFromViewport(image_attributes);
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("image onload"));
// Scrolling down should trigger the fetch of the image.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, kLoadingDistanceThreshold + kViewportHeight),
mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
full_resource.Complete(ReadTestImage());
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("image onload"));
EXPECT_TRUE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageMissingDimensionsForLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeEager));
}
void TestLoadImageExpectingFullImageLoad(const char* image_attributes) {
SimSubresourceRequest full_resource("https://example.com/image.png",
"image/png");
LoadMainResourceWithImageFarFromViewport(image_attributes);
EXPECT_FALSE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("image onload"));
full_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("image onload"));
EXPECT_FALSE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageMissingDimensionsForLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeEager));
}
private:
ScopedLazyImageLoadingForTest scoped_lazy_image_loading_for_test_;
ScopedAutomaticLazyImageLoadingForTest
scoped_automatic_lazy_image_loading_for_test_;
ScopedRestrictAutomaticLazyImageLoadingToDataSaverForTest
scoped_restrict_automatic_lazy_image_loading_to_data_saver_for_test_;
ScopedLazyImageVisibleLoadTimeMetricsForTest
scoped_lazy_image_visible_load_time_metrics_for_test_ = true;
};
TEST_F(LazyLoadAutomaticImagesTest, AttributeChangedFromLazyToUnset) {
TestLoadImageExpectingLazyLoad("id='my_image' loading='lazy'");
// The body's load event should have already fired.
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("child frame element onload"));
Element* img = GetDocument().getElementById("my_image");
ASSERT_TRUE(img);
img->removeAttribute(html_names::kLoadingAttr);
test::RunPendingTasks();
EXPECT_FALSE(ConsoleMessages().Contains("image onload"));
SimSubresourceRequest img_resource("https://example.com/image.png",
"image/png");
// The img should still be deferred because automatic lazy loading is enabled.
// Scroll down until it is visible.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 150), mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
img_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("image onload"));
}
TEST_F(LazyLoadAutomaticImagesTest, AttributeChangedFromLazyToEager) {
TestLoadImageExpectingLazyLoad("id='my_image' loading='lazy'");
SimSubresourceRequest full_resource("https://example.com/image.png",
"image/png");
GetDocument()
.getElementById("my_image")
->setAttribute(html_names::kLoadingAttr, "eager");
Compositor().BeginFrame();
test::RunPendingTasks();
full_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("image onload"));
EXPECT_FALSE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageMissingDimensionsForLazy));
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeEager));
}
TEST_F(LazyLoadAutomaticImagesTest, AttributeChangedFromAutoToEager) {
TestLoadImageExpectingLazyLoad("id='my_image' loading='auto'");
SimSubresourceRequest full_resource("https://example.com/image.png",
"image/png");
GetDocument()
.getElementById("my_image")
->setAttribute(html_names::kLoadingAttr, "eager");
Compositor().BeginFrame();
test::RunPendingTasks();
full_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("image onload"));
EXPECT_TRUE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageMissingDimensionsForLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeEager));
}
TEST_F(LazyLoadAutomaticImagesTest, AttributeChangedFromUnsetToEager) {
TestLoadImageExpectingLazyLoad("id='my_image'");
SimSubresourceRequest full_resource("https://example.com/image.png",
"image/png");
GetDocument()
.getElementById("my_image")
->setAttribute(html_names::kLoadingAttr, "eager");
Compositor().BeginFrame();
test::RunPendingTasks();
full_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("image onload"));
EXPECT_TRUE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageMissingDimensionsForLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeEager));
}
TEST_F(LazyLoadAutomaticImagesTest, TinyImageWithLazyAttr) {
TestLoadImageExpectingLazyLoad("loading='lazy' width='1px' height='1px'");
EXPECT_FALSE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeEager));
}
TEST_F(LazyLoadAutomaticImagesTest, TinyImageViaStyleWithLazyAttr) {
TestLoadImageExpectingLazyLoad(
"loading='lazy' style='width:1px;height:1px;'");
EXPECT_FALSE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeLazy));
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kLazyLoadImageLoadingAttributeEager));
}
TEST_F(LazyLoadAutomaticImagesTest, TinyImageWidth1Height1) {
TestLoadImageExpectingFullImageLoad("width='1px' height='1px'");
}
TEST_F(LazyLoadAutomaticImagesTest, TinyImageWidth10Height10) {
TestLoadImageExpectingFullImageLoad("width='10px' height='10px'");
}
TEST_F(LazyLoadAutomaticImagesTest, TinyImageWidth1Height11) {
TestLoadImageExpectingLazyLoadWithoutPlaceholder("width='1px' height='11px'");
}
TEST_F(LazyLoadAutomaticImagesTest, TinyImageViaStyleWidth1Height1) {
TestLoadImageExpectingFullImageLoad("style='width:1px;height:1px;'");
}
TEST_F(LazyLoadAutomaticImagesTest, TinyImageViaStyleWidth10Height10) {
TestLoadImageExpectingFullImageLoad("style='width:10px;height:10px;'");
}
TEST_F(LazyLoadAutomaticImagesTest, TinyImageViaStyleWidth11Height1) {
TestLoadImageExpectingLazyLoadWithoutPlaceholder(
"style='width:11px;height:1px;'");
}
TEST_F(LazyLoadAutomaticImagesTest, FirstKImagesLoaded) {
SetLazyImageFirstKFullyLoad(1);
SimRequest main_resource("https://example.com/", "text/html");
SimSubresourceRequest img1("https://example.com/image.png?id=1", "image/png");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<img src='https://example.com/image.png?id=1'
onload='console.log("image id=1 onload");' />
<img src='https://example.com/image.png?id=2'
onload='console.log("image id=2 onload");' />
</body>)HTML",
kViewportHeight + kLoadingDistanceThreshold + 100));
Compositor().BeginFrame();
// One image should be loaded fully, even though it is below viewport.
img1.Complete(ReadTestImage());
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("image id=1 onload"));
EXPECT_FALSE(ConsoleMessages().Contains("image id=2 onload"));
// Scrolling down should trigger the fetch of the second image.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, kLoadingDistanceThreshold + kViewportHeight),
mojom::blink::ScrollType::kProgrammatic);
SimSubresourceRequest img2("https://example.com/image.png?id=2", "image/png");
Compositor().BeginFrame();
test::RunPendingTasks();
img2.Complete(ReadTestImage());
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("image id=1 onload"));
EXPECT_TRUE(ConsoleMessages().Contains("image id=2 onload"));
EXPECT_TRUE(ConsoleMessages().Contains(kLazyLoadEventsDeferredMessage));
}
TEST_F(LazyLoadAutomaticImagesTest, JavascriptCreatedImageFarFromViewport) {
SimRequest main_resource("https://example.com/", "text/html");
SimSubresourceRequest image_resource("https://example.com/image.png",
"image/png");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<script>
var my_image = new Image(50, 50);
my_image.onload = function() { console.log('my_image onload'); };
my_image.src = 'https://example.com/image.png';
document.body.appendChild(my_image);
</script>
</body>)HTML",
kViewportHeight + kLoadingDistanceThreshold + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_FALSE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("my_image onload"));
image_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("my_image onload"));
}
TEST_F(LazyLoadAutomaticImagesTest, JavascriptCreatedImageAddedAfterLoad) {
SimRequest main_resource("https://example.com/", "text/html");
SimSubresourceRequest image_resource("https://example.com/image.png",
"image/png");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<script>
var my_image = new Image(50, 50);
my_image.onload = function() {
console.log('my_image onload');
document.body.appendChild(this);
};
my_image.src = 'https://example.com/image.png';
</script>
</body>)HTML",
kViewportHeight + kLoadingDistanceThreshold + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_FALSE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("my_image onload"));
image_resource.Complete(ReadTestImage());
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("my_image onload"));
}
TEST_F(LazyLoadAutomaticImagesTest, ImageInsideLazyLoadedFrame) {
ScopedLazyFrameLoadingForTest scoped_lazy_frame_loading_for_test(true);
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<iframe src='https://example.com/child_frame.html' loading='lazy'
id='child_frame' width='300px' height='300px'
onload='console.log("child frame onload");'></iframe>
</body>)HTML",
kViewportHeight + kLoadingDistanceThreshold + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("child frame onload"));
SimRequest child_frame_resource("https://example.com/child_frame.html",
"text/html");
// Scroll down so that the iframe is near the viewport, but the images within
// it aren't near the viewport yet.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 150), mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
SimSubresourceRequest css_resource("https://example.com/style.css",
"text/css");
SimSubresourceRequest eager_resource("https://example.com/eager.png",
"image/png");
SimSubresourceRequest auto_resource("https://example.com/auto.png",
"image/png");
SimSubresourceRequest unset_resource("https://example.com/unset.png",
"image/png");
child_frame_resource.Complete(R"HTML(
<head>
<link rel='stylesheet' href='https://example.com/style.css' />
</head>
<body onload='window.parent.console.log("child body onload");'>
<div style='height: 100px;'></div>
<img src='https://example.com/eager.png' loading='eager'
onload='window.parent.console.log("eager onload");' />
<img src='https://example.com/lazy.png' loading='lazy'
onload='window.parent.console.log("lazy onload");' />
<img src='https://example.com/auto.png' loading='auto'
onload='window.parent.console.log("auto onload");' />
<img src='https://example.com/unset.png'
onload='window.parent.console.log("unset onload");' />
</body>)HTML");
css_resource.Complete("img { width: 50px; height: 50px; }");
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("child frame onload"));
EXPECT_FALSE(ConsoleMessages().Contains("child body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("eager onload"));
EXPECT_FALSE(ConsoleMessages().Contains("lazy onload"));
EXPECT_FALSE(ConsoleMessages().Contains("auto onload"));
EXPECT_FALSE(ConsoleMessages().Contains("unset onload"));
Vector<char> full_image = ReadTestImage();
ASSERT_LT(2048U, full_image.size());
Vector<char> partial_image;
partial_image.Append(full_image.data(), 2048U);
eager_resource.Complete(full_image);
auto_resource.Complete(full_image);
unset_resource.Complete(full_image);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child frame onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("eager onload"));
EXPECT_FALSE(ConsoleMessages().Contains("lazy onload"));
EXPECT_TRUE(ConsoleMessages().Contains("auto onload"));
EXPECT_TRUE(ConsoleMessages().Contains("unset onload"));
SimSubresourceRequest lazy_resource("https://example.com/lazy.png",
"image/png");
// Scroll down so that all the images in the iframe are near the viewport.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 250), mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
lazy_resource.Complete(full_image);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child frame onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("eager onload"));
EXPECT_TRUE(ConsoleMessages().Contains("lazy onload"));
EXPECT_TRUE(ConsoleMessages().Contains("auto onload"));
EXPECT_TRUE(ConsoleMessages().Contains("unset onload"));
}
TEST_F(LazyLoadAutomaticImagesTest, LazyLoadDisabledOnReload) {
String main_resource_html = String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<img src='https://example.com/image.png'
width='50px' height='50px'
onload='console.log("auto image onload");' />
<img src='https://example.com/image_lazy.png' loading='lazy'
width='50px' height='50px'
onload='console.log("auto image onload");' />
</body>)HTML",
LazyLoadAutomaticImagesTest::kViewportHeight +
LazyLoadAutomaticImagesTest::kLoadingDistanceThreshold + 100);
{
HistogramTester histogram_tester;
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(main_resource_html);
Compositor().BeginFrame();
test::RunPendingTasks();
// Scrolling down should trigger the fetch both the images, and record
// visible load time metrics.
SimSubresourceRequest auto_image("https://example.com/image.png",
"image/png");
SimSubresourceRequest lazy_image("https://example.com/image_lazy.png",
"image/png");
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, LazyLoadAutomaticImagesTest::kLoadingDistanceThreshold +
LazyLoadAutomaticImagesTest::kViewportHeight),
mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
auto_image.Complete(ReadTestImage());
lazy_image.Complete(ReadTestImage());
test::RunPendingTasks();
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 2);
}
// Reloading the page should load the image with loading=auto, but still defer
// the image with loading=lazy.
{
HistogramTester histogram_tester;
SimRequest main_resource("https://example.com/", "text/html");
SimSubresourceRequest auto_image("https://example.com/image.png",
"image/png");
MainFrame().StartReload(WebFrameLoadType::kReload);
main_resource.Complete(main_resource_html);
auto_image.Complete(ReadTestImage());
// Scrolling down should trigger the fetch deferred image, and record
// visible load time metrics for both images.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, LazyLoadAutomaticImagesTest::kLoadingDistanceThreshold +
LazyLoadAutomaticImagesTest::kViewportHeight),
mojom::blink::ScrollType::kProgrammatic);
SimSubresourceRequest lazy_image("https://example.com/image_lazy.png",
"image/png");
Compositor().BeginFrame();
test::RunPendingTasks();
Compositor().BeginFrame();
lazy_image.Complete(ReadTestImage());
test::RunPendingTasks();
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 2);
}
}
TEST_F(LazyLoadAutomaticImagesTest, AboveTheFoldImageLoadedBeforeVisible) {
HistogramTester histogram_tester;
// Since the image is above the fold, ensure that the image starts loading
// before it becomes visible. When automatic lazyloading is enabled, the main
// way that an above-the-fold image that's relevant to automatic lazyload
// (i.e. would have visible load time metrics recorded for it) can be finshed
// loading by the time it becomes visible is for it to be fully loaded as one
// of the first K images seen by the parser.
SetLazyImageFirstKFullyLoad(1);
SimRequest main_resource("https://example.com/", "text/html");
SimSubresourceRequest image_resource("https://example.com/image.png",
"image/png");
LoadURL("https://example.com/");
main_resource.Complete(
"<body><img src='https://example.com/image.png' /></body>");
image_resource.Complete(ReadTestImage());
// VisibleLoadTime should not have been recorded yet, since the image is not
// visible yet.
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 0);
Compositor().BeginFrame();
test::RunPendingTasks();
histogram_tester.ExpectTotalCount(
"Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold", 0);
histogram_tester.ExpectTotalCount(
"Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold", 0);
histogram_tester.ExpectUniqueSample(
"Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 0, 1);
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 0);
}
TEST_F(LazyLoadAutomaticImagesTest, AboveTheFoldImageVisibleBeforeLoaded) {
HistogramTester histogram_tester;
SimRequest main_resource("https://example.com/", "text/html");
SimSubresourceRequest image_resource("https://example.com/image.png",
"image/png");
LoadURL("https://example.com/");
main_resource.Complete(
"<body><img src='https://example.com/image.png' /></body>");
Compositor().BeginFrame();
test::RunPendingTasks();
// VisibleBeforeLoaded should have been recorded immediately when the image
// became visible.
histogram_tester.ExpectUniqueSample(
"Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold",
static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
// VisibleLoadTime should not have been recorded yet, since the image is not
// finished loading yet.
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 0);
image_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
histogram_tester.ExpectUniqueSample(
"Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold",
static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
histogram_tester.ExpectTotalCount(
"Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold", 0);
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 1);
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 0);
}
TEST_F(LazyLoadAutomaticImagesTest, BelowTheFoldImageLoadedBeforeVisible) {
HistogramTester histogram_tester;
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body>
<div style='height: %dpx;'></div>
<img src='https://example.com/image.png' />
</body>)HTML",
kViewportHeight + kLoadingDistanceThreshold + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
SimSubresourceRequest image_resource("https://example.com/image.png",
"image/png");
// Scroll down such that the image is within kLoadingDistanceThreshold of the
// viewport, but isn't visible yet.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 200), mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
image_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
// VisibleLoadTime should not have been recorded yet, since the image is not
// visible yet.
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 0);
// Scroll down such that the image is visible.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, kViewportHeight + kLoadingDistanceThreshold),
mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
histogram_tester.ExpectTotalCount(
"Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold", 0);
histogram_tester.ExpectTotalCount(
"Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold", 0);
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 0);
histogram_tester.ExpectUniqueSample(
"Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 0, 1);
}
TEST_F(LazyLoadAutomaticImagesTest, BelowTheFoldImageVisibleBeforeLoaded) {
HistogramTester histogram_tester;
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body>
<div style='height: %dpx;'></div>
<img src='https://example.com/image.png' />
</body>)HTML",
kViewportHeight + kLoadingDistanceThreshold + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
SimSubresourceRequest image_resource("https://example.com/image.png",
"image/png");
// Scroll down such that the image is visible.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, kViewportHeight + kLoadingDistanceThreshold),
mojom::blink::ScrollType::kProgrammatic);
Compositor().BeginFrame();
test::RunPendingTasks();
// VisibleBeforeLoaded should have been recorded immediately when the image
// became visible.
histogram_tester.ExpectUniqueSample(
"Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold",
static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
// VisibleLoadTime should not have been recorded yet, since the image is not
// finished loading yet.
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 0);
image_resource.Complete(ReadTestImage());
Compositor().BeginFrame();
test::RunPendingTasks();
histogram_tester.ExpectTotalCount(
"Blink.VisibleBeforeLoaded.LazyLoadImages.AboveTheFold", 0);
histogram_tester.ExpectUniqueSample(
"Blink.VisibleBeforeLoaded.LazyLoadImages.BelowTheFold",
static_cast<int>(WebEffectiveConnectionType::kType4G), 1);
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.AboveTheFold.4G", 0);
histogram_tester.ExpectTotalCount(
"Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold.4G", 1);
}
} // namespace
} // namespace blink