| // Copyright 2018 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_single_script_update_checker.h" |
| |
| #include <vector> |
| #include "base/bind.h" |
| #include "base/containers/queue.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "content/browser/service_worker/embedded_worker_test_helper.h" |
| #include "content/browser/service_worker/service_worker_context_wrapper.h" |
| #include "content/browser/service_worker/service_worker_storage.h" |
| #include "content/browser/service_worker/service_worker_test_utils.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "mojo/public/cpp/system/data_pipe_utils.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_util.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "services/network/test/test_utils.h" |
| #include "third_party/blink/public/common/features.h" |
| |
| namespace content { |
| namespace { |
| |
| constexpr char kScriptURL[] = "https://example.com/script.js"; |
| constexpr char kImportedScriptURL[] = "https://example.com/imported-script.js"; |
| constexpr char kScope[] = "https://example.com/"; |
| constexpr char kSuccessHeader[] = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: text/javascript\n\n"; |
| |
| class ServiceWorkerSingleScriptUpdateCheckerTest : public testing::Test { |
| public: |
| struct CheckResult { |
| CheckResult( |
| const GURL& script_url, |
| ServiceWorkerSingleScriptUpdateChecker::Result compare_result, |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::FailureInfo> |
| failure_info, |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::PausedState> |
| paused_state) |
| : url(script_url), |
| result(compare_result), |
| failure_info(std::move(failure_info)), |
| paused_state(std::move(paused_state)) {} |
| |
| CheckResult(CheckResult&& ref) = default; |
| |
| CheckResult& operator=(CheckResult&& ref) = default; |
| |
| ~CheckResult() = default; |
| |
| GURL url; |
| ServiceWorkerSingleScriptUpdateChecker::Result result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::FailureInfo> |
| failure_info; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker::PausedState> |
| paused_state; |
| }; |
| |
| ServiceWorkerSingleScriptUpdateCheckerTest() |
| : task_environment_(BrowserTaskEnvironment::IO_MAINLOOP), |
| browser_context_(std::make_unique<TestBrowserContext>()) { |
| BrowserContext::EnsureResourceContextInitialized(browser_context_.get()); |
| base::RunLoop().RunUntilIdle(); |
| } |
| ~ServiceWorkerSingleScriptUpdateCheckerTest() override = default; |
| |
| ServiceWorkerStorage* storage() { return helper_->context()->storage(); } |
| |
| void SetUp() override { |
| feature_list_.InitAndEnableFeature( |
| blink::features::kServiceWorkerImportedScriptUpdateCheck); |
| helper_ = std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()); |
| storage()->LazyInitializeForTest(); |
| } |
| |
| size_t TotalBytes(const std::vector<std::string>& data_chunks) { |
| size_t bytes = 0; |
| for (const auto& data : data_chunks) |
| bytes += data.size(); |
| return bytes; |
| } |
| |
| // Create an update checker which will always ask HTTP cache validation. |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| const char* url, |
| const GURL& scope, |
| std::unique_ptr<ServiceWorkerResponseReader> compare_reader, |
| std::unique_ptr<ServiceWorkerResponseReader> copy_reader, |
| std::unique_ptr<ServiceWorkerResponseWriter> writer, |
| network::TestURLLoaderFactory* loader_factory, |
| base::Optional<CheckResult>* out_check_result) { |
| return CreateSingleScriptUpdateChecker( |
| url, url, scope, false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kNone, |
| base::TimeDelta() /* time_since_last_check */, |
| std::move(compare_reader), std::move(copy_reader), std::move(writer), |
| loader_factory, out_check_result); |
| } |
| |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> |
| CreateSingleScriptUpdateChecker( |
| const char* url, |
| const char* main_script_url, |
| const GURL& scope, |
| bool force_bypass_cache, |
| blink::mojom::ServiceWorkerUpdateViaCache update_via_cache, |
| base::TimeDelta time_since_last_check, |
| std::unique_ptr<ServiceWorkerResponseReader> compare_reader, |
| std::unique_ptr<ServiceWorkerResponseReader> copy_reader, |
| std::unique_ptr<ServiceWorkerResponseWriter> writer, |
| network::TestURLLoaderFactory* loader_factory, |
| base::Optional<CheckResult>* out_check_result) { |
| helper_->SetNetworkFactory(loader_factory); |
| GetLoaderFactoryForUpdateCheck(scope); |
| return std::make_unique<ServiceWorkerSingleScriptUpdateChecker>( |
| GURL(url), url == main_script_url, GURL(main_script_url), scope, |
| force_bypass_cache, update_via_cache, time_since_last_check, |
| net::HttpRequestHeaders(), |
| base::BindRepeating([](BrowserContext* context) { return context; }, |
| browser_context_.get()), |
| loader_factory_for_update_check_, std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), |
| base::BindOnce( |
| [](base::Optional<CheckResult>* out_check_result_param, |
| const GURL& script_url, |
| ServiceWorkerSingleScriptUpdateChecker::Result result, |
| std::unique_ptr< |
| ServiceWorkerSingleScriptUpdateChecker::FailureInfo> |
| failure_info, |
| std::unique_ptr< |
| ServiceWorkerSingleScriptUpdateChecker::PausedState> |
| paused_state) { |
| *out_check_result_param = |
| CheckResult(script_url, result, std::move(failure_info), |
| std::move(paused_state)); |
| }, |
| out_check_result)); |
| } |
| |
| std::unique_ptr<network::TestURLLoaderFactory> CreateLoaderFactoryWithRespone( |
| const GURL& url, |
| const std::string& header, |
| const std::string& body, |
| net::Error error) { |
| auto loader_factory = std::make_unique<network::TestURLLoaderFactory>(); |
| network::ResourceResponseHead head; |
| head.headers = base::MakeRefCounted<net::HttpResponseHeaders>( |
| net::HttpUtil::AssembleRawHeaders(header)); |
| head.headers->GetMimeType(&head.mime_type); |
| network::URLLoaderCompletionStatus status(error); |
| status.decoded_body_length = body.size(); |
| loader_factory->AddResponse(url, head, body, status); |
| return loader_factory; |
| } |
| |
| protected: |
| void GetLoaderFactoryForUpdateCheck(const GURL& scope) { |
| base::RunLoop loop; |
| helper_->context_wrapper()->GetLoaderFactoryForUpdateCheck( |
| scope, |
| base::BindLambdaForTesting( |
| [&](scoped_refptr<network::SharedURLLoaderFactory> loader_factory) { |
| DCHECK(loader_factory); |
| loader_factory_for_update_check_ = std::move(loader_factory); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| BrowserTaskEnvironment task_environment_; |
| std::unique_ptr<EmbeddedWorkerTestHelper> helper_; |
| base::test::ScopedFeatureList feature_list_; |
| std::unique_ptr<TestBrowserContext> browser_context_; |
| scoped_refptr<network::SharedURLLoaderFactory> |
| loader_factory_for_update_check_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ServiceWorkerSingleScriptUpdateCheckerTest); |
| }; |
| |
| class ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest |
| : public ServiceWorkerSingleScriptUpdateCheckerTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| static bool IsAsync() { return GetParam(); } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTestP, |
| ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, |
| testing::Bool()); |
| |
| TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, |
| Identical_SingleRead) { |
| // Response body from the network. |
| const std::string body_from_net("abcdef"); |
| |
| // Stored data for |kScriptURL|. |
| const std::vector<std::string> body_from_storage{body_from_net}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| IsAsync()); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| if (IsAsync()) { |
| // Blocked on reading the header. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the header, and then blocked on reading the body. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body. |
| compare_reader_rawptr->CompletePendingRead(); |
| } |
| |
| // Complete the comparison of the body. It should be identical. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, |
| Identical_MultipleRead) { |
| // Response body from the network. |
| const std::string body_from_net("abcdef"); |
| |
| // Stored data for |kScriptURL|. |
| const std::vector<std::string> body_from_storage{"abc", "def"}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| IsAsync()); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| if (IsAsync()) { |
| // Blocked on reading the header. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the header, and then blocked on reading the body. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body ("abc"). |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body ("def"). |
| compare_reader_rawptr->CompletePendingRead(); |
| } |
| |
| // Complete the comparison of the body. It should be identical. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, Identical_Empty) { |
| // Response body from the network, which is empty. |
| const std::string body_from_net(""); |
| |
| // Stored data for |kScriptURL| (the data for compare reader). |
| const std::vector<std::string> body_from_storage{body_from_net}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| IsAsync()); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| if (IsAsync()) { |
| // Blocked on reading the header. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the header. The initial block of the network body is empty, and |
| // the empty body is passed to the cache writer. It will finish the |
| // comparison immediately. |
| compare_reader_rawptr->CompletePendingRead(); |
| } |
| |
| // Both network and storage are empty. The result should be kIdentical. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_FALSE(check_result.value().paused_state); |
| } |
| |
| TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, |
| Different_SingleRead_NetworkIsLonger) { |
| // Response body from the network. |
| const std::string body_from_net = "abcdef"; |
| |
| // Stored data for |kScriptURL|. |
| const std::vector<std::string> body_from_storage{"abc", ""}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| IsAsync()); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| if (IsAsync()) { |
| // Blocked on reading the header. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the header, and then blocked on reading the body. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body ("abc"). |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body from storage (""). The cache writer detects the end of |
| // the body from the disk cache. |
| compare_reader_rawptr->CompletePendingRead(); |
| } |
| |
| // Complete the comparison of the body. It should be different. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, |
| Different_SingleRead_StorageIsLonger) { |
| // Response body from the network. |
| const std::string body_from_net = "abc"; |
| |
| // Stored data for |kScriptURL|. |
| const std::vector<std::string> body_from_storage{"abc", "def"}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| IsAsync()); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| if (IsAsync()) { |
| // Blocked on reading the header. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the header, and then blocked on reading the body. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body ("abc"). At this point, data from the network reaches |
| // the end. |
| compare_reader_rawptr->CompletePendingRead(); |
| } |
| |
| // Complete the comparison of the body. It should be different. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| |
| // The update checker realizes that the script is different before reaching |
| // the end of the script from the disk cache. |
| EXPECT_FALSE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| // Regression test for https://crbug.com/995146. |
| // It should detect the update appropriately even when OnComplete() arrives |
| // after the end of the body. |
| TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, |
| Different_SingleRead_EndOfTheBodyFirst) { |
| // Response body from the network. |
| const std::string body_from_net = "abc"; |
| |
| // Stored data for |kScriptURL|. |
| const std::vector<std::string> body_from_storage{"abc", "def"}; |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| IsAsync()); |
| |
| auto loader_factory = std::make_unique<network::TestURLLoaderFactory>(); |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| // Wait until the testing loader factory receives the network request. |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(1u, loader_factory->pending_requests()->size()); |
| |
| // |client| simulates sending the data from the network to the update checker. |
| network::mojom::URLLoaderClientPtr client = |
| std::move(loader_factory->GetPendingRequest(0)->client); |
| |
| // Simulate sending the response head. |
| network::ResourceResponseHead head; |
| head.headers = base::MakeRefCounted<net::HttpResponseHeaders>( |
| net::HttpUtil::AssembleRawHeaders(kSuccessHeader)); |
| head.headers->GetMimeType(&head.mime_type); |
| client->OnReceiveResponse(head); |
| |
| // Simulate sending the response body. The buffer size for the data pipe |
| // should be larger than the body to send the whole body in one chunk. |
| MojoCreateDataPipeOptions options; |
| options.struct_size = sizeof(MojoCreateDataPipeOptions); |
| options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE; |
| options.element_num_bytes = 1; |
| options.capacity_num_bytes = body_from_net.size() * 2; |
| mojo::ScopedDataPipeConsumerHandle body_consumer; |
| mojo::ScopedDataPipeProducerHandle body_producer; |
| ASSERT_EQ(MOJO_RESULT_OK, |
| mojo::CreateDataPipe(&options, &body_producer, &body_consumer)); |
| client->OnStartLoadingResponseBody(std::move(body_consumer)); |
| mojo::BlockingCopyFromString(body_from_net, body_producer); |
| body_producer.reset(); |
| |
| if (IsAsync()) { |
| // Blocked on reading the header. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the header, and then blocked on reading the body. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body ("abc"). At this point, data from the network reaches |
| // the end. |
| compare_reader_rawptr->CompletePendingRead(); |
| } |
| |
| // Comparison should not finish until OnComplete() is called. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Call OnComplete() to complete the comparison. The new script should be |
| // different. |
| client->OnComplete(network::URLLoaderCompletionStatus(net::OK)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent, |
| check_result.value().result); |
| EXPECT_EQ(kScriptURL, check_result.value().url); |
| |
| // The update checker realizes that the script is different before reaching |
| // the end of the script from the disk cache. |
| EXPECT_FALSE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, |
| Different_SingleRead_DifferentBody) { |
| // Response body from the network. |
| const std::string body_from_net = "abc"; |
| |
| // Stored data for |kScriptURL|. |
| const std::vector<std::string> body_from_storage{"abx"}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| IsAsync()); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| if (IsAsync()) { |
| // Blocked on reading the header. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the header, and then blocked on reading the body. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body ("abx"). |
| compare_reader_rawptr->CompletePendingRead(); |
| } |
| |
| // Complete the comparison of the body. It should be different. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, |
| Different_MultipleRead_NetworkIsLonger) { |
| // Response body from the network. |
| const std::string body_from_net = "abcdef"; |
| |
| // Stored data for |kScriptURL| (the data for compare reader). |
| const std::vector<std::string> body_from_storage{"ab", "c", ""}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| IsAsync()); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| if (IsAsync()) { |
| // Blocked on reading the header. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the header, and then blocked on reading the body. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body from storage ("ab"), and then blocked on reading the |
| // body again. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body from storage ("c"), and then blocked on reading the body |
| // again. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body from storage (""). The cache writer detects the end of |
| // the body from the disk cache. |
| compare_reader_rawptr->CompletePendingRead(); |
| } |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, |
| Different_MultipleRead_StorageIsLonger) { |
| // Response body from the network. |
| const std::string body_from_net = "abc"; |
| |
| // Stored data for |kScriptURL| (the data for compare reader). |
| const std::vector<std::string> body_from_storage{"ab", "c", "def"}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| IsAsync()); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| if (IsAsync()) { |
| // Blocked on reading the header. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the header, and then blocked on reading the body. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body from storage ("ab"), and then blocked on reading the |
| // body again. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body from storage ("c"). At this point, data from the network |
| // reaches the end. |
| compare_reader_rawptr->CompletePendingRead(); |
| } |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| |
| // The update checker realizes that the script is different before reaching |
| // the end of the script from the disk cache. |
| EXPECT_FALSE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, |
| Different_MultipleRead_DifferentBody) { |
| // Response body from the network. |
| const std::string body_from_net = "abc"; |
| |
| // Stored data for |kScriptURL| (the data for compare reader). |
| const std::vector<std::string> body_from_storage{"ab", "x"}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| IsAsync()); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| if (IsAsync()) { |
| // Blocked on reading the header. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the header, and then blocked on reading the body. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body from storage ("ab"), and then blocked on reading the |
| // body again. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Unblock the body from storage ("x"), which is different from the body |
| // from the network. |
| compare_reader_rawptr->CompletePendingRead(); |
| } |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent); |
| EXPECT_EQ(check_result.value().url, kScriptURL); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, |
| PendingReadWithErrorStatusShouldNotLeak) { |
| // Response body from the network. |
| const std::string body_from_net("abc"); |
| |
| // Stored data for |kScriptURL| (the data for compare reader). |
| const std::vector<std::string> body_from_storage{"ab", "c"}; |
| |
| auto loader_factory = std::make_unique<network::TestURLLoaderFactory>(); |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| /*async=*/true); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateCheckerWithoutHttpCache( |
| kScriptURL, GURL(kScope), std::move(compare_reader), |
| std::move(copy_reader), std::move(writer), loader_factory.get(), |
| &check_result); |
| |
| // The update checker sends a request to the loader. The testing loader keeps |
| // the request. |
| base::RunLoop().RunUntilIdle(); |
| network::TestURLLoaderFactory::PendingRequest* request = |
| loader_factory->GetPendingRequest(0); |
| ASSERT_TRUE(request); |
| |
| // Simulate to send the head and the body back to the checker. |
| // Note that OnComplete() is not called yet. |
| { |
| network::ResourceResponseHead head = |
| network::CreateResourceResponseHead(net::HTTP_OK); |
| head.headers = base::MakeRefCounted<net::HttpResponseHeaders>( |
| net::HttpUtil::AssembleRawHeaders(kSuccessHeader)); |
| head.headers->GetMimeType(&head.mime_type); |
| request->client->OnReceiveResponse(head); |
| |
| MojoCreateDataPipeOptions options; |
| options.struct_size = sizeof(MojoCreateDataPipeOptions); |
| options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE; |
| options.element_num_bytes = 1; |
| options.capacity_num_bytes = body_from_net.size(); |
| mojo::ScopedDataPipeConsumerHandle consumer; |
| mojo::ScopedDataPipeProducerHandle producer; |
| EXPECT_EQ(MOJO_RESULT_OK, |
| mojo::CreateDataPipe(&options, &producer, &consumer)); |
| uint32_t bytes_written = body_from_net.size(); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| producer->WriteData(body_from_net.data(), &bytes_written, |
| MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); |
| request->client->OnStartLoadingResponseBody(std::move(consumer)); |
| } |
| |
| // Blocked on reading the header from the storage due to the asynchronous |
| // read. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Update check stops in CompareReader() due to the asynchronous read of the |
| // |compare_reader|. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(check_result.has_value()); |
| |
| // Return failed status code at this point. The update checker will throw the |
| // internal state away. |
| request->client->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_ABORTED)); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Resume the pending read. This should not crash and return kFailed. |
| compare_reader_rawptr->CompletePendingRead(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(ServiceWorkerSingleScriptUpdateChecker::Result::kFailed, |
| check_result.value().result); |
| } |
| |
| // Tests cache validation behavior when updateViaCache is 'all'. |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, UpdateViaCache_All) { |
| auto loader_factory = std::make_unique<network::TestURLLoaderFactory>(); |
| base::Optional<CheckResult> check_result; |
| |
| // Load the main script. Should not validate the cache. |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker( |
| kScriptURL, kScriptURL, GURL(kScope), false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kAll, base::TimeDelta(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), |
| loader_factory.get(), &check_result); |
| |
| const network::ResourceRequest* request = nullptr; |
| ASSERT_TRUE(loader_factory->IsPending(kScriptURL, &request)); |
| EXPECT_FALSE(request->load_flags & net::LOAD_VALIDATE_CACHE); |
| |
| // Load imported script. Should not validate the cache. |
| checker = CreateSingleScriptUpdateChecker( |
| kImportedScriptURL, kScriptURL, GURL(kScope), |
| false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kAll, base::TimeDelta(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), loader_factory.get(), |
| &check_result); |
| |
| ASSERT_TRUE(loader_factory->IsPending(kImportedScriptURL, &request)); |
| EXPECT_FALSE(request->load_flags & net::LOAD_VALIDATE_CACHE); |
| } |
| |
| // Tests cache validation behavior when updateViaCache is 'none'. |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, UpdateViaCache_None) { |
| auto loader_factory = std::make_unique<network::TestURLLoaderFactory>(); |
| base::Optional<CheckResult> check_result; |
| |
| // Load the main script. Should validate the cache. |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker( |
| kScriptURL, kScriptURL, GURL(kScope), false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kNone, base::TimeDelta(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), |
| loader_factory.get(), &check_result); |
| |
| const network::ResourceRequest* request = nullptr; |
| ASSERT_TRUE(loader_factory->IsPending(kScriptURL, &request)); |
| EXPECT_TRUE(request->load_flags & net::LOAD_VALIDATE_CACHE); |
| |
| // Load imported script. Should validate the cache. |
| checker = CreateSingleScriptUpdateChecker( |
| kImportedScriptURL, kScriptURL, GURL(kScope), |
| false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kNone, base::TimeDelta(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), loader_factory.get(), |
| &check_result); |
| |
| ASSERT_TRUE(loader_factory->IsPending(kImportedScriptURL, &request)); |
| EXPECT_TRUE(request->load_flags & net::LOAD_VALIDATE_CACHE); |
| } |
| |
| // Tests cache validation behavior when updateViaCache is 'imports'. |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, UpdateViaCache_Imports) { |
| auto loader_factory = std::make_unique<network::TestURLLoaderFactory>(); |
| base::Optional<CheckResult> check_result; |
| |
| // Load main script. Should validate the cache. |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker( |
| kScriptURL, kScriptURL, GURL(kScope), false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kImports, |
| base::TimeDelta(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), |
| loader_factory.get(), &check_result); |
| |
| const network::ResourceRequest* request = nullptr; |
| ASSERT_TRUE(loader_factory->IsPending(kScriptURL, &request)); |
| EXPECT_TRUE(request->load_flags & net::LOAD_VALIDATE_CACHE); |
| |
| // Load imported script. Should not validate the cache. |
| checker = CreateSingleScriptUpdateChecker( |
| kImportedScriptURL, kScriptURL, GURL(kScope), |
| false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kImports, base::TimeDelta(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), loader_factory.get(), |
| &check_result); |
| |
| ASSERT_TRUE(loader_factory->IsPending(kImportedScriptURL, &request)); |
| EXPECT_FALSE(request->load_flags & net::LOAD_VALIDATE_CACHE); |
| } |
| |
| // Tests cache validation behavior when version's |
| // |force_bypass_cache_for_scripts_| is true. |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, ForceBypassCache) { |
| auto loader_factory = std::make_unique<network::TestURLLoaderFactory>(); |
| base::Optional<CheckResult> check_result; |
| |
| // Load main script. Should validate the cache. |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker( |
| kScriptURL, kScriptURL, GURL(kScope), true /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kAll, base::TimeDelta(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), |
| loader_factory.get(), &check_result); |
| |
| const network::ResourceRequest* request = nullptr; |
| ASSERT_TRUE(loader_factory->IsPending(kScriptURL, &request)); |
| EXPECT_TRUE(request->load_flags & net::LOAD_VALIDATE_CACHE); |
| |
| // Load imported script. Should validate the cache. |
| checker = CreateSingleScriptUpdateChecker( |
| kImportedScriptURL, kScriptURL, GURL(kScope), |
| true /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kAll, base::TimeDelta(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), loader_factory.get(), |
| &check_result); |
| |
| ASSERT_TRUE(loader_factory->IsPending(kImportedScriptURL, &request)); |
| EXPECT_TRUE(request->load_flags & net::LOAD_VALIDATE_CACHE); |
| } |
| |
| // Tests cache validation behavior when more than 24 hours passed. |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, MoreThan24Hours) { |
| auto loader_factory = std::make_unique<network::TestURLLoaderFactory>(); |
| base::Optional<CheckResult> check_result; |
| |
| // Load main script. Should validate the cache. |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker( |
| kScriptURL, kScriptURL, GURL(kScope), false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kAll, |
| base::TimeDelta::FromDays(1) + base::TimeDelta::FromHours(1), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), |
| loader_factory.get(), &check_result); |
| |
| const network::ResourceRequest* request = nullptr; |
| ASSERT_TRUE(loader_factory->IsPending(kScriptURL, &request)); |
| EXPECT_TRUE(request->load_flags & net::LOAD_VALIDATE_CACHE); |
| |
| // Load imported script. Should validate the cache. |
| checker = CreateSingleScriptUpdateChecker( |
| kImportedScriptURL, kScriptURL, GURL(kScope), |
| false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kAll, |
| base::TimeDelta::FromDays(1) + base::TimeDelta::FromHours(1), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), loader_factory.get(), |
| &check_result); |
| |
| ASSERT_TRUE(loader_factory->IsPending(kImportedScriptURL, &request)); |
| EXPECT_TRUE(request->load_flags & net::LOAD_VALIDATE_CACHE); |
| } |
| |
| // Tests MIME type header checking. |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, MimeTypeError) { |
| // Response body from the network. |
| const std::string kBodyFromNet = "abcdef"; |
| |
| // It should report error for no/bad MIME types. |
| const char* kNoMimeHeader = "HTTP/1.1 200 OK\n\n"; |
| const char* kBadMimeHeader = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: text/css\n\n"; |
| const std::string headers[] = {kNoMimeHeader, kBadMimeHeader}; |
| |
| for (const std::string& header : headers) { |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), header, kBodyFromNet, |
| net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker( |
| kScriptURL, kScriptURL, GURL(kScope), |
| false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kNone, base::TimeDelta(), |
| std::move(compare_reader), std::move(copy_reader), |
| std::move(writer), loader_factory.get(), &check_result); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kFailed); |
| EXPECT_EQ(check_result.value().failure_info->status, |
| blink::ServiceWorkerStatusCode::kErrorSecurity); |
| } |
| } |
| |
| // Tests path restriction check error for main script. |
| // |kOutScope| is not under the default scope ("/in-scope/") and the |
| // Service-Worker-Allowed header is not specified. The check should fail. |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, PathRestrictionError) { |
| // Response body from the network. |
| const std::string kBodyFromNet = "abcdef"; |
| const char kMainScriptURL[] = "https://example.com/in-scope/worker.js"; |
| const char kOutScope[] = "https://example.com/out-scope/"; |
| const char kHeader[] = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: text/javascript\n\n"; |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kMainScriptURL), kHeader, |
| kBodyFromNet, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker( |
| kMainScriptURL, kMainScriptURL, GURL(kOutScope), |
| false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kNone, base::TimeDelta(), |
| std::move(compare_reader), std::move(copy_reader), std::move(writer), |
| loader_factory.get(), &check_result); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kFailed); |
| EXPECT_EQ(check_result.value().failure_info->status, |
| blink::ServiceWorkerStatusCode::kErrorSecurity); |
| } |
| |
| // Tests path restriction check success for main script. |
| // |kOutScope| is not under the default scope ("/in-scope/") but the |
| // Service-Worker-Allowed header allows it. The check should pass. |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, PathRestrictionPass) { |
| // Response body from the network. |
| const std::string body_from_net("abcdef"); |
| const char kMainScriptURL[] = "https://example.com/in-scope/worker.js"; |
| const char kOutScope[] = "https://example.com/out-scope/"; |
| const char kHeader[] = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: text/javascript\n" |
| "Service-Worker-Allowed: /out-scope/\n\n"; |
| |
| // Stored data for |kMainScriptURL|. |
| const std::vector<std::string> body_from_storage{body_from_net}; |
| |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kMainScriptURL), kHeader, |
| body_from_net, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get(); |
| compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage), |
| false /* async */); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker( |
| kMainScriptURL, kMainScriptURL, GURL(kOutScope), |
| false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kNone, base::TimeDelta(), |
| std::move(compare_reader), std::move(copy_reader), std::move(writer), |
| loader_factory.get(), &check_result); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical); |
| EXPECT_EQ(check_result.value().url, kMainScriptURL); |
| EXPECT_EQ(check_result.value().failure_info, nullptr); |
| EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone()); |
| } |
| |
| // Tests network error is reported. |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, NetworkError) { |
| // Response body from the network. |
| const std::string kBodyFromNet = "abcdef"; |
| const char kFailHeader[] = "HTTP/1.1 404 Not Found\n\n"; |
| std::unique_ptr<network::TestURLLoaderFactory> loader_factory = |
| CreateLoaderFactoryWithRespone(GURL(kScriptURL), kFailHeader, |
| kBodyFromNet, net::OK); |
| |
| auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>(); |
| auto writer = std::make_unique<MockServiceWorkerResponseWriter>(); |
| |
| base::Optional<CheckResult> check_result; |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker( |
| kScriptURL, kScriptURL, GURL(kScope), false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kNone, base::TimeDelta(), |
| std::move(compare_reader), std::move(copy_reader), std::move(writer), |
| loader_factory.get(), &check_result); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(check_result.has_value()); |
| EXPECT_EQ(check_result.value().result, |
| ServiceWorkerSingleScriptUpdateChecker::Result::kFailed); |
| EXPECT_EQ(check_result.value().failure_info->status, |
| blink::ServiceWorkerStatusCode::kErrorNetwork); |
| } |
| |
| // The main script needs to request a SSL info so that the navigation handled |
| // by the service worker can use the SSL info served for the main script. |
| TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, RequestSSLInfo) { |
| auto loader_factory = std::make_unique<network::TestURLLoaderFactory>(); |
| base::Optional<CheckResult> check_result; |
| |
| // Load the main script. It needs a SSL info. |
| std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker = |
| CreateSingleScriptUpdateChecker( |
| kScriptURL, kScriptURL, GURL(kScope), false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kNone, base::TimeDelta(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), |
| loader_factory.get(), &check_result); |
| base::RunLoop().RunUntilIdle(); |
| |
| { |
| ASSERT_EQ(1u, loader_factory->pending_requests()->size()); |
| const network::TestURLLoaderFactory::PendingRequest* pending_request = |
| loader_factory->GetPendingRequest(0); |
| EXPECT_EQ(kScriptURL, pending_request->request.url); |
| EXPECT_EQ(network::mojom::kURLLoadOptionSendSSLInfoWithResponse, |
| pending_request->options); |
| } |
| |
| // Load imported script. It doesn't need SSL info. |
| checker = CreateSingleScriptUpdateChecker( |
| kImportedScriptURL, kScriptURL, GURL(kScope), |
| false /* force_bypass_cache */, |
| blink::mojom::ServiceWorkerUpdateViaCache::kNone, base::TimeDelta(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseReader>(), |
| std::make_unique<MockServiceWorkerResponseWriter>(), loader_factory.get(), |
| &check_result); |
| base::RunLoop().RunUntilIdle(); |
| |
| { |
| ASSERT_EQ(2u, loader_factory->pending_requests()->size()); |
| const network::TestURLLoaderFactory::PendingRequest* pending_request = |
| loader_factory->GetPendingRequest(1); |
| EXPECT_EQ(kImportedScriptURL, pending_request->request.url); |
| EXPECT_EQ(network::mojom::kURLLoadOptionNone, pending_request->options); |
| } |
| } |
| |
| } // namespace |
| } // namespace content |