| // 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/renderer/service_worker/service_worker_subresource_loader.h" |
| |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "content/common/service_worker/service_worker_container.mojom.h" |
| #include "content/common/service_worker/service_worker_utils.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/resource_type.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "content/renderer/service_worker/controller_service_worker_connector.h" |
| #include "mojo/public/cpp/bindings/binding_set.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "mojo/public/cpp/system/data_pipe_utils.h" |
| #include "net/http/http_util.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "net/url_request/url_request.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h" |
| #include "services/network/test/test_data_pipe_getter.h" |
| #include "services/network/test/test_url_loader_client.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| namespace service_worker_subresource_loader_unittest { |
| |
| // A simple URLLoaderFactory that responds with status 200 to every request. |
| // This is the default network loader factory for |
| // ServiceWorkerSubresourceLoaderTest. |
| class FakeNetworkURLLoaderFactory final |
| : public network::mojom::URLLoaderFactory { |
| public: |
| FakeNetworkURLLoaderFactory() = default; |
| |
| // network::mojom::URLLoaderFactory implementation. |
| void CreateLoaderAndStart(network::mojom::URLLoaderRequest request, |
| int32_t routing_id, |
| int32_t request_id, |
| uint32_t options, |
| const network::ResourceRequest& url_request, |
| network::mojom::URLLoaderClientPtr client, |
| const net::MutableNetworkTrafficAnnotationTag& |
| traffic_annotation) override { |
| std::string headers = "HTTP/1.1 200 OK\n\n"; |
| net::HttpResponseInfo info; |
| info.headers = new net::HttpResponseHeaders( |
| net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.length())); |
| network::ResourceResponseHead response; |
| response.headers = info.headers; |
| response.headers->GetMimeType(&response.mime_type); |
| client->OnReceiveResponse(response); |
| |
| std::string body = "this body came from the network"; |
| uint32_t bytes_written = body.size(); |
| mojo::DataPipe data_pipe; |
| data_pipe.producer_handle->WriteData(body.data(), &bytes_written, |
| MOJO_WRITE_DATA_FLAG_ALL_OR_NONE); |
| client->OnStartLoadingResponseBody(std::move(data_pipe.consumer_handle)); |
| |
| network::URLLoaderCompletionStatus status; |
| status.error_code = net::OK; |
| client->OnComplete(status); |
| } |
| |
| void Clone(network::mojom::URLLoaderFactoryRequest factory) override { |
| NOTREACHED(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(FakeNetworkURLLoaderFactory); |
| }; |
| |
| class FakeControllerServiceWorker : public mojom::ControllerServiceWorker { |
| public: |
| FakeControllerServiceWorker() = default; |
| ~FakeControllerServiceWorker() override = default; |
| |
| static ServiceWorkerResponse OkResponse() { |
| return ServiceWorkerResponse( |
| std::make_unique<std::vector<GURL>>(), 200, "OK", |
| network::mojom::FetchResponseType::kDefault, |
| std::make_unique<ServiceWorkerHeaderMap>(), "" /* blob_uuid */, |
| 0 /* blob_size */, nullptr /* blob */, |
| blink::mojom::ServiceWorkerResponseError::kUnknown, base::Time(), |
| false /* response_is_in_cache_storage */, |
| std::string() /* response_cache_storage_cache_name */, |
| std::make_unique< |
| ServiceWorkerHeaderList>() /* cors_exposed_header_names */); |
| } |
| |
| static ServiceWorkerResponse ErrorResponse() { |
| return ServiceWorkerResponse( |
| std::make_unique<std::vector<GURL>>(), 0 /* status_code */, |
| "" /* status_text */, network::mojom::FetchResponseType::kDefault, |
| std::make_unique<ServiceWorkerHeaderMap>(), "" /* blob_uuid */, |
| 0 /* blob_size */, nullptr /* blob */, |
| blink::mojom::ServiceWorkerResponseError::kPromiseRejected, |
| base::Time(), false /* response_is_in_cache_storage */, |
| std::string() /* response_cache_storage_cache_name */, |
| std::make_unique< |
| ServiceWorkerHeaderList>() /* cors_exposed_header_names */); |
| } |
| |
| static ServiceWorkerResponse RedirectResponse( |
| const std::string& redirect_location_header) { |
| auto headers = std::make_unique<ServiceWorkerHeaderMap>(); |
| (*headers)["Location"] = redirect_location_header; |
| |
| return ServiceWorkerResponse( |
| std::make_unique<std::vector<GURL>>(), 302, "Found", |
| network::mojom::FetchResponseType::kDefault, std::move(headers), |
| "" /* blob_uuid */, 0 /* blob_size */, nullptr /* blob */, |
| blink::mojom::ServiceWorkerResponseError::kUnknown, base::Time(), |
| false /* response_is_in_cache_storage */, |
| std::string() /* response_cache_storage_cache_name */, |
| std::make_unique< |
| ServiceWorkerHeaderList>() /* cors_exposed_header_names */); |
| } |
| |
| void CloseAllBindings() { bindings_.CloseAllBindings(); } |
| |
| // Tells this controller to abort the fetch event without a response. |
| // i.e., simulate the service worker failing to handle the fetch event. |
| void AbortEventWithNoResponse() { response_mode_ = ResponseMode::kAbort; } |
| |
| // Tells this controller 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 controller to respond to fetch events with the specified stream. |
| void RespondWithStream( |
| blink::mojom::ServiceWorkerStreamCallbackRequest callback_request, |
| mojo::ScopedDataPipeConsumerHandle consumer_handle) { |
| response_mode_ = ResponseMode::kStream; |
| stream_handle_ = blink::mojom::ServiceWorkerStreamHandle::New(); |
| stream_handle_->callback_request = std::move(callback_request); |
| stream_handle_->stream = std::move(consumer_handle); |
| } |
| |
| // Tells this controller to respond to fetch events with a error response. |
| void RespondWithError() { response_mode_ = ResponseMode::kErrorResponse; } |
| |
| // Tells this controller to respond to fetch events with a redirect response. |
| void RespondWithRedirect(const std::string& redirect_location_header) { |
| response_mode_ = ResponseMode::kRedirectResponse; |
| redirect_location_header_ = redirect_location_header; |
| } |
| |
| void ReadRequestBody(std::string* out_string) { |
| ASSERT_TRUE(request_body_); |
| std::vector<network::DataElement>* elements = |
| request_body_->elements_mutable(); |
| // So far this test expects a single element (bytes or data pipe). |
| ASSERT_EQ(1u, elements->size()); |
| network::DataElement& element = elements->front(); |
| if (element.type() == network::DataElement::TYPE_BYTES) { |
| *out_string = std::string(element.bytes(), element.length()); |
| } else if (element.type() == network::DataElement::TYPE_DATA_PIPE) { |
| // Read the content into |data_pipe|. |
| mojo::DataPipe data_pipe; |
| network::mojom::DataPipeGetterPtr ptr(element.ReleaseDataPipeGetter()); |
| base::RunLoop run_loop; |
| ptr->Read( |
| std::move(data_pipe.producer_handle), |
| base::BindOnce([](const base::Closure& quit_closure, int32_t status, |
| uint64_t size) { quit_closure.Run(); }, |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| // Copy the content to |out_string|. |
| mojo::BlockingCopyToString(std::move(data_pipe.consumer_handle), |
| out_string); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| // mojom::ControllerServiceWorker: |
| void DispatchFetchEvent( |
| blink::mojom::DispatchFetchEventParamsPtr params, |
| mojom::ServiceWorkerFetchResponseCallbackPtr response_callback, |
| DispatchFetchEventCallback callback) override { |
| EXPECT_FALSE(ServiceWorkerUtils::IsMainResourceType( |
| static_cast<ResourceType>(params->request.resource_type))); |
| request_body_ = params->request.request_body; |
| |
| fetch_event_count_++; |
| fetch_event_request_ = params->request; |
| |
| switch (response_mode_) { |
| case ResponseMode::kDefault: |
| response_callback->OnResponse(OkResponse(), base::Time::Now()); |
| std::move(callback).Run( |
| blink::mojom::ServiceWorkerEventStatus::COMPLETED, base::Time()); |
| break; |
| case ResponseMode::kAbort: |
| std::move(callback).Run(blink::mojom::ServiceWorkerEventStatus::ABORTED, |
| base::Time()); |
| break; |
| case ResponseMode::kStream: |
| response_callback->OnResponseStream( |
| OkResponse(), std::move(stream_handle_), base::Time::Now()); |
| std::move(callback).Run( |
| blink::mojom::ServiceWorkerEventStatus::COMPLETED, base::Time()); |
| break; |
| case ResponseMode::kFallbackResponse: |
| response_callback->OnFallback(base::Time::Now()); |
| std::move(callback).Run( |
| blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::Time::Now()); |
| break; |
| case ResponseMode::kErrorResponse: |
| response_callback->OnResponse(ErrorResponse(), base::Time::Now()); |
| std::move(callback).Run( |
| blink::mojom::ServiceWorkerEventStatus::REJECTED, |
| base::Time::Now()); |
| break; |
| case ResponseMode::kRedirectResponse: { |
| response_callback->OnResponse( |
| RedirectResponse(redirect_location_header_), base::Time::Now()); |
| std::move(callback).Run( |
| blink::mojom::ServiceWorkerEventStatus::COMPLETED, base::Time()); |
| break; |
| } |
| } |
| if (fetch_event_callback_) |
| std::move(fetch_event_callback_).Run(); |
| } |
| |
| void Clone(mojom::ControllerServiceWorkerRequest request) override { |
| bindings_.AddBinding(this, std::move(request)); |
| } |
| |
| void RunUntilFetchEvent() { |
| base::RunLoop loop; |
| fetch_event_callback_ = loop.QuitClosure(); |
| loop.Run(); |
| } |
| |
| int fetch_event_count() const { return fetch_event_count_; } |
| const network::ResourceRequest& fetch_event_request() const { |
| return fetch_event_request_; |
| } |
| |
| private: |
| enum class ResponseMode { |
| kDefault, |
| kAbort, |
| kStream, |
| kFallbackResponse, |
| kErrorResponse, |
| kRedirectResponse |
| }; |
| |
| ResponseMode response_mode_ = ResponseMode::kDefault; |
| scoped_refptr<network::ResourceRequestBody> request_body_; |
| |
| int fetch_event_count_ = 0; |
| network::ResourceRequest fetch_event_request_; |
| base::OnceClosure fetch_event_callback_; |
| mojo::BindingSet<mojom::ControllerServiceWorker> bindings_; |
| |
| // For ResponseMode::kStream. |
| blink::mojom::ServiceWorkerStreamHandlePtr stream_handle_; |
| |
| // For ResponseMode::kRedirectResponse |
| std::string redirect_location_header_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FakeControllerServiceWorker); |
| }; |
| |
| class FakeServiceWorkerContainerHost |
| : public mojom::ServiceWorkerContainerHost { |
| public: |
| explicit FakeServiceWorkerContainerHost( |
| FakeControllerServiceWorker* fake_controller) |
| : fake_controller_(fake_controller) {} |
| |
| ~FakeServiceWorkerContainerHost() override = default; |
| |
| void set_fake_controller(FakeControllerServiceWorker* new_fake_controller) { |
| fake_controller_ = new_fake_controller; |
| } |
| |
| int get_controller_service_worker_count() const { |
| return get_controller_service_worker_count_; |
| } |
| |
| // Implements mojom::ServiceWorkerContainerHost. |
| void Register(const GURL& script_url, |
| blink::mojom::ServiceWorkerRegistrationOptionsPtr options, |
| RegisterCallback callback) override { |
| NOTIMPLEMENTED(); |
| } |
| void GetRegistration(const GURL& client_url, |
| GetRegistrationCallback callback) override { |
| NOTIMPLEMENTED(); |
| } |
| void GetRegistrations(GetRegistrationsCallback callback) override { |
| NOTIMPLEMENTED(); |
| } |
| void GetRegistrationForReady( |
| GetRegistrationForReadyCallback callback) override { |
| NOTIMPLEMENTED(); |
| } |
| void EnsureControllerServiceWorker( |
| mojom::ControllerServiceWorkerRequest request, |
| mojom::ControllerServiceWorkerPurpose purpose) override { |
| get_controller_service_worker_count_++; |
| if (!fake_controller_) |
| return; |
| fake_controller_->Clone(std::move(request)); |
| } |
| void CloneForWorker( |
| mojom::ServiceWorkerContainerHostRequest request) override { |
| bindings_.AddBinding(this, std::move(request)); |
| } |
| void Ping(PingCallback callback) override { NOTIMPLEMENTED(); } |
| |
| private: |
| int get_controller_service_worker_count_ = 0; |
| FakeControllerServiceWorker* fake_controller_; |
| mojo::BindingSet<mojom::ServiceWorkerContainerHost> bindings_; |
| DISALLOW_COPY_AND_ASSIGN(FakeServiceWorkerContainerHost); |
| }; |
| |
| // Returns an expected ResourceResponseHead which is used by stream response |
| // related tests. |
| std::unique_ptr<network::ResourceResponseHead> |
| CreateResponseInfoFromServiceWorker() { |
| auto head = std::make_unique<network::ResourceResponseHead>(); |
| std::string headers = "HTTP/1.1 200 OK\n\n"; |
| head->headers = new net::HttpResponseHeaders( |
| net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.length())); |
| 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_via_service_worker = |
| network::mojom::FetchResponseType::kDefault; |
| head->is_in_cache_storage = false; |
| head->cache_storage_cache_name = std::string(); |
| head->did_service_worker_navigation_preload = false; |
| return head; |
| } |
| |
| const char kHistogramSubresourceFetchEvent[] = |
| "ServiceWorker.FetchEvent.Subresource.Status"; |
| |
| class ServiceWorkerSubresourceLoaderTest : public ::testing::Test { |
| protected: |
| ServiceWorkerSubresourceLoaderTest() |
| : fake_container_host_(&fake_controller_) {} |
| ~ServiceWorkerSubresourceLoaderTest() override = default; |
| |
| void SetUp() override { |
| feature_list_.InitAndEnableFeature(network::features::kNetworkService); |
| |
| network::mojom::URLLoaderFactoryPtr fake_loader_factory; |
| mojo::MakeStrongBinding(std::make_unique<FakeNetworkURLLoaderFactory>(), |
| MakeRequest(&fake_loader_factory)); |
| loader_factory_ = |
| base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>( |
| std::move(fake_loader_factory)); |
| } |
| |
| network::mojom::URLLoaderFactoryPtr CreateSubresourceLoaderFactory() { |
| if (!connector_) { |
| mojom::ServiceWorkerContainerHostPtrInfo host_ptr_info; |
| fake_container_host_.CloneForWorker(mojo::MakeRequest(&host_ptr_info)); |
| connector_ = base::MakeRefCounted<ControllerServiceWorkerConnector>( |
| std::move(host_ptr_info), nullptr /*controller_ptr*/, |
| "" /*client_id*/); |
| } |
| network::mojom::URLLoaderFactoryPtr service_worker_url_loader_factory; |
| ServiceWorkerSubresourceLoaderFactory::Create( |
| connector_, loader_factory_, |
| mojo::MakeRequest(&service_worker_url_loader_factory)); |
| return service_worker_url_loader_factory; |
| } |
| |
| // Starts |request| using |loader_factory| and sets |out_loader| and |
| // |out_loader_client| to the resulting URLLoader and its URLLoaderClient. The |
| // caller can then use functions like client.RunUntilComplete() to wait for |
| // completion. Calling fake_controller_->RunUntilFetchEvent() also advances |
| // the load to until |fake_controller_| receives the fetch event. |
| void StartRequest( |
| const network::mojom::URLLoaderFactoryPtr& loader_factory, |
| const network::ResourceRequest& request, |
| network::mojom::URLLoaderPtr* out_loader, |
| std::unique_ptr<network::TestURLLoaderClient>* out_loader_client) { |
| *out_loader_client = std::make_unique<network::TestURLLoaderClient>(); |
| loader_factory->CreateLoaderAndStart( |
| mojo::MakeRequest(out_loader), 0, 0, network::mojom::kURLLoadOptionNone, |
| request, (*out_loader_client)->CreateInterfacePtr(), |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); |
| } |
| |
| void ExpectResponseInfo(const network::ResourceResponseHead& info, |
| const network::ResourceResponseHead& expected_info) { |
| EXPECT_EQ(expected_info.headers->response_code(), |
| info.headers->response_code()); |
| 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_via_service_worker, |
| info.response_type_via_service_worker); |
| EXPECT_EQ(expected_info.is_in_cache_storage, info.is_in_cache_storage); |
| 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); |
| EXPECT_NE(expected_info.service_worker_start_time, |
| info.service_worker_start_time); |
| EXPECT_NE(expected_info.service_worker_ready_time, |
| info.service_worker_ready_time); |
| } |
| |
| network::ResourceRequest CreateRequest(const GURL& url) { |
| network::ResourceRequest request; |
| request.url = url; |
| request.method = "GET"; |
| request.resource_type = RESOURCE_TYPE_SUB_RESOURCE; |
| return request; |
| } |
| |
| // Performs a request with the given |request_body|, and checks that the body |
| // is passed to the fetch event. |
| void RunFallbackWithRequestBodyTest( |
| scoped_refptr<network::ResourceRequestBody> request_body, |
| const std::string& expected_body) { |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| |
| // Create a request with the body. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/upload")); |
| request.method = "POST"; |
| request.request_body = std::move(request_body); |
| |
| // Set the service worker to do network fallback as it exercises a tricky |
| // code path to ensure the body makes it to network. |
| fake_controller_.RespondWithFallback(); |
| |
| // Perform the request. |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| client->RunUntilComplete(); |
| |
| // Verify that the request body was passed to the fetch event. |
| std::string fetch_event_body; |
| fake_controller_.ReadRequestBody(&fetch_event_body); |
| EXPECT_EQ(expected_body, fetch_event_body); |
| |
| // TODO(falken): It'd be nicer to also check the request body was sent to |
| // network but it requires more complicated network mocking and it was hard |
| // getting EmbeddedTestServer working with these tests (probably |
| // CORSFallbackResponse is too heavy). We also have Web Platform Tests that |
| // cover this case in fetch-event.https.html. |
| } |
| |
| TestBrowserThreadBundle thread_bundle_; |
| scoped_refptr<network::SharedURLLoaderFactory> loader_factory_; |
| scoped_refptr<ControllerServiceWorkerConnector> connector_; |
| FakeServiceWorkerContainerHost fake_container_host_; |
| FakeControllerServiceWorker fake_controller_; |
| base::test::ScopedFeatureList feature_list_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ServiceWorkerSubresourceLoaderTest); |
| }; |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, Basic) { |
| base::HistogramTester histogram_tester; |
| |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| fake_controller_.RunUntilFetchEvent(); |
| |
| EXPECT_EQ(request.url, fake_controller_.fetch_event_request().url); |
| EXPECT_EQ(request.method, fake_controller_.fetch_event_request().method); |
| EXPECT_EQ(1, fake_controller_.fetch_event_count()); |
| EXPECT_EQ(1, fake_container_host_.get_controller_service_worker_count()); |
| |
| client->RunUntilComplete(); |
| histogram_tester.ExpectUniqueSample(kHistogramSubresourceFetchEvent, |
| blink::SERVICE_WORKER_OK, 1); |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, Abort) { |
| fake_controller_.AbortEventWithNoResponse(); |
| base::HistogramTester histogram_tester; |
| |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| |
| // Perform the request. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| client->RunUntilComplete(); |
| |
| EXPECT_EQ(net::ERR_FAILED, client->completion_status().error_code); |
| histogram_tester.ExpectUniqueSample(kHistogramSubresourceFetchEvent, |
| blink::SERVICE_WORKER_ERROR_ABORT, 1); |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, DropController) { |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| { |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| fake_controller_.RunUntilFetchEvent(); |
| |
| EXPECT_EQ(request.url, fake_controller_.fetch_event_request().url); |
| EXPECT_EQ(request.method, fake_controller_.fetch_event_request().method); |
| EXPECT_EQ(1, fake_controller_.fetch_event_count()); |
| EXPECT_EQ(1, fake_container_host_.get_controller_service_worker_count()); |
| } |
| |
| // Loading another resource reuses the existing connection to the |
| // ControllerServiceWorker (i.e. it doesn't increase the get controller |
| // service worker count). |
| { |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo2.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| fake_controller_.RunUntilFetchEvent(); |
| |
| EXPECT_EQ(request.url, fake_controller_.fetch_event_request().url); |
| EXPECT_EQ(request.method, fake_controller_.fetch_event_request().method); |
| EXPECT_EQ(2, fake_controller_.fetch_event_count()); |
| EXPECT_EQ(1, fake_container_host_.get_controller_service_worker_count()); |
| } |
| |
| // Drop the connection to the ControllerServiceWorker. |
| fake_controller_.CloseAllBindings(); |
| base::RunLoop().RunUntilIdle(); |
| |
| { |
| // This should re-obtain the ControllerServiceWorker. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo3.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| fake_controller_.RunUntilFetchEvent(); |
| |
| EXPECT_EQ(request.url, fake_controller_.fetch_event_request().url); |
| EXPECT_EQ(request.method, fake_controller_.fetch_event_request().method); |
| EXPECT_EQ(3, fake_controller_.fetch_event_count()); |
| EXPECT_EQ(2, fake_container_host_.get_controller_service_worker_count()); |
| } |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, NoController) { |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| { |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| fake_controller_.RunUntilFetchEvent(); |
| |
| EXPECT_EQ(request.url, fake_controller_.fetch_event_request().url); |
| EXPECT_EQ(request.method, fake_controller_.fetch_event_request().method); |
| EXPECT_EQ(1, fake_controller_.fetch_event_count()); |
| EXPECT_EQ(1, fake_container_host_.get_controller_service_worker_count()); |
| } |
| |
| // Make the connector have no controller. |
| connector_->UpdateController(nullptr); |
| base::RunLoop().RunUntilIdle(); |
| |
| base::HistogramTester histogram_tester; |
| { |
| // This should fallback to the network. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo2.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| client->RunUntilComplete(); |
| |
| EXPECT_TRUE(client->has_received_completion()); |
| EXPECT_FALSE(client->response_head().was_fetched_via_service_worker); |
| |
| EXPECT_EQ(1, fake_controller_.fetch_event_count()); |
| EXPECT_EQ(1, fake_container_host_.get_controller_service_worker_count()); |
| } |
| |
| // No fetch event was dispatched, so no sample should be recorded. |
| histogram_tester.ExpectTotalCount(kHistogramSubresourceFetchEvent, 0); |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, DropController_RestartFetchEvent) { |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| |
| { |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| fake_controller_.RunUntilFetchEvent(); |
| |
| EXPECT_EQ(request.url, fake_controller_.fetch_event_request().url); |
| EXPECT_EQ(request.method, fake_controller_.fetch_event_request().method); |
| EXPECT_EQ(1, fake_controller_.fetch_event_count()); |
| EXPECT_EQ(1, fake_container_host_.get_controller_service_worker_count()); |
| } |
| |
| // Loading another resource reuses the existing connection to the |
| // ControllerServiceWorker (i.e. it doesn't increase the get controller |
| // service worker count). |
| { |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo2.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| fake_controller_.RunUntilFetchEvent(); |
| |
| EXPECT_EQ(request.url, fake_controller_.fetch_event_request().url); |
| EXPECT_EQ(request.method, fake_controller_.fetch_event_request().method); |
| EXPECT_EQ(2, fake_controller_.fetch_event_count()); |
| EXPECT_EQ(1, fake_container_host_.get_controller_service_worker_count()); |
| client->RunUntilComplete(); |
| } |
| |
| base::HistogramTester histogram_tester; |
| |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo3.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| |
| // Drop the connection to the ControllerServiceWorker. |
| fake_controller_.CloseAllBindings(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // If connection is closed during fetch event, it's restarted and successfully |
| // finishes. |
| EXPECT_EQ(request.url, fake_controller_.fetch_event_request().url); |
| EXPECT_EQ(request.method, fake_controller_.fetch_event_request().method); |
| EXPECT_EQ(3, fake_controller_.fetch_event_count()); |
| EXPECT_EQ(2, fake_container_host_.get_controller_service_worker_count()); |
| histogram_tester.ExpectUniqueSample(kHistogramSubresourceFetchEvent, |
| blink::SERVICE_WORKER_OK, 1); |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, DropController_TooManyRestart) { |
| base::HistogramTester histogram_tester; |
| // Simulate the container host fails to start a service worker. |
| fake_container_host_.set_fake_controller(nullptr); |
| |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| |
| // Try to dispatch fetch event to the bad worker. |
| base::RunLoop().RunUntilIdle(); |
| |
| // The request should be failed instead of infinite loop to restart the |
| // inflight fetch event. |
| EXPECT_EQ(2, fake_container_host_.get_controller_service_worker_count()); |
| EXPECT_TRUE(client->has_received_completion()); |
| EXPECT_EQ(net::ERR_FAILED, client->completion_status().error_code); |
| |
| histogram_tester.ExpectUniqueSample( |
| kHistogramSubresourceFetchEvent, |
| blink::SERVICE_WORKER_ERROR_START_WORKER_FAILED, 1); |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, StreamResponse) { |
| base::HistogramTester histogram_tester; |
| |
| // Construct the Stream to respond with. |
| const char kResponseBody[] = "Here is sample text for the Stream."; |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| mojo::DataPipe data_pipe; |
| fake_controller_.RespondWithStream(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle)); |
| |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| |
| // Perform the request. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| client->RunUntilResponseReceived(); |
| |
| const network::ResourceResponseHead& info = client->response_head(); |
| ExpectResponseInfo(info, *CreateResponseInfoFromServiceWorker()); |
| |
| // 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); |
| |
| histogram_tester.ExpectUniqueSample(kHistogramSubresourceFetchEvent, |
| blink::SERVICE_WORKER_OK, 1); |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, StreamResponse_Abort) { |
| base::HistogramTester histogram_tester; |
| |
| // Construct the Stream to respond with. |
| const char kResponseBody[] = "Here is sample text for the Stream."; |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| mojo::DataPipe data_pipe; |
| fake_controller_.RespondWithStream(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle)); |
| |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| |
| // Perform the request. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.txt")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| client->RunUntilResponseReceived(); |
| |
| const network::ResourceResponseHead& info = client->response_head(); |
| 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); |
| |
| histogram_tester.ExpectUniqueSample(kHistogramSubresourceFetchEvent, |
| blink::SERVICE_WORKER_OK, 1); |
| } |
| |
| // Test when the service worker responds with network fallback. |
| // i.e., does not call respondWith(). |
| TEST_F(ServiceWorkerSubresourceLoaderTest, FallbackResponse) { |
| base::HistogramTester histogram_tester; |
| fake_controller_.RespondWithFallback(); |
| |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| |
| // Perform the request. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| client->RunUntilComplete(); |
| |
| // OnFallback() should complete the network request using network loader. |
| EXPECT_TRUE(client->has_received_completion()); |
| EXPECT_FALSE(client->response_head().was_fetched_via_service_worker); |
| |
| histogram_tester.ExpectUniqueSample(kHistogramSubresourceFetchEvent, |
| blink::SERVICE_WORKER_OK, 1); |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, ErrorResponse) { |
| base::HistogramTester histogram_tester; |
| fake_controller_.RespondWithError(); |
| |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| |
| // Perform the request. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| client->RunUntilComplete(); |
| |
| EXPECT_EQ(net::ERR_FAILED, client->completion_status().error_code); |
| histogram_tester.ExpectUniqueSample(kHistogramSubresourceFetchEvent, |
| blink::SERVICE_WORKER_OK, 1); |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, RedirectResponse) { |
| base::HistogramTester histogram_tester; |
| fake_controller_.RespondWithRedirect("https://www.example.com/bar.png"); |
| |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| |
| // Perform the request. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| client->RunUntilRedirectReceived(); |
| |
| EXPECT_EQ(net::OK, client->completion_status().error_code); |
| EXPECT_TRUE(client->has_received_redirect()); |
| { |
| const net::RedirectInfo& redirect_info = client->redirect_info(); |
| EXPECT_EQ(302, redirect_info.status_code); |
| EXPECT_EQ("GET", redirect_info.new_method); |
| EXPECT_EQ(GURL("https://www.example.com/bar.png"), redirect_info.new_url); |
| } |
| client->ClearHasReceivedRedirect(); |
| |
| // Redirect once more. |
| fake_controller_.RespondWithRedirect("https://other.example.com/baz.png"); |
| loader->FollowRedirect(base::nullopt, base::nullopt); |
| client->RunUntilRedirectReceived(); |
| |
| EXPECT_EQ(net::OK, client->completion_status().error_code); |
| EXPECT_TRUE(client->has_received_redirect()); |
| { |
| const net::RedirectInfo& redirect_info = client->redirect_info(); |
| EXPECT_EQ(302, redirect_info.status_code); |
| EXPECT_EQ("GET", redirect_info.new_method); |
| EXPECT_EQ(GURL("https://other.example.com/baz.png"), redirect_info.new_url); |
| } |
| client->ClearHasReceivedRedirect(); |
| |
| // Give the final response. |
| const char kResponseBody[] = "Here is sample text for the Stream."; |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| mojo::DataPipe data_pipe; |
| fake_controller_.RespondWithStream(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle)); |
| loader->FollowRedirect(base::nullopt, base::nullopt); |
| client->RunUntilResponseReceived(); |
| |
| const network::ResourceResponseHead& info = client->response_head(); |
| EXPECT_EQ(200, info.headers->response_code()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info.response_type_via_service_worker); |
| |
| // 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); |
| |
| // There were 3 fetch events, so expect a count of 3. |
| histogram_tester.ExpectUniqueSample(kHistogramSubresourceFetchEvent, |
| blink::SERVICE_WORKER_OK, 3); |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, TooManyRedirects) { |
| base::HistogramTester histogram_tester; |
| |
| int count = 1; |
| std::string redirect_location = |
| std::string("https://www.example.com/redirect_") + |
| base::IntToString(count); |
| fake_controller_.RespondWithRedirect(redirect_location); |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| |
| // Perform the request. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| |
| // The Fetch spec says: "If request’s redirect count is twenty, return a |
| // network error." https://fetch.spec.whatwg.org/#http-redirect-fetch |
| // So fetch can follow the redirect response until 20 times. |
| static_assert(net::URLRequest::kMaxRedirects == 20, |
| "The Fetch spec requires kMaxRedirects to be 20"); |
| for (; count < net::URLRequest::kMaxRedirects + 1; ++count) { |
| client->RunUntilRedirectReceived(); |
| |
| EXPECT_TRUE(client->has_received_redirect()); |
| EXPECT_EQ(net::OK, client->completion_status().error_code); |
| const net::RedirectInfo& redirect_info = client->redirect_info(); |
| EXPECT_EQ(302, redirect_info.status_code); |
| EXPECT_EQ("GET", redirect_info.new_method); |
| EXPECT_EQ(GURL(redirect_location), redirect_info.new_url); |
| |
| client->ClearHasReceivedRedirect(); |
| |
| // Redirect more. |
| redirect_location = std::string("https://www.example.com/redirect_") + |
| base::IntToString(count); |
| fake_controller_.RespondWithRedirect(redirect_location); |
| loader->FollowRedirect(base::nullopt, base::nullopt); |
| } |
| client->RunUntilComplete(); |
| |
| // Fetch can't follow the redirect response 21 times. |
| EXPECT_FALSE(client->has_received_redirect()); |
| EXPECT_EQ(net::ERR_TOO_MANY_REDIRECTS, |
| client->completion_status().error_code); |
| |
| // Expect a sample for each fetch event (kMaxRedirects + 1). |
| histogram_tester.ExpectUniqueSample(kHistogramSubresourceFetchEvent, |
| blink::SERVICE_WORKER_OK, |
| net::URLRequest::kMaxRedirects + 1); |
| } |
| |
| // Test when the service worker responds with network fallback to CORS request. |
| TEST_F(ServiceWorkerSubresourceLoaderTest, CORSFallbackResponse) { |
| fake_controller_.RespondWithFallback(); |
| |
| network::mojom::URLLoaderFactoryPtr factory = |
| CreateSubresourceLoaderFactory(); |
| |
| struct TestCase { |
| network::mojom::FetchRequestMode fetch_request_mode; |
| base::Optional<url::Origin> request_initiator; |
| bool expected_was_fallback_required_by_service_worker; |
| }; |
| const TestCase kTests[] = { |
| {network::mojom::FetchRequestMode::kSameOrigin, |
| base::Optional<url::Origin>(), false}, |
| {network::mojom::FetchRequestMode::kNoCORS, base::Optional<url::Origin>(), |
| false}, |
| {network::mojom::FetchRequestMode::kCORS, base::Optional<url::Origin>(), |
| true}, |
| {network::mojom::FetchRequestMode::kCORSWithForcedPreflight, |
| base::Optional<url::Origin>(), true}, |
| {network::mojom::FetchRequestMode::kNavigate, |
| base::Optional<url::Origin>(), false}, |
| {network::mojom::FetchRequestMode::kSameOrigin, |
| url::Origin::Create(GURL("https://www.example.com/")), false}, |
| {network::mojom::FetchRequestMode::kNoCORS, |
| url::Origin::Create(GURL("https://www.example.com/")), false}, |
| {network::mojom::FetchRequestMode::kCORS, |
| url::Origin::Create(GURL("https://www.example.com/")), false}, |
| {network::mojom::FetchRequestMode::kCORSWithForcedPreflight, |
| url::Origin::Create(GURL("https://www.example.com/")), false}, |
| {network::mojom::FetchRequestMode::kNavigate, |
| url::Origin::Create(GURL("https://other.example.com/")), false}, |
| {network::mojom::FetchRequestMode::kSameOrigin, |
| url::Origin::Create(GURL("https://other.example.com/")), false}, |
| {network::mojom::FetchRequestMode::kNoCORS, |
| url::Origin::Create(GURL("https://other.example.com/")), false}, |
| {network::mojom::FetchRequestMode::kCORS, |
| url::Origin::Create(GURL("https://other.example.com/")), true}, |
| {network::mojom::FetchRequestMode::kCORSWithForcedPreflight, |
| url::Origin::Create(GURL("https://other.example.com/")), true}, |
| {network::mojom::FetchRequestMode::kNavigate, |
| url::Origin::Create(GURL("https://other.example.com/")), false}}; |
| |
| for (const auto& test : kTests) { |
| SCOPED_TRACE( |
| ::testing::Message() |
| << "fetch_request_mode: " << static_cast<int>(test.fetch_request_mode) |
| << " request_initiator: " |
| << (test.request_initiator ? test.request_initiator->Serialize() |
| : std::string("null"))); |
| // Perform the request. |
| network::ResourceRequest request = |
| CreateRequest(GURL("https://www.example.com/foo.png")); |
| request.fetch_request_mode = test.fetch_request_mode; |
| request.request_initiator = test.request_initiator; |
| network::mojom::URLLoaderPtr loader; |
| std::unique_ptr<network::TestURLLoaderClient> client; |
| StartRequest(factory, request, &loader, &client); |
| client->RunUntilResponseReceived(); |
| |
| const network::ResourceResponseHead& info = client->response_head(); |
| EXPECT_EQ(test.expected_was_fallback_required_by_service_worker, |
| info.was_fetched_via_service_worker); |
| EXPECT_EQ(test.expected_was_fallback_required_by_service_worker, |
| info.was_fallback_required_by_service_worker); |
| } |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, FallbackWithRequestBody_String) { |
| const std::string kData = "Hi, this is the request body (string)"; |
| auto request_body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| network::mojom::DataPipeGetterPtr data_pipe_getter_ptr; |
| request_body->AppendBytes(kData.c_str(), kData.length()); |
| |
| RunFallbackWithRequestBodyTest(std::move(request_body), kData); |
| } |
| |
| TEST_F(ServiceWorkerSubresourceLoaderTest, FallbackWithRequestBody_DataPipe) { |
| const std::string kData = "Hi, this is the request body (data pipe)"; |
| auto request_body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| network::mojom::DataPipeGetterPtr data_pipe_getter_ptr; |
| auto data_pipe_getter = std::make_unique<network::TestDataPipeGetter>( |
| kData, mojo::MakeRequest(&data_pipe_getter_ptr)); |
| request_body->AppendDataPipe(std::move(data_pipe_getter_ptr)); |
| |
| RunFallbackWithRequestBodyTest(std::move(request_body), kData); |
| } |
| |
| } // namespace service_worker_subresource_loader_unittest |
| } // namespace content |