| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/service_worker/service_worker_navigation_loader.h" |
| |
| #include "base/run_loop.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "content/browser/loader/navigation_loader_interceptor.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_registration.h" |
| #include "content/browser/service_worker/service_worker_test_utils.h" |
| #include "content/browser/service_worker/service_worker_version.h" |
| #include "content/common/service_worker/service_worker.mojom.h" |
| #include "content/common/service_worker/service_worker_utils.h" |
| #include "content/common/single_request_url_loader_factory.h" |
| #include "content/public/test/mock_render_process_host.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "mojo/public/cpp/system/data_pipe_utils.h" |
| #include "net/ssl/ssl_info.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/resource_response.h" |
| #include "services/network/public/mojom/fetch_api.mojom.h" |
| #include "services/network/test/test_url_loader_client.h" |
| #include "storage/browser/blob/blob_data_builder.h" |
| #include "storage/browser/blob/blob_data_handle.h" |
| #include "storage/browser/blob/blob_impl.h" |
| #include "storage/browser/blob/blob_storage_context.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h" |
| #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h" |
| |
| namespace content { |
| namespace service_worker_navigation_loader_unittest { |
| |
| void ReceiveRequestHandler( |
| SingleRequestURLLoaderFactory::RequestHandler* out_handler, |
| SingleRequestURLLoaderFactory::RequestHandler handler) { |
| *out_handler = std::move(handler); |
| } |
| |
| blink::mojom::FetchAPIResponsePtr OkResponse( |
| blink::mojom::SerializedBlobPtr blob_body) { |
| auto response = blink::mojom::FetchAPIResponse::New(); |
| response->status_code = 200; |
| response->status_text = "OK"; |
| response->response_type = network::mojom::FetchResponseType::kDefault; |
| response->blob = std::move(blob_body); |
| return response; |
| } |
| |
| blink::mojom::FetchAPIResponsePtr ErrorResponse() { |
| auto response = blink::mojom::FetchAPIResponse::New(); |
| response->status_code = 0; |
| response->response_type = network::mojom::FetchResponseType::kDefault; |
| response->error = blink::mojom::ServiceWorkerResponseError::kPromiseRejected; |
| return response; |
| } |
| |
| blink::mojom::FetchAPIResponsePtr RedirectResponse( |
| const std::string& redirect_location_header) { |
| auto response = blink::mojom::FetchAPIResponse::New(); |
| response->status_code = 301; |
| response->status_text = "Moved Permanently"; |
| response->response_type = network::mojom::FetchResponseType::kDefault; |
| response->headers["Location"] = redirect_location_header; |
| return response; |
| } |
| |
| // NavigationPreloadLoaderClient mocks the renderer-side URLLoaderClient for the |
| // navigation preload network request performed by the browser. In production |
| // code, this is ServiceWorkerContextClient::NavigationPreloadRequest, |
| // which it forwards the response to FetchEvent#preloadResponse. Here, it |
| // simulates passing the response to FetchEvent#respondWith. |
| // |
| // The navigation preload test is quite involved. The flow of data is: |
| // 1. ServiceWorkerNavigationLoader asks ServiceWorkerFetchDispatcher to start |
| // navigation preload. |
| // 2. ServiceWorkerFetchDispatcher starts the network request which is mocked |
| // by EmbeddedWorkerTestHelper's default network loader factory. The |
| // response is sent to |
| // ServiceWorkerFetchDispatcher::DelegatingURLLoaderClient. |
| // 3. DelegatingURLLoaderClient sends the response to the |preload_handle| |
| // that was passed to Helper::OnFetchEvent(). |
| // 4. Helper::OnFetchEvent() creates NavigationPreloadLoaderClient, which |
| // receives the response. |
| // 5. NavigationPreloadLoaderClient calls OnFetchEvent()'s callbacks |
| // with the response. |
| // 6. Like all FetchEvent responses, the response is sent to |
| // ServiceWorkerNavigationLoader::DidDispatchFetchEvent, and the |
| // RequestHandler is returned. |
| class NavigationPreloadLoaderClient final |
| : public network::mojom::URLLoaderClient { |
| public: |
| NavigationPreloadLoaderClient( |
| blink::mojom::FetchEventPreloadHandlePtr preload_handle, |
| blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback, |
| mojom::ServiceWorker::DispatchFetchEventCallback finish_callback) |
| : url_loader_(std::move(preload_handle->url_loader)), |
| binding_(this, std::move(preload_handle->url_loader_client_request)), |
| response_callback_(std::move(response_callback)), |
| finish_callback_(std::move(finish_callback)) { |
| binding_.set_connection_error_handler( |
| base::BindOnce(&NavigationPreloadLoaderClient::OnConnectionError, |
| base::Unretained(this))); |
| } |
| ~NavigationPreloadLoaderClient() override = default; |
| |
| // network::mojom::URLLoaderClient implementation |
| void OnReceiveResponse( |
| const network::ResourceResponseHead& response_head) override { |
| response_head_ = response_head; |
| } |
| void OnStartLoadingResponseBody( |
| mojo::ScopedDataPipeConsumerHandle body) override { |
| body_ = std::move(body); |
| // We could call OnResponseStream() here, but for simplicity, don't do |
| // anything until OnComplete(). |
| } |
| void OnComplete(const network::URLLoaderCompletionStatus& status) override { |
| blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback; |
| auto stream_handle = blink::mojom::ServiceWorkerStreamHandle::New(); |
| stream_handle->callback_request = mojo::MakeRequest(&stream_callback); |
| stream_handle->stream = std::move(body_); |
| |
| // Simulate passing the navigation preload response to |
| // FetchEvent#respondWith. |
| auto response = blink::mojom::FetchAPIResponse::New(); |
| response->url_list = |
| std::vector<GURL>(response_head_.url_list_via_service_worker); |
| response->status_code = response_head_.headers->response_code(); |
| response->status_text = response_head_.headers->GetStatusText(); |
| response->response_type = response_head_.response_type; |
| response_callback_->OnResponseStream( |
| std::move(response), std::move(stream_handle), base::TimeTicks::Now()); |
| std::move(finish_callback_) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::TimeTicks::Now()); |
| stream_callback->OnCompleted(); |
| delete this; |
| } |
| void OnReceiveRedirect( |
| const net::RedirectInfo& redirect_info, |
| const network::ResourceResponseHead& response_head) override {} |
| void OnUploadProgress(int64_t current_position, |
| int64_t total_size, |
| OnUploadProgressCallback ack_callback) override {} |
| void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override {} |
| void OnTransferSizeUpdated(int32_t transfer_size_diff) override {} |
| |
| void OnConnectionError() { delete this; } |
| |
| private: |
| network::mojom::URLLoaderPtr url_loader_; |
| mojo::Binding<network::mojom::URLLoaderClient> binding_; |
| |
| network::ResourceResponseHead response_head_; |
| mojo::ScopedDataPipeConsumerHandle body_; |
| |
| // Callbacks that complete Helper::OnFetchEvent(). |
| blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback_; |
| mojom::ServiceWorker::DispatchFetchEventCallback finish_callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NavigationPreloadLoaderClient); |
| }; |
| |
| // Helper simulates a service worker handling fetch events. The response can be |
| // customized via RespondWith* functions. |
| class Helper : public EmbeddedWorkerTestHelper { |
| public: |
| Helper() : EmbeddedWorkerTestHelper(base::FilePath()) {} |
| ~Helper() override = default; |
| |
| // Tells this helper to respond to fetch events with the specified blob. |
| void RespondWithBlob(blink::mojom::SerializedBlobPtr blob) { |
| response_mode_ = ResponseMode::kBlob; |
| blob_body_ = std::move(blob); |
| } |
| |
| // Tells this helper 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 helper 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 helper to respond to fetch events with an error response. |
| void RespondWithError() { response_mode_ = ResponseMode::kErrorResponse; } |
| |
| // Tells this helper to respond to fetch events with |
| // FetchEvent#preloadResponse. See NavigationPreloadLoaderClient's |
| // documentation for details. |
| void RespondWithNavigationPreloadResponse() { |
| response_mode_ = ResponseMode::kNavigationPreloadResponse; |
| } |
| |
| // Tells this helper to respond to fetch events with the redirect response. |
| void RespondWithRedirectResponse(const GURL& new_url) { |
| response_mode_ = ResponseMode::kRedirect; |
| redirected_url_ = new_url; |
| } |
| |
| // Tells this helper to simulate failure to dispatch the fetch event to the |
| // service worker. |
| void FailToDispatchFetchEvent() { |
| response_mode_ = ResponseMode::kFailFetchEventDispatch; |
| } |
| |
| // Tells this helper to simulate "early response", where the respondWith() |
| // promise resolves before the waitUntil() promise. In this mode, the |
| // helper sets the response mode to "early response", which simulates the |
| // promise passed to respondWith() resolving before the waitUntil() promise |
| // resolves. In this mode, the helper will respond to fetch events |
| // immediately, but will not finish the fetch event until FinishWaitUntil() is |
| // called. |
| void RespondEarly() { response_mode_ = ResponseMode::kEarlyResponse; } |
| void FinishWaitUntil() { |
| std::move(finish_callback_) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::TimeTicks::Now()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Tells this helper to wait for FinishRespondWith() to be called before |
| // providing the response to the fetch event. |
| void DeferResponse() { response_mode_ = ResponseMode::kDeferredResponse; } |
| void FinishRespondWith() { |
| response_callback_->OnResponse(OkResponse(nullptr /* blob_body */), |
| base::TimeTicks::Now()); |
| response_callback_.FlushForTesting(); |
| std::move(finish_callback_) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::TimeTicks::Now()); |
| } |
| |
| void ReadRequestBody(std::string* out_string) { |
| ASSERT_TRUE(request_body_); |
| const std::vector<network::DataElement>* elements = |
| request_body_->elements(); |
| // So far this test expects a single bytes element. |
| ASSERT_EQ(1u, elements->size()); |
| const network::DataElement& element = elements->front(); |
| ASSERT_EQ(network::DataElement::TYPE_BYTES, element.type()); |
| *out_string = std::string(element.bytes(), element.length()); |
| } |
| |
| void RunUntilFetchEvent() { |
| if (has_received_fetch_event_) |
| return; |
| base::RunLoop run_loop; |
| quit_closure_for_fetch_event_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| protected: |
| void OnFetchEvent( |
| int embedded_worker_id, |
| const network::ResourceRequest& request, |
| blink::mojom::FetchEventPreloadHandlePtr preload_handle, |
| blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback, |
| mojom::ServiceWorker::DispatchFetchEventCallback finish_callback) |
| override { |
| // Basic checks on DispatchFetchEvent parameters. |
| EXPECT_TRUE(ServiceWorkerUtils::IsMainResourceType( |
| static_cast<ResourceType>(request.resource_type))); |
| |
| has_received_fetch_event_ = true; |
| request_body_ = request.request_body; |
| |
| switch (response_mode_) { |
| case ResponseMode::kDefault: |
| EmbeddedWorkerTestHelper::OnFetchEvent( |
| embedded_worker_id, request, std::move(preload_handle), |
| std::move(response_callback), std::move(finish_callback)); |
| break; |
| case ResponseMode::kBlob: |
| response_callback->OnResponse(OkResponse(std::move(blob_body_)), |
| base::TimeTicks::Now()); |
| std::move(finish_callback) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::TimeTicks::Now()); |
| break; |
| case ResponseMode::kStream: |
| response_callback->OnResponseStream(OkResponse(nullptr /* blob_body */), |
| std::move(stream_handle_), |
| base::TimeTicks::Now()); |
| std::move(finish_callback) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::TimeTicks::Now()); |
| break; |
| case ResponseMode::kFallbackResponse: |
| response_callback->OnFallback(base::TimeTicks::Now()); |
| std::move(finish_callback) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::TimeTicks::Now()); |
| break; |
| case ResponseMode::kErrorResponse: |
| response_callback->OnResponse(ErrorResponse(), base::TimeTicks::Now()); |
| std::move(finish_callback) |
| .Run(blink::mojom::ServiceWorkerEventStatus::REJECTED, |
| base::TimeTicks::Now()); |
| break; |
| case ResponseMode::kNavigationPreloadResponse: |
| // Deletes itself when done. |
| new NavigationPreloadLoaderClient(std::move(preload_handle), |
| std::move(response_callback), |
| std::move(finish_callback)); |
| break; |
| case ResponseMode::kFailFetchEventDispatch: |
| // Simulate failure by stopping the worker before the event finishes. |
| // This causes ServiceWorkerVersion::StartRequest() to call its error |
| // callback, which triggers ServiceWorkerNavigationLoader's dispatch |
| // failed behavior. |
| SimulateWorkerStopped(embedded_worker_id); |
| // Finish the event by calling |finish_callback|. |
| // This is the Mojo callback for |
| // mojom::ServiceWorker::DispatchFetchEvent(). |
| // If this is not called, Mojo will complain. In production code, |
| // ServiceWorkerContextClient would call this when it aborts all |
| // callbacks after an unexpected stop. |
| std::move(finish_callback) |
| .Run(blink::mojom::ServiceWorkerEventStatus::ABORTED, |
| base::TimeTicks::Now()); |
| break; |
| case ResponseMode::kDeferredResponse: |
| finish_callback_ = std::move(finish_callback); |
| response_callback_ = std::move(response_callback); |
| // Now the caller must call FinishRespondWith() to finish the event. |
| break; |
| case ResponseMode::kEarlyResponse: |
| finish_callback_ = std::move(finish_callback); |
| response_callback->OnResponse(OkResponse(nullptr /* blob_body */), |
| base::TimeTicks::Now()); |
| // Now the caller must call FinishWaitUntil() to finish the event. |
| break; |
| case ResponseMode::kRedirect: |
| response_callback->OnResponse(RedirectResponse(redirected_url_.spec()), |
| base::TimeTicks::Now()); |
| std::move(finish_callback) |
| .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED, |
| base::TimeTicks::Now()); |
| break; |
| } |
| |
| if (quit_closure_for_fetch_event_) |
| std::move(quit_closure_for_fetch_event_).Run(); |
| } |
| |
| private: |
| enum class ResponseMode { |
| kDefault, |
| kBlob, |
| kStream, |
| kFallbackResponse, |
| kErrorResponse, |
| kNavigationPreloadResponse, |
| kFailFetchEventDispatch, |
| kDeferredResponse, |
| kEarlyResponse, |
| kRedirect |
| }; |
| |
| ResponseMode response_mode_ = ResponseMode::kDefault; |
| scoped_refptr<network::ResourceRequestBody> request_body_; |
| |
| // For ResponseMode::kBlob. |
| blink::mojom::SerializedBlobPtr blob_body_; |
| |
| // For ResponseMode::kStream. |
| blink::mojom::ServiceWorkerStreamHandlePtr stream_handle_; |
| |
| // For ResponseMode::kEarlyResponse and kDeferredResponse. |
| mojom::ServiceWorker::DispatchFetchEventCallback finish_callback_; |
| blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback_; |
| |
| // For ResponseMode::kRedirect. |
| GURL redirected_url_; |
| |
| bool has_received_fetch_event_ = false; |
| base::OnceClosure quit_closure_for_fetch_event_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Helper); |
| }; |
| |
| // Returns typical response info for a resource load that went through a service |
| // worker. |
| std::unique_ptr<network::ResourceResponseHead> |
| CreateResponseInfoFromServiceWorker() { |
| auto head = std::make_unique<network::ResourceResponseHead>(); |
| head->was_fetched_via_service_worker = true; |
| head->was_fallback_required_by_service_worker = false; |
| head->url_list_via_service_worker = std::vector<GURL>(); |
| head->response_type = network::mojom::FetchResponseType::kDefault; |
| head->is_in_cache_storage = false; |
| head->cache_storage_cache_name = std::string(); |
| head->did_service_worker_navigation_preload = false; |
| return head; |
| } |
| |
| const char kHistogramMainResourceFetchEvent[] = |
| "ServiceWorker.FetchEvent.MainResource.Status"; |
| |
| // ServiceWorkerNavigationLoaderTest is for testing the handling of requests |
| // by a service worker via ServiceWorkerNavigationLoader. |
| // |
| // 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. |
| // |
| // ServiceWorkerNavigationLoaderTest is also a |
| // ServiceWorkerNavigationLoader::Delegate. In production code, |
| // ServiceWorkerControlleeRequestHandler is the Delegate. So this class also |
| // basically mocks that part of ServiceWorkerControlleeRequestHandler. |
| class ServiceWorkerNavigationLoaderTest |
| : public testing::Test, |
| public ServiceWorkerNavigationLoader::Delegate { |
| public: |
| ServiceWorkerNavigationLoaderTest() |
| : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {} |
| ~ServiceWorkerNavigationLoaderTest() override = default; |
| |
| void SetUp() override { |
| feature_list_.InitAndEnableFeature(network::features::kNetworkService); |
| helper_ = std::make_unique<Helper>(); |
| |
| // Create an active service worker. |
| storage()->LazyInitializeForTest(base::DoNothing()); |
| base::RunLoop().RunUntilIdle(); |
| blink::mojom::ServiceWorkerRegistrationOptions options; |
| options.scope = GURL("https://example.com/"); |
| registration_ = |
| new ServiceWorkerRegistration(options, storage()->NewRegistrationId(), |
| helper_->context()->AsWeakPtr()); |
| version_ = new ServiceWorkerVersion( |
| registration_.get(), GURL("https://example.com/service_worker.js"), |
| blink::mojom::ScriptType::kClassic, storage()->NewVersionId(), |
| helper_->context()->AsWeakPtr()); |
| std::vector<ServiceWorkerDatabase::ResourceRecord> records; |
| records.push_back(WriteToDiskCacheSync( |
| storage(), version_->script_url(), storage()->NewResourceId(), |
| {} /* headers */, "I'm the body", "I'm the meta data")); |
| version_->script_cache_map()->SetResources(records); |
| version_->set_fetch_handler_existence( |
| ServiceWorkerVersion::FetchHandlerExistence::EXISTS); |
| version_->SetStatus(ServiceWorkerVersion::ACTIVATED); |
| registration_->SetActiveVersion(version_); |
| |
| // Make the registration findable via storage functions. |
| registration_->set_last_update_check(base::Time::Now()); |
| base::Optional<blink::ServiceWorkerStatusCode> status; |
| storage()->StoreRegistration(registration_.get(), version_.get(), |
| CreateReceiverOnCurrentThread(&status)); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value()); |
| } |
| |
| ServiceWorkerStorage* storage() { return helper_->context()->storage(); } |
| |
| // Indicates whether ServiceWorkerNavigationLoader decided to handle a |
| // request, i.e., it returned a non-null RequestHandler for the request. |
| enum class LoaderResult { |
| kHandledRequest, |
| kDidNotHandleRequest, |
| }; |
| |
| // Starts a request. Returns whether ServiceWorkerNavigationLoader handled the |
| // request. If kHandledRequest was returned, the request is ongoing and the |
| // caller can use functions like client_.RunUntilComplete() to wait for |
| // completion. |
| LoaderResult StartRequest(std::unique_ptr<network::ResourceRequest> request) { |
| // Start a ServiceWorkerNavigationLoader. It should return a |
| // RequestHandler. |
| SingleRequestURLLoaderFactory::RequestHandler handler; |
| loader_ = std::make_unique<ServiceWorkerNavigationLoader>( |
| base::BindOnce(&ReceiveRequestHandler, &handler), |
| base::BindOnce(&ServiceWorkerNavigationLoaderTest::Fallback, |
| base::Unretained(this)), |
| this, *request, |
| base::WrapRefCounted<URLLoaderFactoryGetter>( |
| helper_->context()->loader_factory_getter())); |
| loader_->ForwardToServiceWorker(); |
| base::RunLoop().RunUntilIdle(); |
| if (!handler) |
| return LoaderResult::kDidNotHandleRequest; |
| |
| // Run the handler. It will load |request.url|. |
| std::move(handler).Run(*request, mojo::MakeRequest(&loader_ptr_), |
| client_.CreateInterfacePtr()); |
| |
| return LoaderResult::kHandledRequest; |
| } |
| |
| // The |fallback_callback| passed to the ServiceWorkerNavigationLoader in |
| // StartRequest(). |
| void Fallback(bool reset_subresource_loader_params) { |
| did_call_fallback_callback_ = true; |
| reset_subresource_loader_params_ = reset_subresource_loader_params; |
| if (quit_closure_for_fallback_callback_) |
| std::move(quit_closure_for_fallback_callback_).Run(); |
| } |
| |
| // Runs until the ServiceWorkerNavigationLoader created in StartRequest() |
| // calls the |fallback_callback| given to it. The argument passed to |
| // |fallback_callback| is saved in |reset_subresurce_loader_params_|. |
| void RunUntilFallbackCallback() { |
| if (did_call_fallback_callback_) |
| return; |
| base::RunLoop run_loop; |
| quit_closure_for_fallback_callback_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| void ExpectResponseInfo(const network::ResourceResponseHead& info, |
| const network::ResourceResponseHead& expected_info) { |
| EXPECT_EQ(expected_info.was_fetched_via_service_worker, |
| info.was_fetched_via_service_worker); |
| EXPECT_EQ(expected_info.was_fallback_required_by_service_worker, |
| info.was_fallback_required_by_service_worker); |
| EXPECT_EQ(expected_info.url_list_via_service_worker, |
| info.url_list_via_service_worker); |
| EXPECT_EQ(expected_info.response_type, info.response_type); |
| EXPECT_FALSE(info.service_worker_start_time.is_null()); |
| EXPECT_FALSE(info.service_worker_ready_time.is_null()); |
| EXPECT_LT(info.service_worker_start_time, info.service_worker_ready_time); |
| 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); |
| } |
| |
| std::unique_ptr<network::ResourceRequest> CreateRequest() { |
| std::unique_ptr<network::ResourceRequest> request = |
| std::make_unique<network::ResourceRequest>(); |
| request->url = GURL("https://www.example.com/"); |
| request->method = "GET"; |
| request->fetch_request_mode = network::mojom::FetchRequestMode::kNavigate; |
| request->fetch_credentials_mode = |
| network::mojom::FetchCredentialsMode::kInclude; |
| request->fetch_redirect_mode = network::mojom::FetchRedirectMode::kManual; |
| return request; |
| } |
| |
| protected: |
| // ServiceWorkerNavigationLoader::Delegate ----------------------------------- |
| void OnPrepareToRestart() override {} |
| |
| ServiceWorkerVersion* GetServiceWorkerVersion( |
| ServiceWorkerMetrics::URLRequestJobResult* result) override { |
| return version_.get(); |
| } |
| |
| bool RequestStillValid( |
| ServiceWorkerMetrics::URLRequestJobResult* result) override { |
| return true; |
| } |
| |
| void MainResourceLoadFailed() override { |
| was_main_resource_load_failed_called_ = true; |
| } |
| // -------------------------------------------------------------------------- |
| |
| TestBrowserThreadBundle thread_bundle_; |
| std::unique_ptr<Helper> helper_; |
| scoped_refptr<ServiceWorkerRegistration> registration_; |
| scoped_refptr<ServiceWorkerVersion> version_; |
| storage::BlobStorageContext blob_context_; |
| network::TestURLLoaderClient client_; |
| bool was_main_resource_load_failed_called_ = false; |
| std::unique_ptr<ServiceWorkerNavigationLoader> loader_; |
| network::mojom::URLLoaderPtr loader_ptr_; |
| |
| bool did_call_fallback_callback_ = false; |
| bool reset_subresource_loader_params_ = false; |
| base::OnceClosure quit_closure_for_fallback_callback_; |
| |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| TEST_F(ServiceWorkerNavigationLoaderTest, Basic) { |
| base::HistogramTester histogram_tester; |
| |
| // Perform the request |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| client_.RunUntilComplete(); |
| |
| EXPECT_EQ(net::OK, client_.completion_status().error_code); |
| const network::ResourceResponseHead& info = client_.response_head(); |
| EXPECT_EQ(200, info.headers->response_code()); |
| ExpectResponseInfo(info, *CreateResponseInfoFromServiceWorker()); |
| |
| histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent, |
| blink::ServiceWorkerStatusCode::kOk, 1); |
| } |
| |
| TEST_F(ServiceWorkerNavigationLoaderTest, NoActiveWorker) { |
| base::HistogramTester histogram_tester; |
| |
| // Clear |version_| to make GetServiceWorkerVersion() return null. |
| version_ = nullptr; |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| |
| client_.RunUntilComplete(); |
| EXPECT_EQ(net::ERR_FAILED, client_.completion_status().error_code); |
| |
| // No fetch event was dispatched. |
| histogram_tester.ExpectTotalCount(kHistogramMainResourceFetchEvent, 0); |
| } |
| |
| // Test that the request body is passed to the fetch event. |
| TEST_F(ServiceWorkerNavigationLoaderTest, RequestBody) { |
| const std::string kData = "hi this is the request body"; |
| |
| // Create a request with a body. |
| auto request_body = base::MakeRefCounted<network::ResourceRequestBody>(); |
| request_body->AppendBytes(kData.c_str(), kData.length()); |
| std::unique_ptr<network::ResourceRequest> request = CreateRequest(); |
| request->method = "POST"; |
| request->request_body = request_body; |
| |
| // This test doesn't use the response to the fetch event, so just have the |
| // service worker do the default simple response. |
| StartRequest(std::move(request)); |
| client_.RunUntilComplete(); |
| |
| // Verify that the request body was passed to the fetch event. |
| std::string body; |
| helper_->ReadRequestBody(&body); |
| EXPECT_EQ(kData, body); |
| } |
| |
| TEST_F(ServiceWorkerNavigationLoaderTest, BlobResponse) { |
| // Construct the blob to respond with. |
| const std::string kResponseBody = "Here is sample text for the blob."; |
| auto blob_data = std::make_unique<storage::BlobDataBuilder>("blob-id:myblob"); |
| blob_data->AppendData(kResponseBody); |
| std::unique_ptr<storage::BlobDataHandle> blob_handle = |
| blob_context_.AddFinishedBlob(std::move(blob_data)); |
| |
| auto blob = blink::mojom::SerializedBlob::New(); |
| blob->uuid = blob_handle->uuid(); |
| blob->size = blob_handle->size(); |
| blink::mojom::BlobRequest request = mojo::MakeRequest(&blob->blob); |
| storage::BlobImpl::Create(std::move(blob_handle), std::move(request)); |
| helper_->RespondWithBlob(std::move(blob)); |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| client_.RunUntilComplete(); |
| |
| const network::ResourceResponseHead& info = client_.response_head(); |
| EXPECT_EQ(200, info.headers->response_code()); |
| ExpectResponseInfo(info, *CreateResponseInfoFromServiceWorker()); |
| |
| // Test the body. |
| std::string body; |
| EXPECT_TRUE(client_.response_body().is_valid()); |
| EXPECT_TRUE( |
| mojo::BlockingCopyToString(client_.response_body_release(), &body)); |
| EXPECT_EQ(kResponseBody, body); |
| EXPECT_EQ(net::OK, client_.completion_status().error_code); |
| } |
| |
| // Tell the helper to respond with a non-existent Blob. |
| TEST_F(ServiceWorkerNavigationLoaderTest, BrokenBlobResponse) { |
| const std::string kBrokenUUID = "broken_uuid"; |
| |
| // Create the broken blob. |
| std::unique_ptr<storage::BlobDataHandle> blob_handle = |
| blob_context_.AddBrokenBlob(kBrokenUUID, "", "", |
| storage::BlobStatus::ERR_OUT_OF_MEMORY); |
| auto blob = blink::mojom::SerializedBlob::New(); |
| blob->uuid = kBrokenUUID; |
| blink::mojom::BlobRequest request = mojo::MakeRequest(&blob->blob); |
| storage::BlobImpl::Create(std::move(blob_handle), std::move(request)); |
| helper_->RespondWithBlob(std::move(blob)); |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| |
| // We should get a valid response once the headers arrive. |
| client_.RunUntilResponseReceived(); |
| const network::ResourceResponseHead& info = client_.response_head(); |
| EXPECT_EQ(200, info.headers->response_code()); |
| ExpectResponseInfo(info, *CreateResponseInfoFromServiceWorker()); |
| |
| // However, since the blob is broken we should get an error while transferring |
| // the body. |
| client_.RunUntilComplete(); |
| EXPECT_EQ(net::ERR_OUT_OF_MEMORY, client_.completion_status().error_code); |
| } |
| |
| TEST_F(ServiceWorkerNavigationLoaderTest, StreamResponse) { |
| // 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; |
| helper_->RespondWithStream(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle)); |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| client_.RunUntilResponseReceived(); |
| |
| const network::ResourceResponseHead& info = client_.response_head(); |
| EXPECT_EQ(200, info.headers->response_code()); |
| ExpectResponseInfo(info, *CreateResponseInfoFromServiceWorker()); |
| |
| EXPECT_TRUE(version_->HasWorkInBrowser()); |
| |
| // Write the body stream. |
| uint32_t written_bytes = sizeof(kResponseBody) - 1; |
| MojoResult mojo_result = data_pipe.producer_handle->WriteData( |
| kResponseBody, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| ASSERT_EQ(MOJO_RESULT_OK, mojo_result); |
| EXPECT_EQ(sizeof(kResponseBody) - 1, written_bytes); |
| stream_callback->OnCompleted(); |
| data_pipe.producer_handle.reset(); |
| |
| client_.RunUntilComplete(); |
| EXPECT_EQ(net::OK, client_.completion_status().error_code); |
| |
| // Test the body. |
| std::string response; |
| EXPECT_TRUE(client_.response_body().is_valid()); |
| EXPECT_TRUE( |
| mojo::BlockingCopyToString(client_.response_body_release(), &response)); |
| EXPECT_EQ(kResponseBody, response); |
| } |
| |
| // Test when a stream response body is aborted. |
| TEST_F(ServiceWorkerNavigationLoaderTest, StreamResponse_Abort) { |
| // 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; |
| helper_->RespondWithStream(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle)); |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| client_.RunUntilResponseReceived(); |
| |
| const network::ResourceResponseHead& info = client_.response_head(); |
| EXPECT_EQ(200, info.headers->response_code()); |
| ExpectResponseInfo(info, *CreateResponseInfoFromServiceWorker()); |
| |
| // Start writing the body stream, then abort before finishing. |
| uint32_t written_bytes = sizeof(kResponseBody) - 1; |
| MojoResult mojo_result = data_pipe.producer_handle->WriteData( |
| kResponseBody, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| ASSERT_EQ(MOJO_RESULT_OK, mojo_result); |
| EXPECT_EQ(sizeof(kResponseBody) - 1, written_bytes); |
| stream_callback->OnAborted(); |
| data_pipe.producer_handle.reset(); |
| |
| client_.RunUntilComplete(); |
| EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code); |
| |
| // Test the body. |
| std::string response; |
| EXPECT_TRUE(client_.response_body().is_valid()); |
| EXPECT_TRUE( |
| mojo::BlockingCopyToString(client_.response_body_release(), &response)); |
| EXPECT_EQ(kResponseBody, response); |
| } |
| |
| // Test when the loader is cancelled while a stream response is being written. |
| TEST_F(ServiceWorkerNavigationLoaderTest, StreamResponseAndCancel) { |
| // 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; |
| helper_->RespondWithStream(mojo::MakeRequest(&stream_callback), |
| std::move(data_pipe.consumer_handle)); |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| client_.RunUntilResponseReceived(); |
| |
| const network::ResourceResponseHead& info = client_.response_head(); |
| EXPECT_EQ(200, info.headers->response_code()); |
| ExpectResponseInfo(info, *CreateResponseInfoFromServiceWorker()); |
| |
| // Start writing the body stream, then break the Mojo connection to the loader |
| // before finishing. |
| uint32_t written_bytes = sizeof(kResponseBody) - 1; |
| MojoResult mojo_result = data_pipe.producer_handle->WriteData( |
| kResponseBody, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| ASSERT_EQ(MOJO_RESULT_OK, mojo_result); |
| EXPECT_EQ(sizeof(kResponseBody) - 1, written_bytes); |
| EXPECT_TRUE(data_pipe.producer_handle.is_valid()); |
| EXPECT_TRUE(version_->HasWorkInBrowser()); |
| loader_ptr_.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(version_->HasWorkInBrowser()); |
| |
| // Although ServiceWorkerNavigationLoader resets its URLLoaderClient pointer |
| // on connection error, the URLLoaderClient still exists. In this test, it is |
| // |client_| which owns the data pipe, so it's still valid to write data to |
| // it. |
| mojo_result = data_pipe.producer_handle->WriteData( |
| kResponseBody, &written_bytes, MOJO_WRITE_DATA_FLAG_NONE); |
| // TODO(falken): This should probably be an error. |
| EXPECT_EQ(MOJO_RESULT_OK, mojo_result); |
| |
| client_.RunUntilComplete(); |
| EXPECT_FALSE(data_pipe.consumer_handle.is_valid()); |
| EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code); |
| } |
| |
| // Test when the service worker responds with network fallback. |
| // i.e., does not call respondWith(). |
| TEST_F(ServiceWorkerNavigationLoaderTest, FallbackResponse) { |
| base::HistogramTester histogram_tester; |
| helper_->RespondWithFallback(); |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| |
| // The fallback callback should be called. |
| RunUntilFallbackCallback(); |
| EXPECT_FALSE(reset_subresource_loader_params_); |
| EXPECT_FALSE(was_main_resource_load_failed_called_); |
| |
| // The request should not be handled by the loader, but it shouldn't be a |
| // failure. |
| EXPECT_FALSE(was_main_resource_load_failed_called_); |
| histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent, |
| blink::ServiceWorkerStatusCode::kOk, 1); |
| } |
| |
| // Test when the service worker rejects the FetchEvent. |
| TEST_F(ServiceWorkerNavigationLoaderTest, ErrorResponse) { |
| base::HistogramTester histogram_tester; |
| helper_->RespondWithError(); |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| |
| client_.RunUntilComplete(); |
| EXPECT_EQ(net::ERR_FAILED, client_.completion_status().error_code); |
| |
| // Event dispatch still succeeded. |
| histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent, |
| blink::ServiceWorkerStatusCode::kOk, 1); |
| } |
| |
| // Test when dispatching the fetch event to the service worker failed. |
| TEST_F(ServiceWorkerNavigationLoaderTest, FailFetchDispatch) { |
| base::HistogramTester histogram_tester; |
| helper_->FailToDispatchFetchEvent(); |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| |
| // The fallback callback should be called. |
| RunUntilFallbackCallback(); |
| EXPECT_TRUE(reset_subresource_loader_params_); |
| EXPECT_TRUE(was_main_resource_load_failed_called_); |
| |
| histogram_tester.ExpectUniqueSample( |
| kHistogramMainResourceFetchEvent, |
| blink::ServiceWorkerStatusCode::kErrorFailed, 1); |
| } |
| |
| // Test when the respondWith() promise resolves before the waitUntil() promise |
| // resolves. The response should be received before the event finishes. |
| TEST_F(ServiceWorkerNavigationLoaderTest, EarlyResponse) { |
| helper_->RespondEarly(); |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| client_.RunUntilComplete(); |
| |
| const network::ResourceResponseHead& info = client_.response_head(); |
| EXPECT_EQ(200, info.headers->response_code()); |
| ExpectResponseInfo(info, *CreateResponseInfoFromServiceWorker()); |
| |
| // Although the response was already received, the event remains outstanding |
| // until waitUntil() resolves. |
| EXPECT_TRUE(version_->HasWorkInBrowser()); |
| helper_->FinishWaitUntil(); |
| EXPECT_FALSE(version_->HasWorkInBrowser()); |
| } |
| |
| // Test asking the loader to fallback to network. In production code, this |
| // happens when there is no active service worker for the URL, or it must be |
| // skipped, etc. |
| TEST_F(ServiceWorkerNavigationLoaderTest, FallbackToNetwork) { |
| base::HistogramTester histogram_tester; |
| |
| network::ResourceRequest request; |
| request.url = GURL("https://www.example.com/"); |
| request.method = "GET"; |
| request.fetch_request_mode = network::mojom::FetchRequestMode::kNavigate; |
| request.fetch_credentials_mode = |
| network::mojom::FetchCredentialsMode::kInclude; |
| request.fetch_redirect_mode = network::mojom::FetchRedirectMode::kManual; |
| |
| SingleRequestURLLoaderFactory::RequestHandler handler; |
| auto loader = std::make_unique<ServiceWorkerNavigationLoader>( |
| base::BindOnce(&ReceiveRequestHandler, &handler), |
| base::BindOnce(&ServiceWorkerNavigationLoaderTest::Fallback, |
| base::Unretained(this)), |
| this, request, |
| base::WrapRefCounted<URLLoaderFactoryGetter>( |
| helper_->context()->loader_factory_getter())); |
| // Ask the loader to fallback to network. In production code, |
| // ServiceWorkerControlleeRequestHandler calls FallbackToNetwork() to do this. |
| loader->FallbackToNetwork(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(handler); |
| |
| // No fetch event was dispatched. |
| histogram_tester.ExpectTotalCount(kHistogramMainResourceFetchEvent, 0); |
| } |
| |
| // Test responding to the fetch event with the navigation preload response. |
| TEST_F(ServiceWorkerNavigationLoaderTest, NavigationPreload) { |
| registration_->EnableNavigationPreload(true); |
| helper_->RespondWithNavigationPreloadResponse(); |
| |
| // Perform the request |
| LoaderResult result = StartRequest(CreateRequest()); |
| ASSERT_EQ(LoaderResult::kHandledRequest, result); |
| client_.RunUntilComplete(); |
| |
| EXPECT_EQ(net::OK, client_.completion_status().error_code); |
| const network::ResourceResponseHead& info = client_.response_head(); |
| EXPECT_EQ(200, info.headers->response_code()); |
| |
| std::unique_ptr<network::ResourceResponseHead> expected_info = |
| CreateResponseInfoFromServiceWorker(); |
| expected_info->did_service_worker_navigation_preload = true; |
| ExpectResponseInfo(info, *expected_info); |
| |
| std::string response; |
| EXPECT_TRUE(client_.response_body().is_valid()); |
| EXPECT_TRUE( |
| mojo::BlockingCopyToString(client_.response_body_release(), &response)); |
| EXPECT_EQ("this body came from the network", response); |
| } |
| |
| // Test responding to the fetch event with a redirect response. |
| TEST_F(ServiceWorkerNavigationLoaderTest, Redirect) { |
| base::HistogramTester histogram_tester; |
| GURL new_url("https://example.com/redirected"); |
| helper_->RespondWithRedirectResponse(new_url); |
| |
| // Perform the request. |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| client_.RunUntilRedirectReceived(); |
| |
| const network::ResourceResponseHead& info = client_.response_head(); |
| EXPECT_EQ(301, info.headers->response_code()); |
| ExpectResponseInfo(info, *CreateResponseInfoFromServiceWorker()); |
| |
| const net::RedirectInfo& redirect_info = client_.redirect_info(); |
| EXPECT_EQ(301, redirect_info.status_code); |
| EXPECT_EQ("GET", redirect_info.new_method); |
| EXPECT_EQ(new_url, redirect_info.new_url); |
| |
| histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent, |
| blink::ServiceWorkerStatusCode::kOk, 1); |
| } |
| |
| TEST_F(ServiceWorkerNavigationLoaderTest, LifetimeAfterForwardToServiceWorker) { |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| base::WeakPtr<ServiceWorkerNavigationLoader> loader = loader_->AsWeakPtr(); |
| ASSERT_TRUE(loader); |
| |
| client_.RunUntilComplete(); |
| EXPECT_TRUE(loader); |
| |
| // Even after calling DetachedFromRequest(), |loader_| should be alive until |
| // the Mojo connection to the loader is disconnected. |
| loader_.release()->DetachedFromRequest(); |
| EXPECT_TRUE(loader); |
| |
| // When the interface pointer to |loader_| is disconnected, its weak pointers |
| // (|loader|) are invalidated. |
| loader_ptr_.reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(loader); |
| |
| // |loader_| is deleted here. LSan test will alert if it leaks. |
| } |
| |
| TEST_F(ServiceWorkerNavigationLoaderTest, LifetimeAfterFallbackToNetwork) { |
| network::ResourceRequest request; |
| request.url = GURL("https://www.example.com/"); |
| request.method = "GET"; |
| request.fetch_request_mode = network::mojom::FetchRequestMode::kNavigate; |
| request.fetch_credentials_mode = |
| network::mojom::FetchCredentialsMode::kInclude; |
| request.fetch_redirect_mode = network::mojom::FetchRedirectMode::kManual; |
| |
| SingleRequestURLLoaderFactory::RequestHandler handler; |
| auto loader = std::make_unique<ServiceWorkerNavigationLoader>( |
| base::BindOnce(&ReceiveRequestHandler, &handler), |
| base::BindOnce(&ServiceWorkerNavigationLoaderTest::Fallback, |
| base::Unretained(this)), |
| this, request, |
| base::WrapRefCounted<URLLoaderFactoryGetter>( |
| helper_->context()->loader_factory_getter())); |
| base::WeakPtr<ServiceWorkerNavigationLoader> loader_weakptr = |
| loader->AsWeakPtr(); |
| // Ask the loader to fallback to network. In production code, |
| // ServiceWorkerControlleeRequestHandler calls FallbackToNetwork() to do this. |
| loader->FallbackToNetwork(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(handler); |
| EXPECT_TRUE(loader_weakptr); |
| |
| // DetachedFromRequest() deletes |loader_|. |
| loader.release()->DetachedFromRequest(); |
| EXPECT_FALSE(loader_weakptr); |
| } |
| |
| TEST_F(ServiceWorkerNavigationLoaderTest, ConnectionErrorDuringFetchEvent) { |
| helper_->DeferResponse(); |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| |
| // Wait for the fetch event to be dispatched. |
| helper_->RunUntilFetchEvent(); |
| |
| // Break the Mojo connection. The loader should return an aborted status. |
| loader_ptr_.reset(); |
| client_.RunUntilComplete(); |
| EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code); |
| |
| // The loader is still alive. Finish the fetch event. It shouldn't crash or |
| // call any callbacks on |client_|, which would throw an error. |
| helper_->FinishRespondWith(); |
| // There's no event to wait for, so just pump the message loop and the test |
| // passes if there is no error or crash. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(ServiceWorkerNavigationLoaderTest, DetachedDuringFetchEvent) { |
| LoaderResult result = StartRequest(CreateRequest()); |
| EXPECT_EQ(LoaderResult::kHandledRequest, result); |
| |
| // Detach the loader immediately after it started. This results in |
| // DidDispatchFetchEvent() being invoked later with null |delegate_|. |
| loader_.release()->DetachedFromRequest(); |
| client_.RunUntilComplete(); |
| EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code); |
| } |
| |
| } // namespace service_worker_navigation_loader_unittest |
| } // namespace content |