0

Rewrite tests for blocking of popup windows in JS framework

These were the only tests that required the native network interceptor,
so it's gone now.

Change-Id: I06d6c2fd8a57c68654a075b308bd95c600b24ef2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6015099
Commit-Queue: Andrey Kosyakov <caseq@chromium.org>
Reviewed-by: Peter Kvitek <kvitekp@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1382590}
This commit is contained in:
Andrey Kosyakov
2024-11-13 21:35:42 +00:00
committed by Chromium LUCI CQ
parent f5c0373f9f
commit e520b5485b
8 changed files with 100 additions and 352 deletions

@ -620,8 +620,6 @@ test("headless_browsertests") {
"test/headless_origin_trials_browsertest.cc",
"test/headless_test_launcher.cc",
"test/headless_web_contents_browsertest.cc",
"test/test_network_interceptor.cc",
"test/test_network_interceptor.h",
]
if (enable_printing && enable_pdf) {

@ -106,6 +106,15 @@
}
}
/**
* Returns the array of requested URLs.
*
* @return {!Array<string>}
*/
requestedUrls() {
return this.requestedUrls_;
}
/**
* Checks if specified urls have been requested.
*

@ -0,0 +1,2 @@
Tests pop ups can be blocked.
PASS

@ -0,0 +1,53 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(async function(testRunner) {
const {session, dp} = await testRunner.startBlank(
'Tests pop ups can be blocked.');
const {sessionId} =
(await testRunner.browserP().Target.attachToBrowserTarget({})).result;
const bp = (new TestRunner.Session(testRunner, sessionId)).protocol;
const HttpInterceptor = await testRunner.loadScript(
'../helpers/http-interceptor.js');
const httpInterceptor = await (new HttpInterceptor(testRunner, bp)).init();
httpInterceptor.setDisableRequestedUrlsLogging(true);
httpInterceptor.addResponse('http://example.com/index.html', `
<script>
const win = window.open('/page2.html');
if (!win) {
console.error('ready');
}
win.addEventListener('load', () => console.log('ready'));
</script>`);
httpInterceptor.addResponse('http://example.com/page2.html',
`<body>Page 2</body>`);
dp.Runtime.enable();
const readyPromise = dp.Runtime.onceConsoleAPICalled();
session.navigate('http://example.com/index.html');
const message = (await readyPromise).params.args[0].value;
if (message !== 'ready') {
testRunner.fail(`Unexpected console message: ${message}`);
}
const requestedUrls = new Set(httpInterceptor.requestedUrls());
if (!requestedUrls.has('http://example.com/index.html')) {
testRunner.fail('Main page not requested');
}
const seenPopupRequest = requestedUrls.has(
'http://example.com/page2.html');
if (seenPopupRequest === !testRunner.params('blockingNewWebContents')) {
testRunner.log('PASS');
} else {
const message = seenPopupRequest ? 'Popup blocked but requested'
: 'Popup not blocked but not requested';
testRunner.log(Array.from(requestedUrls), `FAIL: ${message}: `);
}
testRunner.completeTest();
})

@ -22,6 +22,8 @@
#include "headless/test/headless_browser_test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/network_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/switches.h"
namespace headless {
@ -238,6 +240,13 @@ void HeadlessProtocolBrowserTest::FinishTest() {
HEADLESS_PROTOCOL_TEST_CLASS(HeadlessProtocolBrowserTest, TEST_NAME, \
SCRIPT_NAME)
#define HEADLESS_PROTOCOL_TEST_P(CLASS_NAME, TEST_NAME, SCRIPT_NAME) \
IN_PROC_BROWSER_TEST_P(CLASS_NAME, TEST_NAME) { \
test_folder_ = "/protocol/"; \
script_name_ = SCRIPT_NAME; \
RunTest(); \
}
// Headless-specific tests
HEADLESS_PROTOCOL_TEST(VirtualTimeBasics, "emulation/virtual-time-basics.js")
HEADLESS_PROTOCOL_TEST(VirtualTimeInterrupt,
@ -471,6 +480,33 @@ INSTANTIATE_TEST_SUITE_P(
HEADLESS_DEVTOOLED_TEST_P(HeadlessAllowedVideoCodecsTest);
class PopupWindowOpenTest : public HeadlessProtocolBrowserTest,
public testing::WithParamInterface<bool> {
protected:
PopupWindowOpenTest() = default;
void CustomizeHeadlessBrowserContext(
HeadlessBrowserContext::Builder& builder) override {
builder.SetBlockNewWebContents(ShouldBlockNewWebContents());
}
base::Value::Dict GetPageUrlExtraParams() override {
base::Value::Dict params;
params.Set("blockingNewWebContents", ShouldBlockNewWebContents());
return params;
}
bool ShouldBlockNewWebContents() const { return GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
PopupWindowOpenTest,
::testing::Bool());
HEADLESS_PROTOCOL_TEST_P(PopupWindowOpenTest,
Open,
"sanity/popup-window-open.js")
class HeadlessProtocolBrowserTestWithoutSiteIsolation
: public HeadlessProtocolBrowserTest {
public:

@ -31,7 +31,6 @@
#include "headless/test/headless_browser_test.h"
#include "headless/test/headless_browser_test_utils.h"
#include "headless/test/headless_devtooled_browsertest.h"
#include "headless/test/test_network_interceptor.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/switches.h"
@ -628,94 +627,6 @@ class CookiesEnabled : public HeadlessDevTooledBrowserTest {
HEADLESS_DEVTOOLED_TEST_F(CookiesEnabled);
namespace {
const char* kPageWhichOpensAWindow = R"(
<html>
<body>
<script>
const win = window.open('/page2.html');
if (!win)
console.error('ready');
win.addEventListener('load', () => console.log('ready'));
</script>
</body>
</html>
)";
const char* kPage2 = R"(
<html>
<body>
Page 2.
</body>
</html>
)";
} // namespace
class WebContentsOpenTest : public HeadlessDevTooledBrowserTest {
public:
void PreRunAsynchronousTest() override {
interceptor_ = std::make_unique<TestNetworkInterceptor>();
}
void PostRunAsynchronousTest() override { interceptor_.reset(); }
void RunDevTooledTest() override {
DCHECK(interceptor_);
interceptor_->InsertResponse("http://foo.com/index.html",
{kPageWhichOpensAWindow, "text/html"});
interceptor_->InsertResponse("http://foo.com/page2.html",
{kPage2, "text/html"});
devtools_client_.AddEventHandler(
"Runtime.consoleAPICalled",
base::BindRepeating(&WebContentsOpenTest::OnConsoleAPICalled,
base::Unretained(this)));
SendCommandSync(devtools_client_, "Runtime.enable");
devtools_client_.SendCommand("Page.navigate",
Param("url", "http://foo.com/index.html"));
}
virtual void OnConsoleAPICalled(const base::Value::Dict& params) {}
protected:
std::unique_ptr<TestNetworkInterceptor> interceptor_;
};
class DontBlockWebContentsOpenTest : public WebContentsOpenTest {
public:
void CustomizeHeadlessBrowserContext(
HeadlessBrowserContext::Builder& builder) override {
builder.SetBlockNewWebContents(false);
}
void OnConsoleAPICalled(const base::Value::Dict& params) override {
EXPECT_THAT(
interceptor_->urls_requested(),
ElementsAre("http://foo.com/index.html", "http://foo.com/page2.html"));
FinishAsynchronousTest();
}
};
HEADLESS_DEVTOOLED_TEST_F(DontBlockWebContentsOpenTest);
class BlockWebContentsOpenTest : public WebContentsOpenTest {
public:
void CustomizeHeadlessBrowserContext(
HeadlessBrowserContext::Builder& builder) override {
builder.SetBlockNewWebContents(true);
}
void OnConsoleAPICalled(const base::Value::Dict& params) override {
EXPECT_THAT(interceptor_->urls_requested(),
ElementsAre("http://foo.com/index.html"));
FinishAsynchronousTest();
}
};
HEADLESS_DEVTOOLED_TEST_F(BlockWebContentsOpenTest);
// Regression test for crbug.com/1385982.
class BlockDevToolsEmbedding : public HeadlessDevTooledBrowserTest {
protected:

@ -1,196 +0,0 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "headless/test/test_network_interceptor.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/redirect_util.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace headless {
namespace {
class RedirectLoader : public network::mojom::URLLoader {
public:
RedirectLoader(TestNetworkInterceptor::Impl* interceptor_impl,
content::URLLoaderInterceptor::RequestParams* request,
TestNetworkInterceptor::Response* response,
const std::string& url)
: interceptor_impl_(interceptor_impl),
receiver_(this, std::move(request->receiver)),
client_(std::move(request->client)),
url_request_(request->url_request),
response_(response),
url_(request->url_request.url) {
receiver_.set_disconnect_handler(
base::BindOnce([](RedirectLoader* self) { delete self; }, this));
NotifyRedirect(std::move(url));
}
void FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const std::optional<GURL>& new_url) override;
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override {}
void PauseReadingBodyFromNet() override {}
void ResumeReadingBodyFromNet() override {}
private:
void NotifyRedirect(const std::string& location) {
auto redirect_info = net::RedirectInfo::ComputeRedirectInfo(
url_request_.method, url_request_.url, url_request_.site_for_cookies,
net::RedirectInfo::FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT,
url_request_.referrer_policy, url_request_.referrer.spec(),
response_->headers->response_code(), url_.Resolve(location),
net::RedirectUtil::GetReferrerPolicyHeader(response_->headers.get()),
false /* insecure_scheme_was_upgraded */, true);
auto head = network::mojom::URLResponseHead::New();
head->request_time = base::Time::Now();
head->response_time = base::Time::Now();
head->content_length = 0;
head->encoded_data_length = 0;
head->headers = response_->headers;
url_ = redirect_info.new_url;
method_ = redirect_info.new_method;
client_->OnReceiveRedirect(redirect_info, std::move(head));
}
const raw_ptr<TestNetworkInterceptor::Impl> interceptor_impl_;
mojo::Receiver<network::mojom::URLLoader> receiver_;
mojo::Remote<network::mojom::URLLoaderClient> client_;
network::ResourceRequest url_request_;
raw_ptr<TestNetworkInterceptor::Response> response_;
GURL url_;
std::string method_;
};
} // namespace
class TestNetworkInterceptor::Impl {
public:
explicit Impl(base::WeakPtr<TestNetworkInterceptor> interceptor)
: interceptor_(std::move(interceptor)) {}
void InsertResponse(std::string url, Response response) {
response_map_.emplace(StripFragment(url),
std::make_unique<Response>(std::move(response)));
}
Response* FindResponse(const std::string& method, const std::string& url) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&TestNetworkInterceptor::LogRequest,
interceptor_, method, url));
auto it = response_map_.find(StripFragment(url));
return it == response_map_.end() ? nullptr : it->second.get();
}
bool RequestHandler(content::URLLoaderInterceptor::RequestParams* request) {
Response* response = FindResponse(request->url_request.method,
request->url_request.url.spec());
if (!response)
return false;
std::string location;
if (response->headers->IsRedirect(&location)) {
new RedirectLoader(this, request, response, location);
return true;
}
content::URLLoaderInterceptor::WriteResponse(
response->raw_headers, response->body, request->client.get());
return true;
}
private:
std::string StripFragment(std::string url) {
GURL::Replacements replacements;
replacements.ClearRef();
return GURL(url).ReplaceComponents(replacements).spec();
}
std::map<std::string, std::unique_ptr<Response>> response_map_;
base::WeakPtr<TestNetworkInterceptor> interceptor_;
};
void RedirectLoader::FollowRedirect(
const std::vector<std::string>& removed_headers /* unused */,
const net::HttpRequestHeaders& modified_headers /* unused */,
const net::HttpRequestHeaders& modified_cors_exempt_headers /* unused */,
const std::optional<GURL>& new_url) {
response_ = interceptor_impl_->FindResponse(method_, url_.spec());
CHECK(response_) << "No content for " << url_.spec();
std::string location;
if (response_->headers->IsRedirect(&location)) {
NotifyRedirect(location);
return;
}
content::URLLoaderInterceptor::WriteResponse(response_->raw_headers,
response_->body, client_.get());
delete this;
}
TestNetworkInterceptor::Response::Response(const std::string data) {
static const char kHeaderDelimiter[] = "\r\n\r\n";
size_t end_of_headers = data.find(kHeaderDelimiter);
CHECK(end_of_headers != std::string::npos);
raw_headers = data.substr(0, end_of_headers);
body = data.substr(end_of_headers + strlen(kHeaderDelimiter));
headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(raw_headers));
}
TestNetworkInterceptor::Response::Response(const std::string body,
const std::string& mime_type)
: raw_headers("HTTP/1.1 200 OK\r\nContent-Type: " + mime_type),
body(std::move(body)) {
headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(raw_headers));
}
TestNetworkInterceptor::Response::Response(const Response& r) = default;
TestNetworkInterceptor::Response::Response(Response&& r) = default;
TestNetworkInterceptor::Response::~Response() {}
TestNetworkInterceptor::TestNetworkInterceptor() {
impl_ = std::make_unique<Impl>(weak_factory_.GetWeakPtr());
interceptor_ = std::make_unique<content::URLLoaderInterceptor>(
base::BindRepeating(&TestNetworkInterceptor::Impl::RequestHandler,
base::Unretained(impl_.get())));
}
TestNetworkInterceptor::~TestNetworkInterceptor() {
content::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE, impl_.release());
interceptor_.reset();
}
void TestNetworkInterceptor::InsertResponse(std::string url,
Response response) {
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&Impl::InsertResponse, base::Unretained(impl_.get()),
std::move(url), std::move(response)));
}
void TestNetworkInterceptor::LogRequest(std::string method, std::string url) {
urls_requested_.emplace_back(std::move(url));
methods_requested_.emplace_back(std::move(method));
}
} // namespace headless

