| // Copyright 2014 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_url_request_job.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/test/histogram_tester.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "content/browser/blob_storage/chrome_blob_storage_context.h" |
| #include "content/browser/loader/resource_request_info_impl.h" |
| #include "content/browser/resource_context_impl.h" |
| #include "content/browser/service_worker/embedded_worker_registry.h" |
| #include "content/browser/service_worker/embedded_worker_test_helper.h" |
| #include "content/browser/service_worker/service_worker_context_core.h" |
| #include "content/browser/service_worker/service_worker_context_wrapper.h" |
| #include "content/browser/service_worker/service_worker_provider_host.h" |
| #include "content/browser/service_worker/service_worker_registration.h" |
| #include "content/browser/service_worker/service_worker_response_info.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_messages.h" |
| #include "content/common/service_worker/service_worker_status_code.h" |
| #include "content/common/service_worker/service_worker_types.h" |
| #include "content/common/service_worker/service_worker_utils.h" |
| #include "content/public/browser/blob_handle.h" |
| #include "content/public/common/browser_side_navigation_policy.h" |
| #include "content/public/common/request_context_frame_type.h" |
| #include "content/public/common/request_context_type.h" |
| #include "content/public/common/resource_request_body.h" |
| #include "content/public/common/resource_type.h" |
| #include "content/public/common/service_worker_modes.h" |
| #include "content/public/test/mock_resource_context.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "net/base/io_buffer.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/ssl/ssl_info.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_job_factory_impl.h" |
| #include "net/url_request/url_request_test_job.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "storage/browser/blob/blob_data_builder.h" |
| #include "storage/browser/blob/blob_storage_context.h" |
| #include "storage/browser/blob/blob_url_request_job.h" |
| #include "storage/browser/blob/blob_url_request_job_factory.h" |
| #include "storage/common/blob_storage/blob_handle.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/WebKit/public/platform/modules/serviceworker/service_worker.mojom.h" |
| #include "third_party/WebKit/public/platform/modules/serviceworker/service_worker_event_status.mojom.h" |
| #include "third_party/WebKit/public/platform/modules/serviceworker/service_worker_registration.mojom.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| const int kProviderID = 100; |
| const char kTestData[] = "Here is sample text for the blob."; |
| |
| // A simple ProtocolHandler implementation to create ServiceWorkerURLRequestJob. |
| // |
| // MockProtocolHandler is basically a mock of |
| // ServiceWorkerControlleeRequestHandler. In production code, |
| // ServiceWorkerControlleeRequestHandler::MaybeCreateJob() is called by |
| // ServiceWorkerRequestInterceptor, a custom URLRequestInterceptor, but for |
| // testing it's easier to make the job via ProtocolHandler. |
| class MockProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { |
| public: |
| MockProtocolHandler( |
| base::WeakPtr<ServiceWorkerProviderHost> provider_host, |
| const ResourceContext* resource_context, |
| base::WeakPtr<storage::BlobStorageContext> blob_storage_context, |
| ServiceWorkerURLRequestJob::Delegate* delegate) |
| : provider_host_(provider_host), |
| resource_context_(resource_context), |
| blob_storage_context_(blob_storage_context), |
| job_(nullptr), |
| delegate_(delegate), |
| resource_type_(RESOURCE_TYPE_MAIN_FRAME), |
| simulate_navigation_preload_(false) {} |
| |
| ~MockProtocolHandler() override = default; |
| |
| void set_resource_type(ResourceType type) { resource_type_ = type; } |
| void set_custom_timeout(base::Optional<base::TimeDelta> timeout) { |
| custom_timeout_ = timeout; |
| } |
| void set_simulate_navigation_preload() { |
| simulate_navigation_preload_ = true; |
| } |
| |
| // A simple version of |
| // ServiceWorkerControlleeRequestHandler::MaybeCreateJob(). |
| net::URLRequestJob* MaybeCreateJob( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) const override { |
| if (job_ && job_->ShouldFallbackToNetwork()) { |
| // Simulate fallback to network by constructing a valid response. |
| return new net::URLRequestTestJob(request, network_delegate, |
| net::URLRequestTestJob::test_headers(), |
| "PASS", true); |
| } |
| |
| job_ = new ServiceWorkerURLRequestJob( |
| request, network_delegate, provider_host_->client_uuid(), |
| blob_storage_context_, resource_context_, |
| network::mojom::FetchRequestMode::kNoCORS, |
| network::mojom::FetchCredentialsMode::kOmit, |
| FetchRedirectMode::FOLLOW_MODE, std::string() /* integrity */, |
| false /* keepalive */, resource_type_, REQUEST_CONTEXT_TYPE_HYPERLINK, |
| REQUEST_CONTEXT_FRAME_TYPE_TOP_LEVEL, |
| scoped_refptr<ResourceRequestBody>(), ServiceWorkerFetchType::FETCH, |
| custom_timeout_, delegate_); |
| if (simulate_navigation_preload_) { |
| job_->set_simulate_navigation_preload_for_test(); |
| } |
| job_->ForwardToServiceWorker(); |
| return job_; |
| } |
| ServiceWorkerURLRequestJob* job() { return job_; } |
| |
| private: |
| base::WeakPtr<ServiceWorkerProviderHost> provider_host_; |
| const ResourceContext* resource_context_; |
| base::WeakPtr<storage::BlobStorageContext> blob_storage_context_; |
| mutable ServiceWorkerURLRequestJob* job_; |
| ServiceWorkerURLRequestJob::Delegate* delegate_; |
| ResourceType resource_type_; |
| bool simulate_navigation_preload_; |
| base::Optional<base::TimeDelta> custom_timeout_; |
| }; |
| |
| // Returns a BlobProtocolHandler that uses |blob_storage_context|. Caller owns |
| // the memory. |
| std::unique_ptr<storage::BlobProtocolHandler> CreateMockBlobProtocolHandler( |
| storage::BlobStorageContext* blob_storage_context) { |
| return std::make_unique<storage::BlobProtocolHandler>(blob_storage_context); |
| } |
| |
| std::unique_ptr<ServiceWorkerHeaderMap> MakeHeaders() { |
| auto headers = std::make_unique<ServiceWorkerHeaderMap>(); |
| (*headers)["Pineapple"] = "Pen"; |
| (*headers)["Foo"] = "Bar"; |
| (*headers)["Set-Cookie"] = "CookieCookieCookie"; |
| return headers; |
| } |
| |
| void SaveStatusCallback(ServiceWorkerStatusCode* out_status, |
| ServiceWorkerStatusCode status) { |
| *out_status = status; |
| } |
| |
| } // namespace |
| |
| // ServiceWorkerURLRequestJobTest is for testing the handling of URL requests by |
| // a service worker. |
| // |
| // To use it, call SetUpWithHelper() in your test. This sets up the service |
| // worker and the scaffolding to make the worker handle https URLRequests. (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.) Example: |
| // |
| // auto request = url_request_context_.CreateRequest( |
| // GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| // &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| // request->set_method("GET"); |
| // request->Start(); |
| // base::RunLoop().RunUntilIdle(); |
| // // Now the request was handled by a ServiceWorkerURLRequestJob. |
| // |
| // ServiceWorkerURLRequestJobTest is also a |
| // ServiceWorkerURLRequestJob::Delegate. In production code, |
| // ServiceWorkerControlleeRequestHandler is the Delegate (for non-"foreign |
| // fetch" request interceptions). So this class also basically mocks that part |
| // of ServiceWorkerControlleeRequestHandler. |
| class ServiceWorkerURLRequestJobTest |
| : public testing::Test, |
| public ServiceWorkerURLRequestJob::Delegate { |
| public: |
| MockProtocolHandler* handler() { return protocol_handler_; } |
| |
| protected: |
| ServiceWorkerURLRequestJobTest() |
| : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} |
| ~ServiceWorkerURLRequestJobTest() override {} |
| |
| void SetUp() override { |
| browser_context_.reset(new TestBrowserContext); |
| InitializeResourceContext(browser_context_.get()); |
| } |
| |
| void SetUpWithHelper(std::unique_ptr<EmbeddedWorkerTestHelper> helper) { |
| helper_ = std::move(helper); |
| helper_->context()->storage()->LazyInitializeForTest( |
| base::BindOnce(&base::DoNothing)); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Prepare HTTP response info for the version. |
| auto http_info = std::make_unique<net::HttpResponseInfo>(); |
| http_info->ssl_info.cert = |
| net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem"); |
| EXPECT_TRUE(http_info->ssl_info.is_valid()); |
| http_info->ssl_info.security_bits = 0x100; |
| // SSL3 TLS_DHE_RSA_WITH_AES_256_CBC_SHA |
| http_info->ssl_info.connection_status = 0x300039; |
| http_info->headers = base::MakeRefCounted<net::HttpResponseHeaders>(""); |
| |
| // Create a registration and service worker version. |
| blink::mojom::ServiceWorkerRegistrationOptions options; |
| options.scope = GURL("https://example.com/"); |
| registration_ = new ServiceWorkerRegistration( |
| options, 1L, helper_->context()->AsWeakPtr()); |
| version_ = new ServiceWorkerVersion( |
| registration_.get(), GURL("https://example.com/service_worker.js"), 1L, |
| helper_->context()->AsWeakPtr()); |
| // If script streaming is not enabled, SetMainScriptHttpResponseInfo() |
| // should be called manually since |http_info| which is stored in disk cache |
| // won't be read during SWVersion::StartWorker() in tests. |
| if (!ServiceWorkerUtils::IsScriptStreamingEnabled()) |
| version_->SetMainScriptHttpResponseInfo(*http_info); |
| std::vector<ServiceWorkerDatabase::ResourceRecord> records; |
| records.push_back(WriteToDiskCacheWithCustomResponseInfoSync( |
| helper_->context()->storage(), version_->script_url(), 10, |
| std::move(http_info), "I'm the body", "I'm the meta data")); |
| version_->script_cache_map()->SetResources(records); |
| version_->set_fetch_handler_existence( |
| ServiceWorkerVersion::FetchHandlerExistence::EXISTS); |
| |
| // Make the registration findable via storage functions. |
| ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED; |
| helper_->context()->storage()->StoreRegistration( |
| registration_.get(), |
| version_.get(), |
| CreateReceiverOnCurrentThread(&status)); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(SERVICE_WORKER_OK, status); |
| |
| // Create a controlled client. |
| std::unique_ptr<ServiceWorkerProviderHost> provider_host = |
| CreateProviderHostForWindow( |
| helper_->mock_render_process_id(), kProviderID, |
| true /* is_parent_frame_secure */, helper_->context()->AsWeakPtr(), |
| &remote_endpoint_); |
| provider_host_ = provider_host->AsWeakPtr(); |
| provider_host->SetDocumentUrl(GURL("https://example.com/")); |
| registration_->SetActiveVersion(version_); |
| provider_host->AssociateRegistration(registration_.get(), |
| false /* notify_controllerchange */); |
| |
| // Set up scaffolding for handling URL requests. |
| ChromeBlobStorageContext* chrome_blob_storage_context = |
| ChromeBlobStorageContext::GetFor(browser_context_.get()); |
| // Wait for chrome_blob_storage_context to finish initializing. |
| base::RunLoop().RunUntilIdle(); |
| storage::BlobStorageContext* blob_storage_context = |
| chrome_blob_storage_context->context(); |
| url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl); |
| std::unique_ptr<MockProtocolHandler> handler(new MockProtocolHandler( |
| provider_host->AsWeakPtr(), browser_context_->GetResourceContext(), |
| blob_storage_context->AsWeakPtr(), this)); |
| protocol_handler_ = handler.get(); |
| url_request_job_factory_->SetProtocolHandler("https", std::move(handler)); |
| url_request_job_factory_->SetProtocolHandler( |
| "blob", CreateMockBlobProtocolHandler(blob_storage_context)); |
| url_request_context_.set_job_factory(url_request_job_factory_.get()); |
| |
| helper_->context()->AddProviderHost(std::move(provider_host)); |
| } |
| |
| void TearDown() override { |
| version_ = nullptr; |
| registration_ = nullptr; |
| helper_.reset(); |
| request_.reset(); |
| } |
| |
| void TestRequestResult(int expected_status_code, |
| const std::string& expected_status_text, |
| const std::string& expected_response, |
| bool expect_valid_ssl) { |
| EXPECT_EQ(net::OK, url_request_delegate_.request_status()); |
| EXPECT_EQ(expected_status_code, |
| request_->response_headers()->response_code()); |
| EXPECT_EQ(expected_status_text, |
| request_->response_headers()->GetStatusText()); |
| EXPECT_EQ(expected_response, url_request_delegate_.data_received()); |
| const net::SSLInfo& ssl_info = request_->response_info().ssl_info; |
| if (expect_valid_ssl) { |
| EXPECT_TRUE(ssl_info.is_valid()); |
| EXPECT_EQ(ssl_info.security_bits, 0x100); |
| EXPECT_EQ(ssl_info.connection_status, 0x300039); |
| } else { |
| EXPECT_FALSE(ssl_info.is_valid()); |
| } |
| } |
| |
| void CheckHeaders(const net::HttpResponseHeaders* headers) { |
| size_t iter = 0; |
| std::string name; |
| std::string value; |
| EXPECT_TRUE(headers->EnumerateHeaderLines(&iter, &name, &value)); |
| EXPECT_EQ("Foo", name); |
| EXPECT_EQ("Bar", value); |
| EXPECT_TRUE(headers->EnumerateHeaderLines(&iter, &name, &value)); |
| EXPECT_EQ("Pineapple", name); |
| EXPECT_EQ("Pen", value); |
| EXPECT_TRUE(headers->EnumerateHeaderLines(&iter, &name, &value)); |
| EXPECT_EQ("Set-Cookie", name); |
| EXPECT_EQ("CookieCookieCookie", value); |
| EXPECT_FALSE(headers->EnumerateHeaderLines(&iter, &name, &value)); |
| } |
| |
| void TestRequest(int expected_status_code, |
| const std::string& expected_status_text, |
| const std::string& expected_response, |
| bool expect_valid_ssl) { |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| request_->set_method("GET"); |
| request_->Start(); |
| base::RunLoop().RunUntilIdle(); |
| TestRequestResult(expected_status_code, expected_status_text, |
| expected_response, expect_valid_ssl); |
| } |
| |
| bool HasWork() { return version_->HasWork(); } |
| |
| // Runs a request where the active worker starts a request in ACTIVATING state |
| // and fails to reach ACTIVATED. |
| void RunFailToActivateTest(ResourceType resource_type) { |
| protocol_handler_->set_resource_type(resource_type); |
| |
| // Start a request with an activating worker. |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATING); |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request_->set_method("GET"); |
| request_->Start(); |
| |
| // Proceed until the job starts waiting for the worker to activate. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Simulate another worker kicking out the incumbent worker. PostTask since |
| // it might respond synchronously, and the TestDelegate would complain that |
| // the message loop isn't being run. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&ServiceWorkerVersion::SetStatus, version_, |
| ServiceWorkerVersion::REDUNDANT)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Starts a navigation request with navigation preload enabled. |
| void SetUpNavigationPreloadTest(ResourceType resource_type) { |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| protocol_handler_->set_resource_type(resource_type); |
| protocol_handler_->set_simulate_navigation_preload(); |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| ResourceRequestInfo::AllocateForTesting( |
| request_.get(), resource_type, browser_context_->GetResourceContext(), |
| -1, -1, -1, resource_type == RESOURCE_TYPE_MAIN_FRAME, true, true, |
| PREVIEWS_OFF, nullptr); |
| |
| request_->set_method("GET"); |
| request_->Start(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // ServiceWorkerURLRequestJob::Delegate ------------------------------------- |
| void OnPrepareToRestart() override { times_prepare_to_restart_invoked_++; } |
| |
| ServiceWorkerVersion* GetServiceWorkerVersion( |
| ServiceWorkerMetrics::URLRequestJobResult* result) override { |
| if (!provider_host_) { |
| *result = ServiceWorkerMetrics::REQUEST_JOB_ERROR_NO_PROVIDER_HOST; |
| return nullptr; |
| } |
| if (!provider_host_->active_version()) { |
| *result = ServiceWorkerMetrics::REQUEST_JOB_ERROR_NO_ACTIVE_VERSION; |
| return nullptr; |
| } |
| return provider_host_->active_version(); |
| } |
| |
| bool RequestStillValid( |
| ServiceWorkerMetrics::URLRequestJobResult* result) override { |
| if (!provider_host_) { |
| *result = ServiceWorkerMetrics::REQUEST_JOB_ERROR_NO_PROVIDER_HOST; |
| return false; |
| } |
| return true; |
| } |
| |
| void MainResourceLoadFailed() override { |
| ASSERT_TRUE(provider_host_); |
| // Detach the controller so subresource requests also skip the worker. |
| provider_host_->NotifyControllerLost(); |
| } |
| // --------------------------------------------------------------------------- |
| |
| TestBrowserThreadBundle thread_bundle_; |
| base::SimpleTestTickClock tick_clock_; |
| |
| std::unique_ptr<TestBrowserContext> browser_context_; |
| std::unique_ptr<EmbeddedWorkerTestHelper> helper_; |
| scoped_refptr<ServiceWorkerRegistration> registration_; |
| scoped_refptr<ServiceWorkerVersion> version_; |
| |
| std::unique_ptr<net::URLRequestJobFactoryImpl> url_request_job_factory_; |
| net::URLRequestContext url_request_context_; |
| net::TestDelegate url_request_delegate_; |
| std::unique_ptr<net::URLRequest> request_; |
| |
| int times_prepare_to_restart_invoked_ = 0; |
| base::WeakPtr<ServiceWorkerProviderHost> provider_host_; |
| ServiceWorkerRemoteProviderEndpoint remote_endpoint_; |
| |
| // Not owned. |
| // The ProtocolHandler for https requests, which creates a |
| // ServiceWorkerURLRequestJob. |
| MockProtocolHandler* protocol_handler_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ServiceWorkerURLRequestJobTest); |
| }; |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, Simple) { |
| SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath())); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| TestRequest(200, "OK", std::string(), true /* expect_valid_ssl */); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_TRUE(info->url_list_via_service_worker().empty()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| EXPECT_FALSE(info->response_is_in_cache_storage()); |
| EXPECT_EQ(std::string(), info->response_cache_storage_cache_name()); |
| } |
| |
| // Helper for controlling when to start a worker and respond to a fetch event. |
| class DelayHelper : public EmbeddedWorkerTestHelper { |
| public: |
| DelayHelper(ServiceWorkerURLRequestJobTest* test) |
| : EmbeddedWorkerTestHelper(base::FilePath()), test_(test) {} |
| ~DelayHelper() override {} |
| |
| void CompleteNavigationPreload() { |
| test_->handler()->job()->OnNavigationPreloadResponse(); |
| } |
| |
| void CompleteStartWorker() { |
| EmbeddedWorkerTestHelper::OnStartWorker( |
| embedded_worker_id_, service_worker_version_id_, scope_, script_url_, |
| pause_after_download_, std::move(start_worker_request_), |
| std::move(controller_request_), std::move(service_worker_host_), |
| std::move(start_worker_instance_host_), std::move(provider_info_), |
| std::move(installed_scripts_info_)); |
| } |
| |
| void Respond() { |
| response_callback_->OnResponse( |
| ServiceWorkerResponse( |
| std::make_unique<std::vector<GURL>>(), 200, "OK", |
| network::mojom::FetchResponseType::kDefault, |
| std::make_unique<ServiceWorkerHeaderMap>(), std::string(), 0, |
| 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 */), |
| base::Time::Now()); |
| std::move(finish_callback_) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::Time::Now()); |
| } |
| |
| protected: |
| void OnStartWorker( |
| int embedded_worker_id, |
| int64_t service_worker_version_id, |
| const GURL& scope, |
| const GURL& script_url, |
| bool pause_after_download, |
| mojom::ServiceWorkerEventDispatcherRequest dispatcher_request, |
| mojom::ControllerServiceWorkerRequest controller_request, |
| blink::mojom::ServiceWorkerHostAssociatedPtrInfo service_worker_host, |
| mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host, |
| mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info, |
| mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info) |
| override { |
| embedded_worker_id_ = embedded_worker_id; |
| service_worker_version_id_ = service_worker_version_id; |
| scope_ = scope; |
| script_url_ = script_url; |
| pause_after_download_ = pause_after_download; |
| start_worker_request_ = std::move(dispatcher_request); |
| controller_request_ = std::move(controller_request); |
| service_worker_host_ = std::move(service_worker_host); |
| start_worker_instance_host_ = std::move(instance_host); |
| provider_info_ = std::move(provider_info); |
| installed_scripts_info_ = std::move(installed_scripts_info); |
| } |
| |
| void OnLegacyFetchEvent( |
| int embedded_worker_id, |
| const ServiceWorkerFetchRequest& /* request */, |
| mojom::FetchEventPreloadHandlePtr preload_handle, |
| mojom::ServiceWorkerFetchResponseCallbackPtr response_callback, |
| mojom::ServiceWorkerEventDispatcher::DispatchFetchEventCallback |
| finish_callback) override { |
| embedded_worker_id_ = embedded_worker_id; |
| response_callback_ = std::move(response_callback); |
| finish_callback_ = std::move(finish_callback); |
| preload_handle_ = std::move(preload_handle); |
| } |
| |
| private: |
| int64_t service_worker_version_id_; |
| GURL scope_; |
| GURL script_url_; |
| bool pause_after_download_; |
| mojom::ServiceWorkerEventDispatcherRequest start_worker_request_; |
| mojom::ControllerServiceWorkerRequest controller_request_; |
| blink::mojom::ServiceWorkerHostAssociatedPtrInfo service_worker_host_; |
| mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo |
| start_worker_instance_host_; |
| mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info_; |
| mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info_; |
| int embedded_worker_id_ = 0; |
| mojom::ServiceWorkerFetchResponseCallbackPtr response_callback_; |
| mojom::FetchEventPreloadHandlePtr preload_handle_; |
| mojom::ServiceWorkerEventDispatcher::DispatchFetchEventCallback |
| finish_callback_; |
| ServiceWorkerURLRequestJobTest* test_; |
| DISALLOW_COPY_AND_ASSIGN(DelayHelper); |
| }; |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, |
| NavPreloadMetrics_WorkerAlreadyStarted_MainFrame) { |
| SetUpWithHelper(std::make_unique<DelayHelper>(this)); |
| DelayHelper* helper = static_cast<DelayHelper*>(helper_.get()); |
| |
| // Start the worker before the navigation. |
| ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE; |
| base::HistogramTester histogram_tester; |
| version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN, |
| base::BindOnce(&SaveStatusCallback, &status)); |
| base::RunLoop().RunUntilIdle(); |
| helper->CompleteStartWorker(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(SERVICE_WORKER_OK, status); |
| |
| // Do the navigation. |
| SetUpNavigationPreloadTest(RESOURCE_TYPE_MAIN_FRAME); |
| helper->CompleteNavigationPreload(); |
| helper->Respond(); |
| base::RunLoop().RunUntilIdle(); |
| |
| histogram_tester.ExpectUniqueSample( |
| "ServiceWorker.ActivatedWorkerPreparationForMainFrame.Type_" |
| "NavigationPreloadEnabled", |
| static_cast<int>(ServiceWorkerMetrics::WorkerPreparationType::RUNNING), |
| 1); |
| histogram_tester.ExpectUniqueSample( |
| "ServiceWorker.NavPreload.FinishedFirst_MainFrame", false, 1); |
| histogram_tester.ExpectTotalCount( |
| "ServiceWorker.NavPreload.FinishedFirst_MainFrame_" |
| "StartWorkerExistingProcess", |
| 0); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, |
| NavPreloadMetrics_WorkerFirst_MainFrame) { |
| SetUpWithHelper(std::make_unique<DelayHelper>(this)); |
| DelayHelper* helper = static_cast<DelayHelper*>(helper_.get()); |
| |
| base::HistogramTester histogram_tester; |
| SetUpNavigationPreloadTest(RESOURCE_TYPE_MAIN_FRAME); |
| |
| // Worker finishes first. |
| helper->CompleteStartWorker(); |
| helper->CompleteNavigationPreload(); |
| helper->Respond(); |
| base::RunLoop().RunUntilIdle(); |
| |
| histogram_tester.ExpectUniqueSample( |
| "ServiceWorker.ActivatedWorkerPreparationForMainFrame.Type_" |
| "NavigationPreloadEnabled", |
| static_cast<int>(ServiceWorkerMetrics::WorkerPreparationType:: |
| START_IN_EXISTING_READY_PROCESS), |
| 1); |
| histogram_tester.ExpectUniqueSample( |
| "ServiceWorker.NavPreload.FinishedFirst_MainFrame", false, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ServiceWorker.NavPreload.FinishedFirst_MainFrame_WorkerStartOccurred", |
| false, 1); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, |
| NavPreloadMetrics_NavPreloadFirst_MainFrame) { |
| SetUpWithHelper(std::make_unique<DelayHelper>(this)); |
| DelayHelper* helper = static_cast<DelayHelper*>(helper_.get()); |
| |
| base::HistogramTester histogram_tester; |
| SetUpNavigationPreloadTest(RESOURCE_TYPE_MAIN_FRAME); |
| |
| // Nav preload finishes first. |
| helper->CompleteNavigationPreload(); |
| helper->CompleteStartWorker(); |
| helper->Respond(); |
| base::RunLoop().RunUntilIdle(); |
| |
| histogram_tester.ExpectUniqueSample( |
| "ServiceWorker.ActivatedWorkerPreparationForMainFrame.Type_" |
| "NavigationPreloadEnabled", |
| static_cast<int>(ServiceWorkerMetrics::WorkerPreparationType:: |
| START_IN_EXISTING_READY_PROCESS), |
| 1); |
| histogram_tester.ExpectUniqueSample( |
| "ServiceWorker.NavPreload.FinishedFirst_MainFrame", true, 1); |
| histogram_tester.ExpectUniqueSample( |
| "ServiceWorker.NavPreload.FinishedFirst_MainFrame_WorkerStartOccurred", |
| true, 1); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, NavPreloadMetrics_WorkerFirst_SubFrame) { |
| SetUpWithHelper(std::make_unique<DelayHelper>(this)); |
| DelayHelper* helper = static_cast<DelayHelper*>(helper_.get()); |
| |
| base::HistogramTester histogram_tester; |
| SetUpNavigationPreloadTest(RESOURCE_TYPE_SUB_FRAME); |
| |
| // Worker finishes first. |
| helper->CompleteStartWorker(); |
| helper->CompleteNavigationPreload(); |
| helper->Respond(); |
| base::RunLoop().RunUntilIdle(); |
| |
| histogram_tester.ExpectTotalCount( |
| "ServiceWorker.ActivatedWorkerPreparationForMainFrame.Type_" |
| "NavigationPreloadEnabled", |
| 0); |
| histogram_tester.ExpectTotalCount( |
| "ServiceWorker.NavPreload.FinishedFirst_MainFrame", 0); |
| histogram_tester.ExpectTotalCount( |
| "ServiceWorker.NavPreload.FinishedFirst_MainFrame_" |
| "StartWorkerExistingProcess", |
| 0); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, |
| NavPreloadMetrics_NavPreloadFirst_SubFrame) { |
| SetUpWithHelper(std::make_unique<DelayHelper>(this)); |
| DelayHelper* helper = static_cast<DelayHelper*>(helper_.get()); |
| |
| base::HistogramTester histogram_tester; |
| SetUpNavigationPreloadTest(RESOURCE_TYPE_SUB_FRAME); |
| |
| // Nav preload finishes first. |
| helper->CompleteNavigationPreload(); |
| helper->CompleteStartWorker(); |
| helper->Respond(); |
| base::RunLoop().RunUntilIdle(); |
| |
| histogram_tester.ExpectTotalCount( |
| "ServiceWorker.ActivatedWorkerPreparationForMainFrame.Type_" |
| "NavigationPreloadEnabled", |
| 0); |
| histogram_tester.ExpectTotalCount( |
| "ServiceWorker.NavPreload.FinishedFirst_MainFrame", 0); |
| histogram_tester.ExpectTotalCount( |
| "ServiceWorker.NavPreload.FinishedFirst_MainFrame_" |
| "StartWorkerExistingProcess", |
| 0); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, CustomTimeout) { |
| SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath())); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| |
| // Set mock clock on version_ to check timeout behavior. |
| tick_clock_.SetNowTicks(base::TimeTicks::Now()); |
| version_->SetTickClockForTesting(&tick_clock_); |
| |
| protocol_handler_->set_custom_timeout(base::TimeDelta::FromSeconds(5)); |
| TestRequest(200, "OK", std::string(), true /* expect_valid_ssl */); |
| EXPECT_EQ(base::TimeDelta::FromSeconds(5), version_->remaining_timeout()); |
| } |
| |
| class ProviderDeleteHelper : public EmbeddedWorkerTestHelper { |
| public: |
| ProviderDeleteHelper() : EmbeddedWorkerTestHelper(base::FilePath()) {} |
| ~ProviderDeleteHelper() override {} |
| |
| protected: |
| void OnLegacyFetchEvent( |
| int /* embedded_worker_id */, |
| const ServiceWorkerFetchRequest& /* request */, |
| mojom::FetchEventPreloadHandlePtr /* preload_handle */, |
| mojom::ServiceWorkerFetchResponseCallbackPtr response_callback, |
| mojom::ServiceWorkerEventDispatcher::DispatchFetchEventCallback |
| finish_callback) override { |
| context()->RemoveProviderHost(mock_render_process_id(), kProviderID); |
| response_callback->OnResponse( |
| ServiceWorkerResponse( |
| std::make_unique<std::vector<GURL>>(), 200, "OK", |
| network::mojom::FetchResponseType::kDefault, |
| std::make_unique<ServiceWorkerHeaderMap>(), std::string(), 0, |
| 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 */), |
| base::Time::Now()); |
| std::move(finish_callback) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::Time::Now()); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ProviderDeleteHelper); |
| }; |
| |
| // Shouldn't crash if the ProviderHost is deleted prior to completion of the |
| // fetch event. |
| TEST_F(ServiceWorkerURLRequestJobTest, DeletedProviderHostOnFetchEvent) { |
| SetUpWithHelper(std::make_unique<ProviderDeleteHelper>()); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| TestRequest(500, "Service Worker Response Error", std::string(), |
| false /* expect_valid_ssl */); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, DeletedProviderHostBeforeFetchEvent) { |
| SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath())); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| request_->set_method("GET"); |
| request_->Start(); |
| helper_->context()->RemoveProviderHost(helper_->mock_render_process_id(), |
| kProviderID); |
| base::RunLoop().RunUntilIdle(); |
| TestRequestResult(500, "Service Worker Response Error", std::string(), |
| false /* expect_valid_ssl */); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_TRUE(info->service_worker_start_time().is_null()); |
| EXPECT_TRUE(info->service_worker_ready_time().is_null()); |
| } |
| |
| // Responds to fetch events with a blob. |
| class BlobResponder : public EmbeddedWorkerTestHelper { |
| public: |
| BlobResponder(const std::string& blob_uuid, uint64_t blob_size) |
| : EmbeddedWorkerTestHelper(base::FilePath()), |
| blob_uuid_(blob_uuid), |
| blob_size_(blob_size) {} |
| ~BlobResponder() override = default; |
| |
| protected: |
| void OnLegacyFetchEvent( |
| int /* embedded_worker_id */, |
| const ServiceWorkerFetchRequest& /* request */, |
| mojom::FetchEventPreloadHandlePtr /* preload_handle */, |
| mojom::ServiceWorkerFetchResponseCallbackPtr response_callback, |
| mojom::ServiceWorkerEventDispatcher::DispatchFetchEventCallback |
| finish_callback) override { |
| response_callback->OnResponse( |
| ServiceWorkerResponse( |
| std::make_unique<std::vector<GURL>>(), 200, "OK", |
| network::mojom::FetchResponseType::kDefault, MakeHeaders(), |
| blob_uuid_, 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 */), |
| base::Time::Now()); |
| std::move(finish_callback) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::Time::Now()); |
| } |
| |
| std::string blob_uuid_; |
| uint64_t blob_size_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(BlobResponder); |
| }; |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, BlobResponse) { |
| ChromeBlobStorageContext* blob_storage_context = |
| ChromeBlobStorageContext::GetFor(browser_context_.get()); |
| // Wait for blob_storage_context to finish initializing. |
| base::RunLoop().RunUntilIdle(); |
| |
| std::string expected_response; |
| expected_response.reserve((sizeof(kTestData) - 1) * 1024); |
| |
| auto blob_data = std::make_unique<storage::BlobDataBuilder>("blob-id:myblob"); |
| for (int i = 0; i < 1024; ++i) { |
| blob_data->AppendData(kTestData); |
| expected_response += kTestData; |
| } |
| std::unique_ptr<storage::BlobDataHandle> blob_handle = |
| blob_storage_context->context()->AddFinishedBlob(blob_data.get()); |
| SetUpWithHelper(std::make_unique<BlobResponder>(blob_handle->uuid(), |
| expected_response.size())); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| TestRequest(200, "OK", expected_response, true /* expect_valid_ssl */); |
| CheckHeaders(request_->response_headers()); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, NonExistentBlobUUIDResponse) { |
| SetUpWithHelper( |
| std::make_unique<BlobResponder>("blob-id:nothing-is-here", 0)); |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| TestRequest(500, "Service Worker Response Error", std::string(), |
| true /* expect_valid_ssl */); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| } |
| |
| // Responds to fetch events with a stream. |
| class StreamResponder : public EmbeddedWorkerTestHelper { |
| public: |
| explicit StreamResponder( |
| blink::mojom::ServiceWorkerStreamCallbackRequest callback_request, |
| mojo::ScopedDataPipeConsumerHandle consumer_handle) |
| : EmbeddedWorkerTestHelper(base::FilePath()) { |
| EXPECT_TRUE(stream_handle_.is_null()); |
| stream_handle_ = blink::mojom::ServiceWorkerStreamHandle::New(); |
| stream_handle_->callback_request = std::move(callback_request); |
| stream_handle_->stream = std::move(consumer_handle); |
| } |
| ~StreamResponder() override {} |
| |
| protected: |
| void OnLegacyFetchEvent( |
| int /* embedded_worker_id */, |
| const ServiceWorkerFetchRequest& /* request */, |
| mojom::FetchEventPreloadHandlePtr /* preload_handle */, |
| mojom::ServiceWorkerFetchResponseCallbackPtr response_callback, |
| mojom::ServiceWorkerEventDispatcher::DispatchFetchEventCallback |
| finish_callback) override { |
| ASSERT_FALSE(stream_handle_.is_null()); |
| response_callback->OnResponseStream( |
| ServiceWorkerResponse( |
| std::make_unique<std::vector<GURL>>(), 200, "OK", |
| network::mojom::FetchResponseType::kDefault, MakeHeaders(), "", 0, |
| 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 */), |
| std::move(stream_handle_), base::Time::Now()); |
| std::move(finish_callback) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::Time::Now()); |
| } |
| |
| blink::mojom::ServiceWorkerStreamHandlePtr stream_handle_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(StreamResponder); |
| }; |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, StreamResponse) { |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| mojo::DataPipe data_pipe; |
| SetUpWithHelper( |
| std::make_unique<StreamResponder>(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle))); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request_->set_method("GET"); |
| request_->Start(); |
| |
| std::string expected_response; |
| expected_response.reserve((sizeof(kTestData) - 1) * 1024); |
| for (int i = 0; i < 1024; ++i) { |
| expected_response += kTestData; |
| uint32_t written_bytes = sizeof(kTestData) - 1; |
| MojoResult result = data_pipe.producer_handle->WriteData( |
| kTestData, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| ASSERT_EQ(MOJO_RESULT_OK, result); |
| EXPECT_EQ(sizeof(kTestData) - 1, written_bytes); |
| } |
| stream_callback->OnCompleted(); |
| data_pipe.producer_handle.reset(); |
| |
| EXPECT_FALSE(HasWork()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(HasWork()); |
| EXPECT_EQ(net::OK, url_request_delegate_.request_status()); |
| net::HttpResponseHeaders* headers = request_->response_headers(); |
| EXPECT_EQ(200, headers->response_code()); |
| EXPECT_EQ("OK", headers->GetStatusText()); |
| CheckHeaders(headers); |
| EXPECT_EQ(expected_response, url_request_delegate_.data_received()); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| |
| request_.reset(); |
| EXPECT_FALSE(HasWork()); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, StreamResponse_ConsecutiveRead) { |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| mojo::DataPipe data_pipe; |
| SetUpWithHelper( |
| std::make_unique<StreamResponder>(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle))); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request_->set_method("GET"); |
| request_->Start(); |
| std::string expected_response; |
| expected_response.reserve((sizeof(kTestData) - 1) * 1024); |
| for (int i = 0; i < 1024; ++i) { |
| expected_response += kTestData; |
| uint32_t written_bytes = sizeof(kTestData) - 1; |
| MojoResult result = data_pipe.producer_handle->WriteData( |
| kTestData, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| ASSERT_EQ(MOJO_RESULT_OK, result); |
| EXPECT_EQ(sizeof(kTestData) - 1, written_bytes); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(expected_response, url_request_delegate_.data_received()); |
| } |
| stream_callback->OnCompleted(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // IO is still pending since |producer_handle| is not yet reset, but the data |
| // should have been received by now. |
| EXPECT_EQ(net::ERR_IO_PENDING, url_request_delegate_.request_status()); |
| EXPECT_EQ(200, |
| request_->response_headers()->response_code()); |
| EXPECT_EQ("OK", |
| request_->response_headers()->GetStatusText()); |
| EXPECT_EQ(expected_response, url_request_delegate_.data_received()); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, StreamResponseAndCancel) { |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| mojo::DataPipe data_pipe; |
| SetUpWithHelper( |
| std::make_unique<StreamResponder>(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle))); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request_->set_method("GET"); |
| request_->Start(); |
| EXPECT_FALSE(HasWork()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(HasWork()); |
| |
| for (int i = 0; i < 512; ++i) { |
| uint32_t written_bytes = sizeof(kTestData) - 1; |
| MojoResult result = data_pipe.producer_handle->WriteData( |
| kTestData, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| ASSERT_EQ(MOJO_RESULT_OK, result); |
| EXPECT_EQ(sizeof(kTestData) - 1, written_bytes); |
| } |
| EXPECT_TRUE(data_pipe.producer_handle.is_valid()); |
| request_->Cancel(); |
| EXPECT_FALSE(HasWork()); |
| |
| // Fail to write the data pipe because it's already canceled. |
| uint32_t written_bytes = sizeof(kTestData) - 1; |
| MojoResult result = data_pipe.producer_handle->WriteData( |
| kTestData, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); |
| |
| stream_callback->OnAborted(); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(data_pipe.consumer_handle.is_valid()); |
| EXPECT_EQ(net::ERR_ABORTED, url_request_delegate_.request_status()); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, StreamResponse_Abort) { |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| mojo::DataPipe data_pipe; |
| SetUpWithHelper( |
| std::make_unique<StreamResponder>(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle))); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request_->set_method("GET"); |
| request_->Start(); |
| |
| std::string expected_response; |
| expected_response.reserve((sizeof(kTestData) - 1) * 1024); |
| for (int i = 0; i < 1024; ++i) { |
| expected_response += kTestData; |
| uint32_t written_bytes = sizeof(kTestData) - 1; |
| MojoResult result = data_pipe.producer_handle->WriteData( |
| kTestData, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| ASSERT_EQ(MOJO_RESULT_OK, result); |
| EXPECT_EQ(sizeof(kTestData) - 1, written_bytes); |
| } |
| stream_callback->OnAborted(); |
| data_pipe.producer_handle.reset(); |
| |
| EXPECT_FALSE(HasWork()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(HasWork()); |
| EXPECT_EQ(net::ERR_CONNECTION_RESET, url_request_delegate_.request_status()); |
| |
| net::HttpResponseHeaders* headers = request_->response_headers(); |
| EXPECT_EQ(200, headers->response_code()); |
| EXPECT_EQ("OK", headers->GetStatusText()); |
| CheckHeaders(headers); |
| EXPECT_EQ(expected_response, url_request_delegate_.data_received()); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| |
| request_.reset(); |
| EXPECT_FALSE(HasWork()); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, StreamResponse_AbortBeforeData) { |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| mojo::DataPipe data_pipe; |
| SetUpWithHelper( |
| std::make_unique<StreamResponder>(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle))); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request_->set_method("GET"); |
| request_->Start(); |
| base::RunLoop().RunUntilIdle(); |
| stream_callback->OnAborted(); |
| base::RunLoop().RunUntilIdle(); |
| |
| std::string expected_response; |
| expected_response.reserve((sizeof(kTestData) - 1) * 1024); |
| for (int i = 0; i < 1024; ++i) { |
| expected_response += kTestData; |
| uint32_t written_bytes = sizeof(kTestData) - 1; |
| MojoResult result = data_pipe.producer_handle->WriteData( |
| kTestData, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| ASSERT_EQ(MOJO_RESULT_OK, result); |
| EXPECT_EQ(sizeof(kTestData) - 1, written_bytes); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(expected_response, url_request_delegate_.data_received()); |
| } |
| |
| data_pipe.producer_handle.reset(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(net::ERR_CONNECTION_RESET, url_request_delegate_.request_status()); |
| EXPECT_EQ(200, |
| request_->response_headers()->response_code()); |
| EXPECT_EQ("OK", |
| request_->response_headers()->GetStatusText()); |
| EXPECT_EQ(expected_response, url_request_delegate_.data_received()); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, StreamResponse_AbortAfterData) { |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| mojo::DataPipe data_pipe; |
| SetUpWithHelper( |
| std::make_unique<StreamResponder>(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle))); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request_->set_method("GET"); |
| request_->Start(); |
| base::RunLoop().RunUntilIdle(); |
| data_pipe.producer_handle.reset(); |
| base::RunLoop().RunUntilIdle(); |
| stream_callback->OnAborted(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(net::ERR_CONNECTION_RESET, url_request_delegate_.request_status()); |
| EXPECT_EQ(200, request_->response_headers()->response_code()); |
| EXPECT_EQ("OK", request_->response_headers()->GetStatusText()); |
| EXPECT_EQ("", url_request_delegate_.data_received()); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, StreamResponse_ConsecutiveReadAndAbort) { |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| mojo::DataPipe data_pipe; |
| SetUpWithHelper( |
| std::make_unique<StreamResponder>(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle))); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request_->set_method("GET"); |
| request_->Start(); |
| std::string expected_response; |
| expected_response.reserve((sizeof(kTestData) - 1) * 1024); |
| for (int i = 0; i < 512; ++i) { |
| expected_response += kTestData; |
| uint32_t written_bytes = sizeof(kTestData) - 1; |
| MojoResult result = data_pipe.producer_handle->WriteData( |
| kTestData, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| ASSERT_EQ(MOJO_RESULT_OK, result); |
| EXPECT_EQ(sizeof(kTestData) - 1, written_bytes); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(expected_response, url_request_delegate_.data_received()); |
| } |
| stream_callback->OnAborted(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // IO is still pending since |producer_handle| is not yet reset, but the data |
| // should have been received by now. |
| EXPECT_EQ(net::ERR_IO_PENDING, url_request_delegate_.request_status()); |
| EXPECT_EQ(200, request_->response_headers()->response_code()); |
| EXPECT_EQ("OK", request_->response_headers()->GetStatusText()); |
| EXPECT_EQ(expected_response, url_request_delegate_.data_received()); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| } |
| |
| // Helper to simulate failing to dispatch a fetch event to a worker. |
| class FailFetchHelper : public EmbeddedWorkerTestHelper { |
| public: |
| FailFetchHelper() : EmbeddedWorkerTestHelper(base::FilePath()) {} |
| ~FailFetchHelper() override {} |
| |
| protected: |
| void OnLegacyFetchEvent( |
| int embedded_worker_id, |
| const ServiceWorkerFetchRequest& /* request */, |
| mojom::FetchEventPreloadHandlePtr /* preload_handle */, |
| mojom::ServiceWorkerFetchResponseCallbackPtr /* response_callback */, |
| mojom::ServiceWorkerEventDispatcher::DispatchFetchEventCallback |
| finish_callback) override { |
| SimulateWorkerStopped(embedded_worker_id); |
| std::move(finish_callback) |
| .Run(blink::mojom::ServiceWorkerEventStatus::ABORTED, |
| base::Time::Now()); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(FailFetchHelper); |
| }; |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, FailFetchDispatch) { |
| SetUpWithHelper(std::make_unique<FailFetchHelper>()); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request_->set_method("GET"); |
| request_->Start(); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(net::OK, url_request_delegate_.request_status()); |
| // We should have fallen back to network. |
| EXPECT_EQ(200, request_->GetResponseCode()); |
| EXPECT_EQ("PASS", url_request_delegate_.data_received()); |
| EXPECT_FALSE(HasWork()); |
| ServiceWorkerProviderHost* host = helper_->context()->GetProviderHost( |
| helper_->mock_render_process_id(), kProviderID); |
| ASSERT_TRUE(host); |
| EXPECT_EQ(host->controller(), nullptr); |
| |
| EXPECT_EQ(1, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, FailToActivate_MainResource) { |
| SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath())); |
| RunFailToActivateTest(RESOURCE_TYPE_MAIN_FRAME); |
| |
| // The load should fail and we should have fallen back to network because |
| // this is a main resource request. |
| EXPECT_EQ(net::OK, url_request_delegate_.request_status()); |
| EXPECT_EQ(200, request_->GetResponseCode()); |
| EXPECT_EQ("PASS", url_request_delegate_.data_received()); |
| |
| // The controller should be reset since the main resource request failed. |
| ServiceWorkerProviderHost* host = helper_->context()->GetProviderHost( |
| helper_->mock_render_process_id(), kProviderID); |
| ASSERT_TRUE(host); |
| EXPECT_EQ(host->controller(), nullptr); |
| } |
| |
| TEST_F(ServiceWorkerURLRequestJobTest, FailToActivate_Subresource) { |
| SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath())); |
| RunFailToActivateTest(RESOURCE_TYPE_IMAGE); |
| |
| // The load should fail and we should not fall back to network because |
| // this is a subresource request. |
| EXPECT_EQ(net::OK, url_request_delegate_.request_status()); |
| EXPECT_EQ(500, request_->GetResponseCode()); |
| EXPECT_EQ("Service Worker Response Error", |
| request_->response_headers()->GetStatusText()); |
| |
| // The controller should still be set after a subresource request fails. |
| ServiceWorkerProviderHost* host = helper_->context()->GetProviderHost( |
| helper_->mock_render_process_id(), kProviderID); |
| ASSERT_TRUE(host); |
| EXPECT_EQ(host->controller(), version_); |
| } |
| |
| class EarlyResponseHelper : public EmbeddedWorkerTestHelper { |
| public: |
| EarlyResponseHelper() : EmbeddedWorkerTestHelper(base::FilePath()) {} |
| ~EarlyResponseHelper() override {} |
| |
| void FinishWaitUntil() { |
| std::move(finish_callback_) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::Time::Now()); |
| } |
| |
| protected: |
| void OnLegacyFetchEvent( |
| int /* embedded_worker_id */, |
| const ServiceWorkerFetchRequest& /* request */, |
| mojom::FetchEventPreloadHandlePtr /* preload_handle */, |
| mojom::ServiceWorkerFetchResponseCallbackPtr response_callback, |
| mojom::ServiceWorkerEventDispatcher::DispatchFetchEventCallback |
| finish_callback) override { |
| finish_callback_ = std::move(finish_callback); |
| response_callback->OnResponse( |
| ServiceWorkerResponse( |
| std::make_unique<std::vector<GURL>>(), 200, "OK", |
| network::mojom::FetchResponseType::kDefault, |
| std::make_unique<ServiceWorkerHeaderMap>(), std::string(), 0, |
| 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 */), |
| base::Time::Now()); |
| } |
| |
| private: |
| mojom::ServiceWorkerEventDispatcher::DispatchFetchEventCallback |
| finish_callback_; |
| DISALLOW_COPY_AND_ASSIGN(EarlyResponseHelper); |
| }; |
| |
| // This simulates the case when a response is returned and the fetch event is |
| // still in flight. |
| TEST_F(ServiceWorkerURLRequestJobTest, EarlyResponse) { |
| SetUpWithHelper(std::make_unique<EarlyResponseHelper>()); |
| EarlyResponseHelper* helper = |
| static_cast<EarlyResponseHelper*>(helper_.get()); |
| |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| TestRequest(200, "OK", std::string(), true /* expect_valid_ssl */); |
| |
| EXPECT_EQ(0, times_prepare_to_restart_invoked_); |
| ServiceWorkerResponseInfo* info = |
| ServiceWorkerResponseInfo::ForRequest(request_.get()); |
| ASSERT_TRUE(info); |
| EXPECT_TRUE(info->was_fetched_via_service_worker()); |
| EXPECT_FALSE(info->was_fallback_required()); |
| EXPECT_EQ(0u, info->url_list_via_service_worker().size()); |
| EXPECT_EQ(network::mojom::FetchResponseType::kDefault, |
| info->response_type_via_service_worker()); |
| EXPECT_FALSE(info->service_worker_start_time().is_null()); |
| EXPECT_FALSE(info->service_worker_ready_time().is_null()); |
| EXPECT_FALSE(info->response_is_in_cache_storage()); |
| EXPECT_EQ(std::string(), info->response_cache_storage_cache_name()); |
| |
| EXPECT_TRUE(version_->HasWork()); |
| helper->FinishWaitUntil(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(version_->HasWork()); |
| } |
| |
| // Test cancelling the URLRequest while the fetch event is in flight. |
| TEST_F(ServiceWorkerURLRequestJobTest, CancelRequest) { |
| SetUpWithHelper(std::make_unique<DelayHelper>(this)); |
| DelayHelper* helper = static_cast<DelayHelper*>(helper_.get()); |
| |
| // Start the URL request. The job will be waiting for the |
| // worker to respond to the fetch event. |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| request_ = url_request_context_.CreateRequest( |
| GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY, |
| &url_request_delegate_, TRAFFIC_ANNOTATION_FOR_TESTS); |
| request_->set_method("GET"); |
| request_->Start(); |
| base::RunLoop().RunUntilIdle(); |
| helper->CompleteStartWorker(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Cancel the URL request. |
| request_->Cancel(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Respond to the fetch event. |
| EXPECT_TRUE(version_->HasWork()); |
| helper->Respond(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // The fetch event request should no longer be in-flight. |
| EXPECT_FALSE(version_->HasWork()); |
| } |
| |
| // TODO(kinuko): Add more tests with different response data and also for |
| // FallbackToNetwork case. |
| |
| } // namespace content |