blob: 272698549d5a577cf21ba8596027e512dab585c4 [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 <memory>
#include "base/feature_list.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_param_associator.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h"
#include "chrome/browser/data_use_measurement/page_load_capping/chrome_page_load_capping_features.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/infobars/core/confirm_infobar_delegate.h"
#include "components/infobars/core/infobar.h"
#include "components/infobars/core/infobar_delegate.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browsing_data_remover.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/http/http_status_code.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 "net/test/embedded_test_server/simple_connection_listener.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/mojom/loader/pause_subresource_loading_handle.mojom.h"
namespace {
const base::FilePath::CharType kDocRoot[] =
FILE_PATH_LITERAL("chrome/test/data/data_use_measurement");
const char kImagePrefix[] = "/image";
} // namespace
class PageLoadCappingBrowserTest : public InProcessBrowserTest {
public:
PageLoadCappingBrowserTest()
: https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
void PostToSelf() {
EXPECT_FALSE(waiting_);
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
}
void WaitForRequest() {
EXPECT_FALSE(waiting_);
waiting_ = true;
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
run_loop_.reset();
}
GURL GetURL(const std::string& url_string) {
return https_test_server_.GetURL(url_string);
}
void NavigateToHeavyPage() { NavigateToHeavyPageAnchor(std::string()); }
void NavigateToHeavyPageAnchor(const std::string& anchor) {
NavigateToHeavyPageAnchorInBrowser(browser(), anchor);
}
void NavigateToHeavyPageAnchorInBrowser(Browser* browser,
const std::string& anchor) {
ui_test_utils::NavigateToURL(
browser, GetURL(std::string("/page_capping.html").append(anchor)));
}
size_t images_attempted() const { return images_attempted_; }
content::WebContents* contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
size_t InfoBarCount() const {
return InfoBarService::FromWebContents(contents())->infobar_count();
}
void ClickInfoBarLink() {
InfoBarService::FromWebContents(contents())
->infobar_at(0)
->delegate()
->AsConfirmInfoBarDelegate()
->LinkClicked(WindowOpenDisposition::CURRENT_TAB);
}
private:
void SetUp() override {
std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
scoped_refptr<base::FieldTrial> trial =
base::FieldTrialList::CreateFieldTrial("TrialName1", "GroupName1");
std::map<std::string, std::string> feature_parameters = {
{"PageCapMiB", "0"},
{"PageFuzzingKiB", "0"},
{"OptOutStoreDisabled", "true"}};
base::FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams(
"TrialName1", "GroupName1", feature_parameters);
feature_list->RegisterFieldTrialOverride(
data_use_measurement::page_load_capping::features::kDetectingHeavyPages
.name,
base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial.get());
scoped_feature_list_.InitWithFeatureList(std::move(feature_list));
https_test_server_.RegisterRequestHandler(base::BindRepeating(
&PageLoadCappingBrowserTest::HandleRequest, base::Unretained(this)));
https_test_server_.ServeFilesFromSourceDirectory(base::FilePath(kDocRoot));
ASSERT_TRUE(https_test_server_.Start());
InProcessBrowserTest::SetUp();
}
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
// Check if this matches the image requests from the test suite.
if (!StartsWith(request.relative_url, kImagePrefix,
base::CompareCase::SENSITIVE)) {
return nullptr;
}
// This request should match "/image.*" for this test suite.
images_attempted_++;
// Return a 404. This is expected in the test, but embedded test server will
// create warnings when serving its own 404 responses.
std::unique_ptr<net::test_server::BasicHttpResponse> not_found_response =
std::make_unique<net::test_server::BasicHttpResponse>();
not_found_response->set_code(net::HTTP_NOT_FOUND);
if (waiting_) {
run_loop_->QuitWhenIdle();
waiting_ = false;
}
return not_found_response;
}
net::EmbeddedTestServer https_test_server_;
size_t images_attempted_ = 0u;
bool waiting_ = false;
std::unique_ptr<base::RunLoop> run_loop_;
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(PageLoadCappingBrowserTest, PageLoadCappingBlocksLoads) {
// Tests that subresource loading can be blocked from the browser process.
// Load a mostly empty page.
NavigateToHeavyPage();
// Pause subresource loading.
ClickInfoBarLink();
// Adds images to the page. They should not be allowed to load.
// Running this 20 times makes 20 round trips to the renderer, making it very
// likely the earliest request would have made it to the network by the time
// all of the calls have been made.
for (size_t i = 0; i < 20; ++i) {
std::string create_image_script =
std::string(
"var image = document.createElement('img'); "
"document.body.appendChild(image); image.src = '")
.append(kImagePrefix)
.append(base::IntToString(i))
.append(".png';");
EXPECT_TRUE(content::ExecuteScript(contents(), create_image_script));
}
// No images should be loaded as subresource loading was paused.
EXPECT_EQ(0u, images_attempted());
}
IN_PROC_BROWSER_TEST_F(PageLoadCappingBrowserTest,
PageLoadCappingBlocksLoadsAndResume) {
// Tests that after triggerring subresource pausing, resuming allows deferred
// requests to be initiated.
// Load a mostly empty page.
NavigateToHeavyPage();
// Pause subresource loading.
ClickInfoBarLink();
// Adds an image to the page. It should not be allowed to load at first.
// PageLoadCappingBlocksLoads tests that it is not loaded more robustly
std::string create_image_script =
std::string(
"var image = document.createElement('img'); "
"document.body.appendChild(image); image.src = '")
.append(kImagePrefix)
.append(".png';");
ASSERT_TRUE(content::ExecuteScript(contents(), create_image_script));
// Previous image should be allowed to load now.
ClickInfoBarLink();
// An image should be fetched because subresource loading was paused then
// resumed.
if (images_attempted() < 1u)
WaitForRequest();
EXPECT_EQ(1u, images_attempted());
}
IN_PROC_BROWSER_TEST_F(PageLoadCappingBrowserTest, PageLoadCappingAllowLoads) {
// Tests that the image request loads normally when the page has not been
// paused.
// Load a mostly empty page.
NavigateToHeavyPage();
// Adds an image to the page. It should be allowed to load.
std::string create_image_script =
std::string(
"var image = document.createElement('img'); "
"document.body.appendChild(image); image.src = '")
.append(kImagePrefix)
.append(".png';");
ASSERT_TRUE(content::ExecuteScript(contents(), create_image_script));
// An image should be fetched because subresource loading was never paused.
if (images_attempted() < 1u)
WaitForRequest();
EXPECT_EQ(1u, images_attempted());
}
IN_PROC_BROWSER_TEST_F(PageLoadCappingBrowserTest,
PageLoadCappingBlockNewFrameLoad) {
// Tests that the image request loads normally when the page has not been
// paused.
// Load a mostly empty page.
NavigateToHeavyPage();
// Pause subresource loading.
ClickInfoBarLink();
content::TestNavigationObserver load_observer(contents());
// Adds an image to the page. It should be allowed to load.
std::string create_iframe_script = std::string(
"var iframe = document.createElement('iframe');"
"var html = '<body>NewFrame</body>';"
"iframe.src = 'data:text/html;charset=utf-8,' + encodeURI(html);"
"document.body.appendChild(iframe);");
content::ExecuteScriptAsync(contents(), create_iframe_script);
// Make sure the DidFinishNavigation occured.
load_observer.Wait();
PostToSelf();
size_t j = 0;
for (auto* frame : contents()->GetAllFrames()) {
for (size_t i = 0; i < 20; ++i) {
std::string create_image_script =
std::string(
"var image = document.createElement('img'); "
"document.body.appendChild(image); image.src = '")
.append(GetURL(std::string(kImagePrefix)
.append(base::IntToString(++j))
.append(".png';"))
.spec());
EXPECT_TRUE(content::ExecuteScript(frame, create_image_script));
}
}
// An image should not be fetched because subresource loading was paused in
// both frames.
EXPECT_EQ(0u, images_attempted());
}
IN_PROC_BROWSER_TEST_F(PageLoadCappingBrowserTest,
PageLoadCappingBlockNewFrameLoadResume) {
// Tests that the image request loads normally when the page has not been
// paused.
// Load a mostly empty page.
NavigateToHeavyPage();
// Pause subresource loading.
ClickInfoBarLink();
content::TestNavigationObserver load_observer(contents());
// Adds an image to the page. It should be allowed to load.
std::string create_iframe_script = std::string(
"var iframe = document.createElement('iframe');"
"var html = '<body>NewFrame</body>';"
"iframe.src = 'data:text/html;charset=utf-8,' + encodeURI(html);"
"document.body.appendChild(iframe);");
content::ExecuteScriptAsync(contents(), create_iframe_script);
// Make sure the DidFinishNavigation occured.
load_observer.Wait();
PostToSelf();
for (auto* frame : contents()->GetAllFrames()) {
if (contents()->GetMainFrame() == frame)
continue;
std::string create_image_script =
std::string(
"var image = document.createElement('img'); "
"document.body.appendChild(image); image.src = '")
.append(GetURL(std::string(kImagePrefix).append(".png';")).spec());
ASSERT_TRUE(content::ExecuteScript(frame, create_image_script));
}
// An image should not be fetched because subresource loading was paused in
// both frames.
EXPECT_EQ(0u, images_attempted());
// Previous image should be allowed to load now.
ClickInfoBarLink();
// An image should be fetched because subresource loading was resumed.
if (images_attempted() < 1u)
WaitForRequest();
EXPECT_EQ(1u, images_attempted());
}
IN_PROC_BROWSER_TEST_F(PageLoadCappingBrowserTest,
PageLoadCappingInfobarShownAfterSamePageNavigation) {
// Verifies that same page navigations do not dismiss the InfoBar.
// Load a page.
NavigateToHeavyPage();
ASSERT_EQ(1u, InfoBarCount());
infobars::InfoBar* infobar =
InfoBarService::FromWebContents(contents())->infobar_at(0);
// Navigate on the page to an anchor.
NavigateToHeavyPageAnchor("#anchor");
EXPECT_EQ(1u, InfoBarCount());
EXPECT_EQ(infobar,
InfoBarService::FromWebContents(contents())->infobar_at(0));
}
IN_PROC_BROWSER_TEST_F(PageLoadCappingBrowserTest,
PageLoadCappingInfoBarNotShownAfterBlacklisted) {
// Verifies the blacklist prevents over-showing the InfoBar.
// Load a page and ignore the InfoBar.
NavigateToHeavyPage();
ASSERT_EQ(1u, InfoBarCount());
// Load a page and ignore the InfoBar.
NavigateToHeavyPage();
ASSERT_EQ(1u, InfoBarCount());
// Load a page and due to session policy blacklisting, the InfoBar should not
// show.
NavigateToHeavyPage();
ASSERT_EQ(0u, InfoBarCount());
}
IN_PROC_BROWSER_TEST_F(PageLoadCappingBrowserTest,
NavigationDataRemovedFromBlacklist) {
// Verifies that clearing browsing data resets blacklist rules.
// Load a page and ignore the InfoBar.
NavigateToHeavyPage();
ASSERT_EQ(1u, InfoBarCount());
// Load a page and ignore the InfoBar.
NavigateToHeavyPage();
ASSERT_EQ(1u, InfoBarCount());
// Load a page and due to session policy blacklisting, the InfoBar should not
// show.
NavigateToHeavyPage();
ASSERT_EQ(0u, InfoBarCount());
// Clear the navigation history.
content::BrowserContext::GetBrowsingDataRemover(browser()->profile())
->Remove(base::Time(), base::Time::Max(),
ChromeBrowsingDataRemoverDelegate::DATA_TYPE_HISTORY,
content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB);
// After clearing history, the InfoBar should be allowed again.
NavigateToHeavyPage();
ASSERT_EQ(1u, InfoBarCount());
}
IN_PROC_BROWSER_TEST_F(PageLoadCappingBrowserTest, IncognitoTest) {
// Verifies the InfoBar is not shown in incognito.
auto* browser = CreateIncognitoBrowser();
// Navigate to the page.
NavigateToHeavyPageAnchorInBrowser(browser, std::string());
DCHECK_EQ(0u, InfoBarService::FromWebContents(
browser->tab_strip_model()->GetActiveWebContents())
->infobar_count());
}