blob: e51d6cc928359021049095c097099bc5f20ed75d [file] [log] [blame]
// 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