blob: 04403816f9d3d157239a10d6e68b26a10e6f7725 [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 "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
#include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/prerender/prerender_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/data_use_measurement/core/data_use_user_data.h"
#include "components/prerender/browser/prerender_handle.h"
#include "components/prerender/browser/prerender_manager.h"
#include "components/prerender/common/prerender_final_status.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/referrer.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/nqe/effective_connection_type.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
class LazyLoadBrowserTest : public InProcessBrowserTest {
protected:
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
void SetUp() override {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kLazyImageLoading,
{{"lazy_image_first_k_fully_load",
base::StringPrintf("%s:0,%s:0,%s:0,%s:0,%s:0,%s:0",
net::kEffectiveConnectionTypeUnknown,
net::kEffectiveConnectionTypeOffline,
net::kEffectiveConnectionTypeSlow2G,
net::kEffectiveConnectionType2G,
net::kEffectiveConnectionType3G,
net::kEffectiveConnectionType4G)}}}},
{});
InProcessBrowserTest::SetUp();
}
void EnableDataSaver(bool enabled) {
data_reduction_proxy::DataReductionProxySettings::
SetDataSaverEnabledForTesting(browser()->profile()->GetPrefs(),
enabled);
base::RunLoop().RunUntilIdle();
}
content::EvalJsResult WaitForElementLoad(content::WebContents* contents,
const char* element_id) {
return content::EvalJs(contents, base::StringPrintf(R"JS(
new Promise((resolve, reject) => {
let e = document.getElementById('%s');
if (loaded_ids.includes(e.id)) {
resolve(true);
} else {
e.addEventListener('load', function() {
resolve(true);
});
}
});)JS",
element_id));
}
content::EvalJsResult ScrollToAndWaitForElementLoad(
content::WebContents* contents,
const char* element_id) {
return content::EvalJs(contents, base::StringPrintf(R"JS(
new Promise((resolve, reject) => {
let e = document.getElementById('%s');
e.scrollIntoView();
if (loaded_ids.includes(e.id)) {
resolve(true);
} else {
e.addEventListener('load', function() {
resolve(true);
});
}
});)JS",
element_id));
}
content::EvalJsResult IsElementLoaded(content::WebContents* contents,
const char* element_id) {
return content::EvalJs(
contents, base::StringPrintf("loaded_ids.includes('%s');", element_id));
}
// Sets up test pages with in-viewport and below-viewport cross-origin frames.
void SetUpLazyLoadFrameTestPage() {
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
cross_origin_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
ASSERT_TRUE(cross_origin_server_.Start());
embedded_test_server()->RegisterRequestHandler(base::Bind(
[](uint16_t cross_origin_port,
const net::test_server::HttpRequest& request)
-> std::unique_ptr<net::test_server::HttpResponse> {
auto response =
std::make_unique<net::test_server::BasicHttpResponse>();
if (request.relative_url == "/mainpage.html") {
response->set_content(base::StringPrintf(
R"HTML(
<body>
<script>
let loaded_ids = new Array();
</script>
<iframe id="atf_auto" src="http://bar.com:%d/simple.html?auto"
width="100" height="100"
onload="loaded_ids.push(this.id); console.log(this.id);">
</iframe>
<iframe id="atf_lazy" src="http://bar.com:%d/simple.html?lazy"
width="100" height="100" loading="lazy"
onload="loaded_ids.push(this.id); console.log(this.id);">
</iframe>
<iframe id="atf_eager" src="http://bar.com:%d/simple.html?eager"
width="100" height="100" loading="eager"
onload="loaded_ids.push(this.id); console.log(this.id);">
</iframe>
<div style="height:11000px;"></div>
Below the viewport cross-origin iframes <br>
<iframe id="btf_auto" src="http://bar.com:%d/simple.html?auto&belowviewport"
width="100" height="100"
onload="loaded_ids.push(this.id); console.log(this.id);">
</iframe>
<iframe id="btf_lazy" src="http://bar.com:%d/simple.html?lazy&belowviewport"
width="100" height="100" loading="lazy"
onload="loaded_ids.push(this.id); console.log(this.id);">
</iframe>
<iframe id="btf_eager" src="http://bar.com:%d/simple.html?eager&belowviewport"
width="100" height="100" loading="eager"
onload="loaded_ids.push(this.id); console.log(this.id);">
</iframe>
</body>)HTML",
cross_origin_port, cross_origin_port, cross_origin_port,
cross_origin_port, cross_origin_port, cross_origin_port));
}
return response;
},
cross_origin_server_.port()));
ASSERT_TRUE(embedded_test_server()->Start());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
net::EmbeddedTestServer cross_origin_server_;
};
IN_PROC_BROWSER_TEST_F(LazyLoadBrowserTest, CSSBackgroundImageDeferred) {
EnableDataSaver(true);
ASSERT_TRUE(embedded_test_server()->Start());
base::HistogramTester histogram_tester;
ui_test_utils::NavigateToURLWithDisposition(
browser(),
embedded_test_server()->GetURL("/lazyload/css-background-image.html"),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
base::RunLoop().RunUntilIdle();
// Navigate away to finish the histogram recording.
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
// Verify that nothing is recorded for the image bucket.
EXPECT_GE(0, histogram_tester.GetBucketCount(
"DataUse.ContentType.UserTrafficKB",
data_use_measurement::DataUseUserData::IMAGE));
}
IN_PROC_BROWSER_TEST_F(LazyLoadBrowserTest, CSSPseudoBackgroundImageLoaded) {
EnableDataSaver(true);
ASSERT_TRUE(embedded_test_server()->Start());
base::HistogramTester histogram_tester;
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/lazyload/css-pseudo-background-image.html"));
base::RunLoop().RunUntilIdle();
// Navigate away to finish the histogram recording.
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
// Verify that the image bucket has substantial kilobytes recorded.
EXPECT_GE(30 /* KB */, histogram_tester.GetBucketCount(
"DataUse.ContentType.UserTrafficKB",
data_use_measurement::DataUseUserData::IMAGE));
}
IN_PROC_BROWSER_TEST_F(LazyLoadBrowserTest,
LazyLoadImage_DeferredAndLoadedOnScroll) {
EnableDataSaver(true);
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url(embedded_test_server()->GetURL("/lazyload/img.html"));
auto* contents = browser()->OpenURL(content::OpenURLParams(
test_url, content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_TYPED, false));
ASSERT_TRUE(content::WaitForLoadStop(contents));
EXPECT_EQ(true, WaitForElementLoad(contents, "atf_auto"));
EXPECT_EQ(true, WaitForElementLoad(contents, "atf_lazy"));
EXPECT_EQ(true, WaitForElementLoad(contents, "atf_eager"));
EXPECT_EQ(true, WaitForElementLoad(contents, "btf_eager"));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(false, IsElementLoaded(contents, "btf_auto"));
EXPECT_EQ(false, IsElementLoaded(contents, "btf_lazy"));
EXPECT_EQ(true, ScrollToAndWaitForElementLoad(contents, "btf_auto"));
EXPECT_EQ(true, ScrollToAndWaitForElementLoad(contents, "btf_lazy"));
}
IN_PROC_BROWSER_TEST_F(LazyLoadBrowserTest,
LazyLoadFrame_DeferredAndLoadedOnScroll) {
EnableDataSaver(true);
SetUpLazyLoadFrameTestPage();
GURL test_url(embedded_test_server()->GetURL("/mainpage.html"));
ui_test_utils::NavigateToURL(browser(), test_url);
auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(true, WaitForElementLoad(contents, "atf_auto"));
EXPECT_EQ(true, WaitForElementLoad(contents, "atf_lazy"));
EXPECT_EQ(true, WaitForElementLoad(contents, "atf_eager"));
EXPECT_EQ(true, WaitForElementLoad(contents, "btf_eager"));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(false, IsElementLoaded(contents, "btf_auto"));
EXPECT_EQ(false, IsElementLoaded(contents, "btf_lazy"));
EXPECT_EQ(true, ScrollToAndWaitForElementLoad(contents, "btf_auto"));
EXPECT_EQ(true, ScrollToAndWaitForElementLoad(contents, "btf_lazy"));
}
// Tests that need to verify lazyload should be disabled in certain cases.
class LazyLoadDisabledBrowserTest : public LazyLoadBrowserTest {
public:
enum class ExpectedLazyLoadAction {
kOff, // All iframes and images should load.
kExplicitOnly, // Only the above viewport elements and below viewport
// explicit lazyload elements will load.
};
content::WebContents* CreateBackgroundWebContents(Browser* browser,
const GURL& url) {
return browser->OpenURL(content::OpenURLParams(
url, content::Referrer(), WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui::PAGE_TRANSITION_TYPED, false));
}
void VerifyLazyLoadFrameBehavior(
content::WebContents* web_contents,
ExpectedLazyLoadAction expected_lazy_load_action) {
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
EXPECT_EQ(true, WaitForElementLoad(web_contents, "atf_auto"));
EXPECT_EQ(true, WaitForElementLoad(web_contents, "atf_lazy"));
EXPECT_EQ(true, WaitForElementLoad(web_contents, "atf_eager"));
EXPECT_EQ(true, WaitForElementLoad(web_contents, "btf_eager"));
base::RunLoop().RunUntilIdle();
switch (expected_lazy_load_action) {
case ExpectedLazyLoadAction::kOff:
EXPECT_EQ(true, IsElementLoaded(web_contents, "btf_auto"));
EXPECT_EQ(true, IsElementLoaded(web_contents, "btf_lazy"));
break;
case ExpectedLazyLoadAction::kExplicitOnly:
EXPECT_EQ(true, IsElementLoaded(web_contents, "btf_auto"));
EXPECT_EQ(false, IsElementLoaded(web_contents, "btf_lazy"));
break;
}
}
void VerifyLazyLoadImageBehavior(
content::WebContents* web_contents,
ExpectedLazyLoadAction expected_lazy_load_action) {
ASSERT_TRUE(content::WaitForLoadStop(web_contents));
EXPECT_EQ(true, WaitForElementLoad(web_contents, "atf_auto"));
EXPECT_EQ(true, WaitForElementLoad(web_contents, "atf_lazy"));
EXPECT_EQ(true, WaitForElementLoad(web_contents, "atf_eager"));
EXPECT_EQ(true, WaitForElementLoad(web_contents, "btf_eager"));
base::RunLoop().RunUntilIdle();
switch (expected_lazy_load_action) {
case ExpectedLazyLoadAction::kOff:
EXPECT_EQ(true, IsElementLoaded(web_contents, "btf_auto"));
EXPECT_EQ(true, IsElementLoaded(web_contents, "btf_lazy"));
break;
case ExpectedLazyLoadAction::kExplicitOnly:
EXPECT_EQ(true, IsElementLoaded(web_contents, "btf_auto"));
EXPECT_EQ(false, IsElementLoaded(web_contents, "btf_lazy"));
break;
}
}
};
IN_PROC_BROWSER_TEST_F(LazyLoadDisabledBrowserTest,
LazyLoadImage_DisabledInBackgroundTab) {
EnableDataSaver(true);
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url(embedded_test_server()->GetURL("/lazyload/img.html"));
auto* web_contents = CreateBackgroundWebContents(browser(), test_url);
VerifyLazyLoadImageBehavior(web_contents, ExpectedLazyLoadAction::kOff);
}
IN_PROC_BROWSER_TEST_F(LazyLoadDisabledBrowserTest,
LazyLoadImage_DisabledInIncognito) {
EnableDataSaver(true);
ASSERT_TRUE(embedded_test_server()->Start());
GURL test_url(embedded_test_server()->GetURL("/lazyload/img.html"));
auto* incognito_web_contents = browser()->OpenURL(content::OpenURLParams(
test_url, content::Referrer(), WindowOpenDisposition::OFF_THE_RECORD,
ui::PAGE_TRANSITION_TYPED, false));
VerifyLazyLoadImageBehavior(incognito_web_contents,
ExpectedLazyLoadAction::kExplicitOnly);
}
IN_PROC_BROWSER_TEST_F(LazyLoadDisabledBrowserTest,
LazyLoadFrame_DisabledInBackgroundTab) {
EnableDataSaver(true);
SetUpLazyLoadFrameTestPage();
GURL test_url(embedded_test_server()->GetURL("/mainpage.html"));
auto* web_contents = CreateBackgroundWebContents(browser(), test_url);
VerifyLazyLoadFrameBehavior(web_contents, ExpectedLazyLoadAction::kOff);
}
IN_PROC_BROWSER_TEST_F(LazyLoadDisabledBrowserTest,
LazyLoadFrame_DisabledInIncognito) {
EnableDataSaver(true);
SetUpLazyLoadFrameTestPage();
GURL test_url(embedded_test_server()->GetURL("/mainpage.html"));
auto* incognito_web_contents = browser()->OpenURL(content::OpenURLParams(
test_url, content::Referrer(), WindowOpenDisposition::OFF_THE_RECORD,
ui::PAGE_TRANSITION_TYPED, false));
VerifyLazyLoadFrameBehavior(incognito_web_contents,
ExpectedLazyLoadAction::kExplicitOnly);
}
class LazyLoadPrerenderBrowserTest
: public prerender::test_utils::PrerenderInProcessBrowserTest {
public:
void SetUpOnMainThread() override {
prerender::test_utils::PrerenderInProcessBrowserTest::SetUpOnMainThread();
prerender::PrerenderManager::SetMode(
prerender::PrerenderManager::PRERENDER_MODE_NOSTATE_PREFETCH);
}
void EnableDataSaver(bool enabled) {
data_reduction_proxy::DataReductionProxySettings::
SetDataSaverEnabledForTesting(browser()->profile()->GetPrefs(),
enabled);
base::RunLoop().RunUntilIdle();
}
};
IN_PROC_BROWSER_TEST_F(LazyLoadPrerenderBrowserTest, ImagesIgnored) {
EnableDataSaver(true);
UseHttpsSrcServer();
std::unique_ptr<prerender::test_utils::TestPrerender> test_prerender =
prerender_contents_factory()->ExpectPrerenderContents(
prerender::FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);
std::unique_ptr<prerender::PrerenderHandle> prerender_handle =
GetPrerenderManager()->AddPrerenderFromOmnibox(
src_server()->GetURL("/lazyload/img.html"),
GetSessionStorageNamespace(), gfx::Size(640, 480));
ASSERT_EQ(prerender_handle->contents(), test_prerender->contents());
test_prerender->WaitForStop();
for (const auto* url :
{"/lazyload/img.html", "/lazyload/images/fruit1.jpg?auto",
"/lazyload/images/fruit1.jpg?lazy", "/lazyload/images/fruit1.jpg?eager",
"/lazyload/images/fruit2.jpg?auto", "/lazyload/images/fruit2.jpg?lazy",
"/lazyload/images/fruit2.jpg?eager"}) {
WaitForRequestCount(src_server()->GetURL(url), 1);
}
}