blob: 68a34e25ed5d2f092b4064ba2ed7b70bef0b6b38 [file] [log] [blame]
// Copyright 2017 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 "content/public/common/browser_side_navigation_policy.h"
#include "content/public/test/browser_test.h"
#include "headless/public/devtools/domains/network.h"
#include "headless/public/devtools/domains/page.h"
#include "headless/public/headless_devtools_client.h"
#include "headless/public/util/expedited_dispatcher.h"
#include "headless/public/util/generic_url_request_job.h"
#include "headless/public/util/url_fetcher.h"
#include "headless/test/headless_browser_test.h"
#include "net/url_request/url_request_job_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using testing::ContainerEq;
namespace headless {
namespace {
// Keep in sync with X_DevTools_Request_Id defined in HTTPNames.json5.
const char kDevtoolsRequestId[] = "X-DevTools-Request-Id";
} // namespace
namespace {
class RequestIdCorrelationProtocolHandler
: public net::URLRequestJobFactory::ProtocolHandler {
public:
explicit RequestIdCorrelationProtocolHandler(
scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner)
: test_delegate_(new TestDelegate(this)),
dispatcher_(new ExpeditedDispatcher(io_thread_task_runner)) {}
~RequestIdCorrelationProtocolHandler() override {}
struct Response {
Response() {}
Response(const std::string& body, const std::string& mime_type)
: data("HTTP/1.1 200 OK\r\nContent-Type: " + mime_type + "\r\n\r\n" +
body) {}
std::string data;
};
void InsertResponse(const std::string& url, const Response& response) {
response_map_[url] = response;
}
const Response* GetResponse(const std::string& url) const {
std::map<std::string, Response>::const_iterator find_it =
response_map_.find(url);
if (find_it == response_map_.end())
return nullptr;
return &find_it->second;
}
class MockURLFetcher : public URLFetcher {
public:
explicit MockURLFetcher(
const RequestIdCorrelationProtocolHandler* protocol_handler)
: protocol_handler_(protocol_handler) {}
~MockURLFetcher() override {}
// URLFetcher implementation:
void StartFetch(const GURL& url,
const std::string& method,
const net::HttpRequestHeaders& request_headers,
const std::string& devtools_request_id,
ResultListener* result_listener) override {
const Response* response = protocol_handler_->GetResponse(url.spec());
if (!response)
result_listener->OnFetchStartError(net::ERR_FILE_NOT_FOUND);
// The header used for correlation should not be sent to the fetcher.
EXPECT_FALSE(request_headers.HasHeader(kDevtoolsRequestId));
result_listener->OnFetchCompleteExtractHeaders(
url, 200, response->data.c_str(), response->data.size());
}
private:
const RequestIdCorrelationProtocolHandler* protocol_handler_;
DISALLOW_COPY_AND_ASSIGN(MockURLFetcher);
};
class TestDelegate : public GenericURLRequestJob::Delegate {
public:
explicit TestDelegate(RequestIdCorrelationProtocolHandler* protocol_handler)
: protocol_handler_(protocol_handler) {}
~TestDelegate() override {}
// GenericURLRequestJob::Delegate implementation:
bool BlockOrRewriteRequest(
const GURL& url,
const std::string& devtools_id,
const std::string& method,
const std::string& referrer,
GenericURLRequestJob::RewriteCallback callback) override {
protocol_handler_->url_to_devtools_id_[url.spec()] = devtools_id;
return false;
}
const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
const GURL& url,
const std::string& devtools_id,
const std::string& method,
const net::HttpRequestHeaders& request_headers) override {
return nullptr;
}
void OnResourceLoadComplete(const GURL& final_url,
const std::string& devtools_id,
const std::string& mime_type,
int http_response_code) override {}
private:
RequestIdCorrelationProtocolHandler* protocol_handler_;
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
};
// net::URLRequestJobFactory::ProtocolHandler implementation::
net::URLRequestJob* MaybeCreateJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override {
return new GenericURLRequestJob(
request, network_delegate, dispatcher_.get(),
base::MakeUnique<MockURLFetcher>(this), test_delegate_.get());
}
std::map<std::string, std::string> url_to_devtools_id_;
private:
std::unique_ptr<TestDelegate> test_delegate_;
std::unique_ptr<ExpeditedDispatcher> dispatcher_;
std::map<std::string, Response> response_map_;
DISALLOW_COPY_AND_ASSIGN(RequestIdCorrelationProtocolHandler);
};
const char* kIndexHtml = R"(
<html>
<head>
<link rel="stylesheet" type="text/css" href="style1.css">
<link rel="stylesheet" type="text/css" href="style2.css">
</head>
<body>Hello.
</body>
</html>)";
const char* kStyle1 = R"(
.border {
border: 1px solid #000;
})";
const char* kStyle2 = R"(
.border {
border: 2px solid #fff;
})";
} // namespace
class ProtocolHandlerRequestIdCorrelationTest
: public HeadlessAsyncDevTooledBrowserTest,
public network::Observer,
public page::Observer {
public:
void RunDevTooledTest() override {
if (content::IsBrowserSideNavigationEnabled()) {
// TODO: get this working with PlzNavigate.
// See discussion on https://codereview.chromium.org/2695923010/
FinishAsynchronousTest();
return;
}
EXPECT_TRUE(embedded_test_server()->Start());
devtools_client_->GetPage()->AddObserver(this);
devtools_client_->GetPage()->Enable();
base::RunLoop run_loop;
devtools_client_->GetNetwork()->AddObserver(this);
devtools_client_->GetNetwork()->Enable(run_loop.QuitClosure());
base::MessageLoop::ScopedNestableTaskAllower nest_loop(
base::MessageLoop::current());
run_loop.Run();
devtools_client_->GetPage()->Navigate("http://foo.com/index.html");
}
ProtocolHandlerMap GetProtocolHandlers() override {
ProtocolHandlerMap protocol_handlers;
std::unique_ptr<RequestIdCorrelationProtocolHandler> http_handler(
new RequestIdCorrelationProtocolHandler(browser()->BrowserIOThread()));
http_handler_ = http_handler.get();
http_handler_->InsertResponse("http://foo.com/index.html",
{kIndexHtml, "text/html"});
http_handler_->InsertResponse("http://foo.com/style1.css",
{kStyle1, "text/css"});
http_handler_->InsertResponse("http://foo.com/style2.css",
{kStyle2, "text/css"});
protocol_handlers[url::kHttpScheme] = std::move(http_handler);
return protocol_handlers;
}
// network::Observer implementation:
void OnRequestWillBeSent(
const network::RequestWillBeSentParams& params) override {
url_to_devtools_id_[params.GetRequest()->GetUrl()] = params.GetRequestId();
EXPECT_FALSE(params.GetRequest()->GetHeaders()->HasKey(kDevtoolsRequestId));
}
// page::Observer implementation:
void OnLoadEventFired(const page::LoadEventFiredParams& params) override {
// Make sure that our protocol handler saw the same url : devtools ids as
// our OnRequestWillBeSent event listener did.
EXPECT_THAT(url_to_devtools_id_,
ContainerEq(http_handler_->url_to_devtools_id_));
EXPECT_EQ(3u, url_to_devtools_id_.size());
FinishAsynchronousTest();
}
private:
std::map<std::string, std::string> url_to_devtools_id_;
RequestIdCorrelationProtocolHandler* http_handler_; // NOT OWNED
};
HEADLESS_ASYNC_DEVTOOLED_TEST_F(ProtocolHandlerRequestIdCorrelationTest);
} // namespace headless