@ -1,65 +0,0 @@
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef HEADLESS_TEST_TEST_NETWORK_INTERCEPTOR_H_
#define HEADLESS_TEST_TEST_NETWORK_INTERCEPTOR_H_
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "content/public/test/url_loader_interceptor.h"
namespace headless {
class TestNetworkInterceptor {
public:
class Impl;
TestNetworkInterceptor();
TestNetworkInterceptor(const TestNetworkInterceptor&) = delete;
TestNetworkInterceptor& operator=(const TestNetworkInterceptor&) = delete;
~TestNetworkInterceptor();
struct Response {
Response() = delete;
Response(const std::string data);
Response(const std::string body, const std::string& mime_type);
Response(const Response& r);
Response(Response&& r);
~Response();
scoped_refptr<net::HttpResponseHeaders> headers;
std::string raw_headers;
std::string body;
};
void InsertResponse(std::string url, Response response);
const std::vector<std::string>& urls_requested() const {
return urls_requested_;
}
const std::vector<std::string>& methods_requested() const {
return methods_requested_;
}
private:
void LogRequest(std::string method, std::string url);
std::vector<std::string> urls_requested_;
std::vector<std::string> methods_requested_;
std::unique_ptr<Impl> impl_;
std::unique_ptr<content::URLLoaderInterceptor> interceptor_;
base::WeakPtrFactory<TestNetworkInterceptor> weak_factory_{this};
};
} // namespace headless
#endif // HEADLESS_TEST_TEST_NETWORK_INTERCEPTOR_H_