blob: 772afca5332aa8efaa32192960e292471f57ce58 [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 "content/browser/service_worker/service_worker_main_resource_loader.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "content/browser/loader/navigation_loader_interceptor.h"
#include "content/browser/loader/single_request_url_loader_factory.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
#include "content/browser/service_worker/fake_service_worker.h"
#include "content/browser/service_worker/service_worker_container_host.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "mojo/public/cpp/system/data_pipe_utils.h"
#include "net/ssl/ssl_info.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_url_loader_client.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_impl.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
namespace content {
namespace service_worker_main_resource_loader_unittest {
void ReceiveRequestHandler(
SingleRequestURLLoaderFactory::RequestHandler* out_handler,
SingleRequestURLLoaderFactory::RequestHandler handler) {
*out_handler = std::move(handler);
}
blink::mojom::FetchAPIResponsePtr OkResponse(
blink::mojom::SerializedBlobPtr blob_body,
network::mojom::FetchResponseSource response_source,
base::Time response_time,
std::string cache_storage_cache_name) {
auto response = blink::mojom::FetchAPIResponse::New();
response->status_code = 200;
response->status_text = "OK";
response->response_type = network::mojom::FetchResponseType::kDefault;
response->response_source = response_source;
response->response_time = response_time;
response->cache_storage_cache_name = cache_storage_cache_name;
response->blob = std::move(blob_body);
if (response->blob) {
response->headers.emplace("Content-Length",
base::NumberToString(response->blob->size));
}
return response;
}
blink::mojom::FetchAPIResponsePtr ErrorResponse() {
auto response = blink::mojom::FetchAPIResponse::New();
response->status_code = 0;
response->response_type = network::mojom::FetchResponseType::kDefault;
response->error = blink::mojom::ServiceWorkerResponseError::kPromiseRejected;
return response;
}
blink::mojom::FetchAPIResponsePtr RedirectResponse(
const std::string& redirect_location_header) {
auto response = blink::mojom::FetchAPIResponse::New();
response->status_code = 301;
response->status_text = "Moved Permanently";
response->response_type = network::mojom::FetchResponseType::kDefault;
response->headers["Location"] = redirect_location_header;
return response;
}
blink::mojom::FetchAPIResponsePtr HeadersResponse(
const base::flat_map<std::string, std::string>& headers) {
auto response = blink::mojom::FetchAPIResponse::New();
response->status_code = 200;
response->status_text = "OK";
response->headers.insert(headers.begin(), headers.end());
return response;
}
// Simulates a service worker handling fetch events. The response can be
// customized via RespondWith* functions.
class FetchEventServiceWorker : public FakeServiceWorker {
public:
FetchEventServiceWorker(
EmbeddedWorkerTestHelper* helper,
FakeEmbeddedWorkerInstanceClient* embedded_worker_instance_client,
BrowserTaskEnvironment* task_environment)
: FakeServiceWorker(helper),
task_environment_(task_environment),
embedded_worker_instance_client_(embedded_worker_instance_client) {}
~FetchEventServiceWorker() override = default;
// Tells this worker to dispatch a fetch event 1s after the fetch event is
// received.
void DispatchAfter1sDelay() {
response_mode_ = ResponseMode::kDispatchAfter1sDelay;
}
// Tells this worker to respond to fetch events with the specified blob.
void RespondWithBlob(blink::mojom::SerializedBlobPtr blob) {
response_mode_ = ResponseMode::kBlob;
blob_body_ = std::move(blob);
}
// Tells this worker to respond to fetch events with the specified stream.
void RespondWithStream(
mojo::PendingReceiver<blink::mojom::ServiceWorkerStreamCallback>
callback_receiver,
mojo::ScopedDataPipeConsumerHandle consumer_handle) {
response_mode_ = ResponseMode::kStream;
stream_handle_ = blink::mojom::ServiceWorkerStreamHandle::New();
stream_handle_->callback_receiver = std::move(callback_receiver);
stream_handle_->stream = std::move(consumer_handle);
}
// Tells this worker to respond to fetch events with network fallback.
// i.e., simulate the service worker not calling respondWith().
void RespondWithFallback() {
response_mode_ = ResponseMode::kFallbackResponse;
}
// Tells this worker to respond to fetch events with an error response.
void RespondWithError() { response_mode_ = ResponseMode::kErrorResponse; }
// Tells this worker to respond to fetch events with a response containing
// specific headers.
void RespondWithHeaders(
const base::flat_map<std::string, std::string>& headers) {
response_mode_ = ResponseMode::kHeaders;
headers_ = headers;
}
// Tells this worker to respond to fetch events with the redirect response.
void RespondWithRedirectResponse(const GURL& new_url) {
response_mode_ = ResponseMode::kRedirect;
redirected_url_ = new_url;
}
// Tells this worker to simulate failure to dispatch the fetch event to the
// service worker.
void FailToDispatchFetchEvent() {
response_mode_ = ResponseMode::kFailFetchEventDispatch;
}
// Tells this worker to simulate "early response", where the respondWith()
// promise resolves before the waitUntil() promise. In this mode, the
// helper sets the response mode to "early response", which simulates the
// promise passed to respondWith() resolving before the waitUntil() promise
// resolves. In this mode, the helper will respond to fetch events
// immediately, but will not finish the fetch event until FinishWaitUntil() is
// called.
void RespondEarly() { response_mode_ = ResponseMode::kEarlyResponse; }
void FinishWaitUntil() {
std::move(finish_callback_)
.Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
base::RunLoop().RunUntilIdle();
}
// Tells this worker to wait for FinishRespondWith() to be called before
// providing the response to the fetch event.
void DeferResponse() { response_mode_ = ResponseMode::kDeferredResponse; }
void FinishRespondWith() {
response_callback_->OnResponse(
OkResponse(nullptr /* blob_body */, response_source_, response_time_,
cache_storage_cache_name_),
blink::mojom::ServiceWorkerFetchEventTiming::New());
response_callback_.FlushForTesting();
std::move(finish_callback_)
.Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
}
void ReadRequestBody(std::string* out_string) {
ASSERT_TRUE(request_body_);
const std::vector<network::DataElement>* elements =
request_body_->elements();
// So far this test expects a single bytes element.
ASSERT_EQ(1u, elements->size());
const network::DataElement& element = elements->front();
ASSERT_EQ(network::DataElement::Tag::kBytes, element.type());
*out_string =
std::string(element.As<network::DataElementBytes>().AsStringPiece());
}
void RunUntilFetchEvent() {
if (has_received_fetch_event_)
return;
base::RunLoop run_loop;
quit_closure_for_fetch_event_ = run_loop.QuitClosure();
run_loop.Run();
}
void SetResponseSource(network::mojom::FetchResponseSource source) {
response_source_ = source;
}
void SetCacheStorageCacheName(std::string cache_name) {
cache_storage_cache_name_ = cache_name;
}
void SetResponseTime(base::Time time) { response_time_ = time; }
void WaitForTransferInstalledScript() {
embedded_worker_instance_client_->WaitForTransferInstalledScript();
}
protected:
void DispatchFetchEventForMainResource(
blink::mojom::DispatchFetchEventParamsPtr params,
mojo::PendingRemote<blink::mojom::ServiceWorkerFetchResponseCallback>
pending_response_callback,
blink::mojom::ServiceWorker::DispatchFetchEventForMainResourceCallback
finish_callback) override {
// Basic checks on DispatchFetchEvent parameters.
EXPECT_TRUE(params->request->is_main_resource_load);
has_received_fetch_event_ = true;
if (params->request->body)
request_body_ = params->request->body;
auto timing = blink::mojom::ServiceWorkerFetchEventTiming::New();
auto now = base::TimeTicks::Now();
timing->dispatch_event_time = now;
timing->respond_with_settled_time = now;
mojo::Remote<blink::mojom::ServiceWorkerFetchResponseCallback>
response_callback(std::move(pending_response_callback));
switch (response_mode_) {
case ResponseMode::kDefault:
FakeServiceWorker::DispatchFetchEventForMainResource(
std::move(params), response_callback.Unbind(),
std::move(finish_callback));
break;
case ResponseMode::kDispatchAfter1sDelay:
task_environment_->AdvanceClock(base::TimeDelta::FromSeconds(1));
FakeServiceWorker::DispatchFetchEventForMainResource(
std::move(params), response_callback.Unbind(),
std::move(finish_callback));
break;
case ResponseMode::kBlob:
response_callback->OnResponse(
OkResponse(std::move(blob_body_), response_source_, response_time_,
cache_storage_cache_name_),
std::move(timing));
std::move(finish_callback)
.Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
break;
case ResponseMode::kStream:
response_callback->OnResponseStream(
OkResponse(nullptr /* blob_body */, response_source_,
response_time_, cache_storage_cache_name_),
std::move(stream_handle_), std::move(timing));
std::move(finish_callback)
.Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
break;
case ResponseMode::kFallbackResponse:
response_callback->OnFallback(std::move(timing));
std::move(finish_callback)
.Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
break;
case ResponseMode::kErrorResponse:
response_callback->OnResponse(ErrorResponse(), std::move(timing));
std::move(finish_callback)
.Run(blink::mojom::ServiceWorkerEventStatus::REJECTED);
break;
case ResponseMode::kFailFetchEventDispatch:
// Simulate failure by stopping the worker before the event finishes.
// This causes ServiceWorkerVersion::StartRequest() to call its error
// callback, which triggers ServiceWorkerMainResourceLoader's dispatch
// failed behavior.
embedded_worker_instance_client_->host()->OnStopped();
// Finish the event by calling |finish_callback|.
// This is the Mojo callback for
// blink::mojom::ServiceWorker::DispatchFetchEventForMainResource().
// If this is not called, Mojo will complain. In production code,
// ServiceWorkerContextClient would call this when it aborts all
// callbacks after an unexpected stop.
std::move(finish_callback)
.Run(blink::mojom::ServiceWorkerEventStatus::ABORTED);
break;
case ResponseMode::kDeferredResponse:
finish_callback_ = std::move(finish_callback);
response_callback_ = std::move(response_callback);
// Now the caller must call FinishRespondWith() to finish the event.
break;
case ResponseMode::kEarlyResponse:
finish_callback_ = std::move(finish_callback);
response_callback->OnResponse(
OkResponse(nullptr /* blob_body */, response_source_,
response_time_, cache_storage_cache_name_),
std::move(timing));
// Now the caller must call FinishWaitUntil() to finish the event.
break;
case ResponseMode::kRedirect:
response_callback->OnResponse(RedirectResponse(redirected_url_.spec()),
std::move(timing));
std::move(finish_callback)
.Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
break;
case ResponseMode::kHeaders:
response_callback->OnResponse(HeadersResponse(headers_),
std::move(timing));
std::move(finish_callback)
.Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
break;
}
if (quit_closure_for_fetch_event_)
std::move(quit_closure_for_fetch_event_).Run();
}
private:
enum class ResponseMode {
kDefault,
kDispatchAfter1sDelay,
kBlob,
kStream,
kFallbackResponse,
kErrorResponse,
kFailFetchEventDispatch,
kDeferredResponse,
kEarlyResponse,
kRedirect,
kHeaders
};
BrowserTaskEnvironment* const task_environment_;
ResponseMode response_mode_ = ResponseMode::kDefault;
scoped_refptr<network::ResourceRequestBody> request_body_;
// For ResponseMode::kBlob.
blink::mojom::SerializedBlobPtr blob_body_;
// For ResponseMode::kStream.
blink::mojom::ServiceWorkerStreamHandlePtr stream_handle_;
// For ResponseMode::kEarlyResponse and kDeferredResponse.
blink::mojom::ServiceWorker::DispatchFetchEventForMainResourceCallback
finish_callback_;
mojo::Remote<blink::mojom::ServiceWorkerFetchResponseCallback>
response_callback_;
// For ResponseMode::kRedirect.
GURL redirected_url_;
// For ResponseMode::kHeaders
base::flat_map<std::string, std::string> headers_;
bool has_received_fetch_event_ = false;
base::OnceClosure quit_closure_for_fetch_event_;
FakeEmbeddedWorkerInstanceClient* const embedded_worker_instance_client_;
network::mojom::FetchResponseSource response_source_ =
network::mojom::FetchResponseSource::kUnspecified;
std::string cache_storage_cache_name_;
base::Time response_time_;
DISALLOW_COPY_AND_ASSIGN(FetchEventServiceWorker);
};
// Returns typical response info for a resource load that went through a service
// worker.
network::mojom::URLResponseHeadPtr CreateResponseInfoFromServiceWorker() {
auto head = network::mojom::URLResponseHead::New();
head->was_fetched_via_service_worker = true;
head->was_fallback_required_by_service_worker = false;
head->url_list_via_service_worker = std::vector<GURL>();
head->response_type = network::mojom::FetchResponseType::kDefault;
head->cache_storage_cache_name = std::string();
head->did_service_worker_navigation_preload = false;
return head;
}
const char kHistogramMainResourceFetchEvent[] =
"ServiceWorker.FetchEvent.MainResource.Status";
// ServiceWorkerMainResourceLoaderTest is for testing the handling of requests
// by a service worker via ServiceWorkerMainResourceLoader.
//
// Of course, no actual service worker runs in the unit test, it is simulated
// via EmbeddedWorkerTestHelper receiving IPC messages from the browser and
// responding as if a service worker is running in the renderer.
class ServiceWorkerMainResourceLoaderTest : public testing::Test {
public:
ServiceWorkerMainResourceLoaderTest()
: task_environment_(BrowserTaskEnvironment::IO_MAINLOOP,
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~ServiceWorkerMainResourceLoaderTest() override = default;
void SetUp() override {
helper_ = std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath());
// Create an active service worker.
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = GURL("https://example.com/");
registration_ = CreateNewServiceWorkerRegistration(
helper_->context()->registry(), options);
version_ = CreateNewServiceWorkerVersion(
helper_->context()->registry(), registration_.get(),
GURL("https://example.com/service_worker.js"),
blink::mojom::ScriptType::kClassic);
std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> records;
records.push_back(WriteToDiskCacheSync(
GetStorageControl(), version_->script_url(), {} /* headers */,
"I'm the body", "I'm the meta data"));
version_->script_cache_map()->SetResources(records);
version_->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration_->SetActiveVersion(version_);
// Make the registration findable via storage functions.
registration_->set_last_update_check(base::Time::Now());
base::Optional<blink::ServiceWorkerStatusCode> status;
base::RunLoop run_loop;
registry()->StoreRegistration(
registration_.get(), version_.get(),
ReceiveServiceWorkerStatus(&status, run_loop.QuitClosure()));
run_loop.Run();
ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
// Set up custom fakes to let tests customize how to respond to fetch
// events.
auto* client =
helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
helper_.get());
service_worker_ =
helper_->AddNewPendingServiceWorker<FetchEventServiceWorker>(
helper_.get(), client, &task_environment_);
// Wait for main script response is set to |version| because
// ServiceWorkerMainResourceLoader needs the main script response to
// create a response. The main script response is set when the first
// TransferInstalledScript().
{
base::Optional<blink::ServiceWorkerStatusCode> status;
base::RunLoop loop;
version_->StartWorker(
ServiceWorkerMetrics::EventType::UNKNOWN,
ReceiveServiceWorkerStatus(&status, loop.QuitClosure()));
loop.Run();
ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
service_worker_->WaitForTransferInstalledScript();
}
}
ServiceWorkerRegistry* registry() { return helper_->context()->registry(); }
mojo::Remote<storage::mojom::ServiceWorkerStorageControl>&
GetStorageControl() {
return helper_->context()->GetStorageControl();
}
// Starts a request. After calling this, the request is ongoing and the
// caller can use functions like client_.RunUntilComplete() to wait for
// completion.
void StartRequest(std::unique_ptr<network::ResourceRequest> request) {
// Create a ServiceWorkerContainerHost and simulate what
// ServiceWorkerControlleeRequestHandler does to assign it a controller.
if (!container_host_) {
container_host_ = CreateContainerHostForWindow(
helper_->mock_render_process_id(),
/*is_parent_frame_secure=*/true, helper_->context()->AsWeakPtr(),
&container_endpoints_);
container_host_->UpdateUrls(request->url,
net::SiteForCookies::FromUrl(request->url),
url::Origin::Create(request->url));
container_host_->AddMatchingRegistration(registration_.get());
container_host_->SetControllerRegistration(
registration_, /*notify_controllerchange=*/false);
}
// Create a ServiceWorkerMainResourceLoader.
loader_ = std::make_unique<ServiceWorkerMainResourceLoader>(
base::BindOnce(&ServiceWorkerMainResourceLoaderTest::Fallback,
base::Unretained(this)),
container_host_,
base::WrapRefCounted<URLLoaderFactoryGetter>(
helper_->context()->loader_factory_getter()));
// Load |request.url|.
loader_->StartRequest(*request, loader_remote_.BindNewPipeAndPassReceiver(),
client_.CreateRemote());
}
// The |fallback_callback| passed to the ServiceWorkerMainResourceLoader in
// StartRequest().
void Fallback(bool reset_subresource_loader_params) {
did_call_fallback_callback_ = true;
reset_subresource_loader_params_ = reset_subresource_loader_params;
if (quit_closure_for_fallback_callback_)
std::move(quit_closure_for_fallback_callback_).Run();
}
// Runs until the ServiceWorkerMainResourceLoader created in StartRequest()
// calls the |fallback_callback| given to it. The argument passed to
// |fallback_callback| is saved in |reset_subresurce_loader_params_|.
void RunUntilFallbackCallback() {
if (did_call_fallback_callback_)
return;
base::RunLoop run_loop;
quit_closure_for_fallback_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
void ExpectResponseInfo(
const network::mojom::URLResponseHead& info,
const network::mojom::URLResponseHead& expected_info) {
EXPECT_EQ(expected_info.was_fetched_via_service_worker,
info.was_fetched_via_service_worker);
EXPECT_EQ(expected_info.was_fallback_required_by_service_worker,
info.was_fallback_required_by_service_worker);
EXPECT_EQ(expected_info.url_list_via_service_worker,
info.url_list_via_service_worker);
EXPECT_EQ(expected_info.response_type, info.response_type);
EXPECT_EQ(expected_info.response_time, info.response_time);
EXPECT_FALSE(info.load_timing.service_worker_start_time.is_null());
EXPECT_FALSE(info.load_timing.service_worker_ready_time.is_null());
EXPECT_FALSE(info.load_timing.service_worker_fetch_start.is_null());
EXPECT_FALSE(
info.load_timing.service_worker_respond_with_settled.is_null());
EXPECT_LE(info.load_timing.service_worker_start_time,
info.load_timing.service_worker_ready_time);
EXPECT_LE(info.load_timing.service_worker_ready_time,
info.load_timing.service_worker_fetch_start);
EXPECT_LE(info.load_timing.service_worker_fetch_start,
info.load_timing.service_worker_respond_with_settled);
EXPECT_EQ(expected_info.service_worker_response_source,
info.service_worker_response_source);
EXPECT_EQ(expected_info.cache_storage_cache_name,
info.cache_storage_cache_name);
EXPECT_EQ(expected_info.did_service_worker_navigation_preload,
info.did_service_worker_navigation_preload);
}
std::unique_ptr<network::ResourceRequest> CreateRequest() {
std::unique_ptr<network::ResourceRequest> request =
std::make_unique<network::ResourceRequest>();
request->url = GURL("https://example.com/");
request->method = "GET";
request->mode = network::mojom::RequestMode::kNavigate;
request->credentials_mode = network::mojom::CredentialsMode::kInclude;
request->redirect_mode = network::mojom::RedirectMode::kManual;
request->destination = network::mojom::RequestDestination::kDocument;
return request;
}
bool HasWorkInBrowser(ServiceWorkerVersion* version) const {
return version->HasWorkInBrowser();
}
protected:
BrowserTaskEnvironment task_environment_;
std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
scoped_refptr<ServiceWorkerRegistration> registration_;
scoped_refptr<ServiceWorkerVersion> version_;
FetchEventServiceWorker* service_worker_;
storage::BlobStorageContext blob_context_;
network::TestURLLoaderClient client_;
std::unique_ptr<ServiceWorkerMainResourceLoader> loader_;
mojo::Remote<network::mojom::URLLoader> loader_remote_;
base::WeakPtr<ServiceWorkerContainerHost> container_host_;
ServiceWorkerRemoteContainerEndpoint container_endpoints_;
bool did_call_fallback_callback_ = false;
bool reset_subresource_loader_params_ = false;
base::OnceClosure quit_closure_for_fallback_callback_;
};
TEST_F(ServiceWorkerMainResourceLoaderTest, Basic) {
base::HistogramTester histogram_tester;
// Perform the request.
StartRequest(CreateRequest());
client_.RunUntilComplete();
EXPECT_EQ(net::OK, client_.completion_status().error_code);
auto& info = client_.response_head();
EXPECT_EQ(200, info->headers->response_code());
EXPECT_FALSE(info->load_timing.receive_headers_start.is_null());
EXPECT_FALSE(info->load_timing.receive_headers_end.is_null());
EXPECT_LE(info->load_timing.receive_headers_start,
info->load_timing.receive_headers_end);
ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());
histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
blink::ServiceWorkerStatusCode::kOk, 1);
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"ResponseReceivedToCompleted2",
1);
}
TEST_F(ServiceWorkerMainResourceLoaderTest, NoActiveWorker) {
base::HistogramTester histogram_tester;
// Make a container host without a controller.
container_host_ = CreateContainerHostForWindow(
helper_->mock_render_process_id(), /*is_parent_frame_secure=*/true,
helper_->context()->AsWeakPtr(), &container_endpoints_);
container_host_->UpdateUrls(
GURL("https://example.com/"),
net::SiteForCookies::FromUrl(GURL("https://example.com/")),
url::Origin::Create(GURL("https://example.com/")));
// Perform the request.
StartRequest(CreateRequest());
client_.RunUntilComplete();
EXPECT_EQ(net::ERR_FAILED, client_.completion_status().error_code);
// No fetch event was dispatched.
histogram_tester.ExpectTotalCount(kHistogramMainResourceFetchEvent, 0);
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"StartToForwardServiceWorker",
0);
}
// Test that the request body is passed to the fetch event.
TEST_F(ServiceWorkerMainResourceLoaderTest, RequestBody) {
const std::string kData = "hi this is the request body";
// Create a request with a body.
auto request_body = base::MakeRefCounted<network::ResourceRequestBody>();
request_body->AppendBytes(kData.c_str(), kData.length());
std::unique_ptr<network::ResourceRequest> request = CreateRequest();
request->method = "POST";
request->request_body = request_body;
// This test doesn't use the response to the fetch event, so just have the
// service worker do the default simple response.
StartRequest(std::move(request));
client_.RunUntilComplete();
// Verify that the request body was passed to the fetch event.
std::string body;
service_worker_->ReadRequestBody(&body);
EXPECT_EQ(kData, body);
}
TEST_F(ServiceWorkerMainResourceLoaderTest, BlobResponse) {
base::HistogramTester histogram_tester;
// Construct the blob to respond with.
const std::string kResponseBody = "Here is sample text for the blob.";
auto blob_data = std::make_unique<storage::BlobDataBuilder>("blob-id:myblob");
blob_data->AppendData(kResponseBody);
std::unique_ptr<storage::BlobDataHandle> blob_handle =
blob_context_.AddFinishedBlob(std::move(blob_data));
auto blob = blink::mojom::SerializedBlob::New();
blob->uuid = blob_handle->uuid();
blob->size = blob_handle->size();
storage::BlobImpl::Create(std::move(blob_handle),
blob->blob.InitWithNewPipeAndPassReceiver());
service_worker_->RespondWithBlob(std::move(blob));
service_worker_->SetResponseSource(
network::mojom::FetchResponseSource::kCacheStorage);
std::string cache_name = "v1";
service_worker_->SetCacheStorageCacheName(cache_name);
base::Time response_time = base::Time::Now();
service_worker_->SetResponseTime(response_time);
// Perform the request.
StartRequest(CreateRequest());
client_.RunUntilComplete();
auto& info = client_.response_head();
EXPECT_EQ(200, info->headers->response_code());
auto expected_info = CreateResponseInfoFromServiceWorker();
expected_info->response_time = response_time;
expected_info->cache_storage_cache_name = cache_name;
expected_info->service_worker_response_source =
network::mojom::FetchResponseSource::kCacheStorage;
ExpectResponseInfo(*info, *expected_info);
EXPECT_EQ(33, info->content_length);
// Test the body.
std::string body;
EXPECT_TRUE(client_.response_body().is_valid());
EXPECT_TRUE(
mojo::BlockingCopyToString(client_.response_body_release(), &body));
EXPECT_EQ(kResponseBody, body);
EXPECT_EQ(net::OK, client_.completion_status().error_code);
// Test histogram of reading body.
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"ResponseReceivedToCompleted2",
1);
}
// Tell the helper to respond with a non-existent Blob.
TEST_F(ServiceWorkerMainResourceLoaderTest, BrokenBlobResponse) {
base::HistogramTester histogram_tester;
const std::string kBrokenUUID = "broken_uuid";
// Create the broken blob.
std::unique_ptr<storage::BlobDataHandle> blob_handle =
blob_context_.AddBrokenBlob(kBrokenUUID, "", "",
storage::BlobStatus::ERR_OUT_OF_MEMORY);
auto blob = blink::mojom::SerializedBlob::New();
blob->uuid = kBrokenUUID;
storage::BlobImpl::Create(std::move(blob_handle),
blob->blob.InitWithNewPipeAndPassReceiver());
service_worker_->RespondWithBlob(std::move(blob));
// Perform the request.
StartRequest(CreateRequest());
// We should get a valid response once the headers arrive.
client_.RunUntilResponseReceived();
auto& info = client_.response_head();
EXPECT_EQ(200, info->headers->response_code());
ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());
// However, since the blob is broken we should get an error while transferring
// the body.
client_.RunUntilComplete();
EXPECT_EQ(net::ERR_OUT_OF_MEMORY, client_.completion_status().error_code);
// Timing histograms shouldn't be recorded on broken response.
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"StartToForwardServiceWorker",
0);
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"ResponseReceivedToCompleted2",
0);
}
TEST_F(ServiceWorkerMainResourceLoaderTest, StreamResponse) {
base::HistogramTester histogram_tester;
// Construct the Stream to respond with.
const char kResponseBody[] = "Here is sample text for the Stream.";
mojo::Remote<blink::mojom::ServiceWorkerStreamCallback> stream_callback;
mojo::DataPipe data_pipe;
service_worker_->RespondWithStream(
stream_callback.BindNewPipeAndPassReceiver(),
std::move(data_pipe.consumer_handle));
// Perform the request.
StartRequest(CreateRequest());
client_.RunUntilResponseReceived();
auto& info = client_.response_head();
EXPECT_EQ(200, info->headers->response_code());
ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());
EXPECT_FALSE(version_->HasNoWork());
// Write the body stream.
uint32_t written_bytes = sizeof(kResponseBody) - 1;
MojoResult mojo_result = data_pipe.producer_handle->WriteData(
kResponseBody, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE);
ASSERT_EQ(MOJO_RESULT_OK, mojo_result);
EXPECT_EQ(sizeof(kResponseBody) - 1, written_bytes);
stream_callback->OnCompleted();
data_pipe.producer_handle.reset();
client_.RunUntilComplete();
EXPECT_EQ(net::OK, client_.completion_status().error_code);
// Test the body.
std::string response;
EXPECT_TRUE(client_.response_body().is_valid());
EXPECT_TRUE(
mojo::BlockingCopyToString(client_.response_body_release(), &response));
EXPECT_EQ(kResponseBody, response);
// Test histogram of reading body.
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"ResponseReceivedToCompleted2",
1);
}
// Test when a stream response body is aborted.
TEST_F(ServiceWorkerMainResourceLoaderTest, StreamResponse_Abort) {
base::HistogramTester histogram_tester;
// Construct the Stream to respond with.
const char kResponseBody[] = "Here is sample text for the Stream.";
mojo::Remote<blink::mojom::ServiceWorkerStreamCallback> stream_callback;
mojo::DataPipe data_pipe;
service_worker_->RespondWithStream(
stream_callback.BindNewPipeAndPassReceiver(),
std::move(data_pipe.consumer_handle));
// Perform the request.
StartRequest(CreateRequest());
client_.RunUntilResponseReceived();
auto& info = client_.response_head();
EXPECT_EQ(200, info->headers->response_code());
ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());
// Start writing the body stream, then abort before finishing.
uint32_t written_bytes = sizeof(kResponseBody) - 1;
MojoResult mojo_result = data_pipe.producer_handle->WriteData(
kResponseBody, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE);
ASSERT_EQ(MOJO_RESULT_OK, mojo_result);
EXPECT_EQ(sizeof(kResponseBody) - 1, written_bytes);
stream_callback->OnAborted();
data_pipe.producer_handle.reset();
client_.RunUntilComplete();
EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code);
// Test the body.
std::string response;
EXPECT_TRUE(client_.response_body().is_valid());
EXPECT_TRUE(
mojo::BlockingCopyToString(client_.response_body_release(), &response));
EXPECT_EQ(kResponseBody, response);
// Timing histograms shouldn't be recorded on abort.
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"StartToForwardServiceWorker",
0);
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"ResponseReceivedToCompleted2",
0);
}
// Test when the loader is cancelled while a stream response is being written.
TEST_F(ServiceWorkerMainResourceLoaderTest, StreamResponseAndCancel) {
base::HistogramTester histogram_tester;
// Construct the Stream to respond with.
const char kResponseBody[] = "Here is sample text for the Stream.";
mojo::Remote<blink::mojom::ServiceWorkerStreamCallback> stream_callback;
mojo::DataPipe data_pipe;
service_worker_->RespondWithStream(
stream_callback.BindNewPipeAndPassReceiver(),
std::move(data_pipe.consumer_handle));
// Perform the request.
StartRequest(CreateRequest());
client_.RunUntilResponseReceived();
auto& info = client_.response_head();
EXPECT_EQ(200, info->headers->response_code());
ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());
// Start writing the body stream, then break the Mojo connection to the loader
// before finishing.
uint32_t written_bytes = sizeof(kResponseBody) - 1;
MojoResult mojo_result = data_pipe.producer_handle->WriteData(
kResponseBody, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE);
ASSERT_EQ(MOJO_RESULT_OK, mojo_result);
EXPECT_EQ(sizeof(kResponseBody) - 1, written_bytes);
EXPECT_TRUE(data_pipe.producer_handle.is_valid());
loader_remote_.reset();
base::RunLoop().RunUntilIdle();
// Although ServiceWorkerMainResourceLoader resets its URLLoaderClient pointer
// on connection error, the URLLoaderClient still exists. In this test, it is
// |client_| which owns the data pipe, so it's still valid to write data to
// it.
mojo_result = data_pipe.producer_handle->WriteData(
kResponseBody, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE);
// TODO(falken): This should probably be an error.
EXPECT_EQ(MOJO_RESULT_OK, mojo_result);
client_.RunUntilComplete();
EXPECT_FALSE(data_pipe.consumer_handle.is_valid());
EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code);
// Timing histograms shouldn't be recorded on cancel.
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"StartToForwardServiceWorker",
0);
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"ResponseReceivedToCompleted2",
0);
}
// Test when the service worker responds with network fallback.
// i.e., does not call respondWith().
TEST_F(ServiceWorkerMainResourceLoaderTest, FallbackResponse) {
base::HistogramTester histogram_tester;
service_worker_->RespondWithFallback();
// Perform the request.
StartRequest(CreateRequest());
// The fallback callback should be called.
RunUntilFallbackCallback();
EXPECT_FALSE(reset_subresource_loader_params_);
// The request should not be handled by the loader, but it shouldn't be a
// failure.
EXPECT_TRUE(container_host_->controller());
histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
blink::ServiceWorkerStatusCode::kOk, 1);
// Test histogram of network fallback.
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"FetchHandlerEndToFallbackNetwork",
1);
}
// Test when the service worker rejects the FetchEvent.
TEST_F(ServiceWorkerMainResourceLoaderTest, ErrorResponse) {
base::HistogramTester histogram_tester;
service_worker_->RespondWithError();
// Perform the request.
StartRequest(CreateRequest());
client_.RunUntilComplete();
EXPECT_EQ(net::ERR_FAILED, client_.completion_status().error_code);
// Event dispatch still succeeded.
histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
blink::ServiceWorkerStatusCode::kOk, 1);
// Timing UMAs shouldn't be recorded when we receive an error response.
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"StartToForwardServiceWorker",
0);
}
// Test when dispatching the fetch event to the service worker failed.
TEST_F(ServiceWorkerMainResourceLoaderTest, FailFetchDispatch) {
base::HistogramTester histogram_tester;
service_worker_->FailToDispatchFetchEvent();
// Perform the request.
StartRequest(CreateRequest());
// The fallback callback should be called.
RunUntilFallbackCallback();
EXPECT_TRUE(reset_subresource_loader_params_);
EXPECT_FALSE(container_host_->controller());
histogram_tester.ExpectUniqueSample(
kHistogramMainResourceFetchEvent,
blink::ServiceWorkerStatusCode::kErrorFailed, 1);
// Timing UMAs shouldn't be recorded when failed to dispatch an event.
histogram_tester.ExpectTotalCount(
"ServiceWorker.LoadTiming.MainFrame.MainResource."
"StartToForwardServiceWorker",
0);
}
// Test when the respondWith() promise resolves before the waitUntil() promise
// resolves. The response should be received before the event finishes.
TEST_F(ServiceWorkerMainResourceLoaderTest, EarlyResponse) {
service_worker_->RespondEarly();
// Perform the request.
StartRequest(CreateRequest());
client_.RunUntilComplete();
auto& info = client_.response_head();
EXPECT_EQ(200, info->headers->response_code());
ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());
// Although the response was already received, the event remains outstanding
// until waitUntil() resolves.
EXPECT_TRUE(HasWorkInBrowser(version_.get()));
service_worker_->FinishWaitUntil();
EXPECT_FALSE(HasWorkInBrowser(version_.get()));
}
// Test responding to the fetch event with a redirect response.
TEST_F(ServiceWorkerMainResourceLoaderTest, Redirect) {
base::HistogramTester histogram_tester;
GURL new_url("https://example.com/redirected");
service_worker_->RespondWithRedirectResponse(new_url);
// Perform the request.
StartRequest(CreateRequest());
client_.RunUntilRedirectReceived();
auto& info = client_.response_head();
EXPECT_EQ(301, info->headers->response_code());
ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());
const net::RedirectInfo& redirect_info = client_.redirect_info();
EXPECT_EQ(301, redirect_info.status_code);
EXPECT_EQ("GET", redirect_info.new_method);
EXPECT_EQ(new_url, redirect_info.new_url);
histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
blink::ServiceWorkerStatusCode::kOk, 1);
}
TEST_F(ServiceWorkerMainResourceLoaderTest, Lifetime) {
StartRequest(CreateRequest());
base::WeakPtr<ServiceWorkerMainResourceLoader> loader = loader_->AsWeakPtr();
ASSERT_TRUE(loader);
client_.RunUntilComplete();
EXPECT_TRUE(loader);
// Even after calling DetachedFromRequest(), |loader_| should be alive until
// the Mojo connection to the loader is disconnected.
loader_.release()->DetachedFromRequest();
EXPECT_TRUE(loader);
// When the remote for |loader_| is disconnected, its weak pointers (|loader|)
// are invalidated.
loader_remote_.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(loader);
// |loader_| is deleted here. LSan test will alert if it leaks.
}
TEST_F(ServiceWorkerMainResourceLoaderTest, ConnectionErrorDuringFetchEvent) {
service_worker_->DeferResponse();
StartRequest(CreateRequest());
// Wait for the fetch event to be dispatched.
service_worker_->RunUntilFetchEvent();
// Break the Mojo connection. The loader should return an aborted status.
loader_remote_.reset();
client_.RunUntilComplete();
EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code);
// The loader is still alive. Finish the fetch event. It shouldn't crash or
// call any callbacks on |client_|, which would throw an error.
service_worker_->FinishRespondWith();
// There's no event to wait for, so just pump the message loop and the test
// passes if there is no error or crash.
base::RunLoop().RunUntilIdle();
}
TEST_F(ServiceWorkerMainResourceLoaderTest, CancelNavigationDuringFetchEvent) {
StartRequest(CreateRequest());
// Delete the container host during the request. The load should abort without
// crashing.
container_endpoints_.host_remote()->reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(container_host_);
client_.RunUntilComplete();
EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code);
}
TEST_F(ServiceWorkerMainResourceLoaderTest, TimingInfo) {
service_worker_->DispatchAfter1sDelay();
// Perform the request.
StartRequest(CreateRequest());
client_.RunUntilComplete();
// The response header's timing is recorded appropriately.
auto& info = client_.response_head();
EXPECT_EQ(200, info->headers->response_code());
ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());
EXPECT_EQ(base::TimeDelta::FromSeconds(1),
info->load_timing.service_worker_ready_time -
info->load_timing.service_worker_start_time);
EXPECT_EQ(base::TimeDelta::FromSeconds(1),
info->load_timing.service_worker_fetch_start -
info->load_timing.service_worker_start_time);
EXPECT_EQ(base::TimeDelta::FromSeconds(1),
info->load_timing.service_worker_respond_with_settled -
info->load_timing.service_worker_start_time);
}
} // namespace service_worker_main_resource_loader_unittest
} // namespace content