blob: 8e75e6f059f139fdcc8cbbd0fd448ce321609d51 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/background_fetch/background_fetch_context.h"
#include "content/browser/background_fetch/background_fetch_data_manager_observer.h"
#include "content/browser/background_fetch/background_fetch_job_controller.h"
#include "content/browser/background_fetch/background_fetch_registration_id.h"
#include "content/browser/background_fetch/background_fetch_service_impl.h"
#include "content/browser/background_fetch/background_fetch_test_base.h"
#include "content/browser/background_fetch/background_fetch_test_data_manager.h"
#include "content/browser/background_fetch/background_fetch_test_service_worker.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/storage_partition_impl.h"
#include "content/common/background_fetch/background_fetch_types.h"
#include "content/test/fake_mojo_message_dispatch_context.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/manifest/manifest.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
#include "ui/gfx/geometry/size.h"
namespace content {
namespace {
using testing::_;
const char kExampleUniqueId[] = "7e57ab1e-c0de-a150-ca75-1e75f005ba11";
const char kExampleDeveloperId[] = "my-background-fetch";
const char kAlternativeDeveloperId[] = "my-alternative-fetch";
blink::Manifest::ImageResource CreateIcon(const std::string& src,
std::vector<gfx::Size> sizes,
const std::string& type) {
blink::Manifest::ImageResource icon;
icon.src = GURL(src);
icon.sizes = std::move(sizes);
icon.type = base::ASCIIToUTF16(type);
return icon;
}
bool ContainsHeader(const base::flat_map<std::string, std::string>& headers,
const std::string& target) {
return headers.cend() !=
std::find_if(headers.cbegin(), headers.cend(),
[target](const auto& pair) -> bool {
return base::EqualsCaseInsensitiveASCII(pair.first,
target);
});
}
std::vector<blink::mojom::FetchAPIRequestPtr> CloneRequestVector(
const std::vector<blink::mojom::FetchAPIRequestPtr>& requests) {
std::vector<blink::mojom::FetchAPIRequestPtr> request_cp;
for (const auto& request : requests) {
request_cp.push_back(BackgroundFetchSettledFetch::CloneRequest(request));
}
return request_cp;
}
} // namespace
class BackgroundFetchServiceTest
: public BackgroundFetchTestBase,
public BackgroundFetchDataManagerObserver,
public ServiceWorkerContextCoreObserver,
public DevToolsBackgroundServicesContextImpl::EventObserver {
public:
BackgroundFetchServiceTest() = default;
~BackgroundFetchServiceTest() override = default;
class ScopedCustomBackgroundFetchService {
public:
ScopedCustomBackgroundFetchService(BackgroundFetchServiceTest* test,
const url::Origin& origin)
: scoped_service_(&test->service_,
std::make_unique<BackgroundFetchServiceImpl>(
test->context_,
origin,
/* render_frame_tree_node_id= */ 0,
/* wc_getter= */ base::NullCallback())) {}
private:
base::AutoReset<std::unique_ptr<BackgroundFetchServiceImpl>>
scoped_service_;
DISALLOW_COPY_AND_ASSIGN(ScopedCustomBackgroundFetchService);
};
// Synchronous wrapper for BackgroundFetchServiceImpl::Fetch().
mojo::Remote<blink::mojom::BackgroundFetchRegistrationService> Fetch(
int64_t service_worker_registration_id,
const std::string& developer_id,
std::vector<blink::mojom::FetchAPIRequestPtr> requests,
blink::mojom::BackgroundFetchOptionsPtr options,
const SkBitmap& icon,
blink::mojom::BackgroundFetchError* out_error,
blink::mojom::BackgroundFetchRegistrationPtr* out_registration) {
DCHECK(out_error);
DCHECK(out_registration);
base::RunLoop run_loop;
service_->Fetch(
service_worker_registration_id, developer_id, std::move(requests),
std::move(options), icon, blink::mojom::BackgroundFetchUkmData::New(),
base::BindOnce(&BackgroundFetchServiceTest::DidGetRegistration,
base::Unretained(this), run_loop.QuitClosure(),
out_error, out_registration));
run_loop.Run();
if (*out_error != blink::mojom::BackgroundFetchError::NONE) {
DCHECK(!(*out_registration)->registration_interface);
return mojo::Remote<blink::mojom::BackgroundFetchRegistrationService>();
}
DCHECK((*out_registration)->registration_interface);
return mojo::Remote<blink::mojom::BackgroundFetchRegistrationService>(
std::move((*out_registration)->registration_interface));
}
// Starts the Fetch without completing it. Only creates a registration.
void StartFetch(int64_t service_worker_registration_id,
const std::string& developer_id,
std::vector<blink::mojom::FetchAPIRequestPtr> requests,
blink::mojom::BackgroundFetchOptionsPtr options,
const SkBitmap& icon) {
BackgroundFetchRegistrationId registration_id(
service_worker_registration_id, origin(), developer_id,
kExampleUniqueId);
base::RunLoop run_loop;
context_->data_manager_->CreateRegistration(
registration_id, std::move(requests), std::move(options), icon,
/* start_paused= */ false,
base::BindOnce(&BackgroundFetchServiceTest::DidStartFetch,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
}
// Calls BackgroundFetchServiceImpl::Fetch() and unregisters the service
// worker before Fetch has completed but after the controller has been
// initialized.
void FetchAndUnregisterServiceWorker(
int64_t service_worker_registration_id,
const std::string& developer_id,
std::vector<blink::mojom::FetchAPIRequestPtr> requests,
blink::mojom::BackgroundFetchOptionsPtr options,
const SkBitmap& icon,
blink::mojom::BackgroundFetchError* out_error,
blink::mojom::BackgroundFetchRegistrationPtr* out_registration) {
DCHECK(out_error);
DCHECK(out_registration);
base::RunLoop run_loop;
service_->Fetch(
service_worker_registration_id, developer_id, std::move(requests),
std::move(options), icon, blink::mojom::BackgroundFetchUkmData::New(),
base::BindOnce(&BackgroundFetchServiceTest::DidGetRegistration,
base::Unretained(this), run_loop.QuitClosure(),
out_error, out_registration));
UnregisterServiceWorker(service_worker_registration_id);
run_loop.Run();
}
// Synchronous wrapper for BackgroundFetchServiceImpl::MatchRequests.
void MatchAllRequests(
const mojo::Remote<blink::mojom::BackgroundFetchRegistrationService>&
registration_service,
std::vector<blink::mojom::BackgroundFetchSettledFetchPtr>* out_fetches) {
DCHECK(registration_service);
DCHECK(out_fetches);
base::RunLoop run_loop;
registration_service->MatchRequests(
/* request_to_match= */ nullptr,
/* cache_query_options= */ nullptr, /* match_all= */ true,
base::BindOnce(&BackgroundFetchServiceTest::DidMatchAllRequests,
base::Unretained(this), run_loop.QuitClosure(),
out_fetches));
run_loop.Run();
}
// Synchronous wrapper for BackgroundFetchServiceImpl::UpdateUI().
void UpdateUI(
const mojo::Remote<blink::mojom::BackgroundFetchRegistrationService>&
registration_service,
const std::string& title,
blink::mojom::BackgroundFetchError* out_error) {
DCHECK(registration_service);
DCHECK(out_error);
base::RunLoop run_loop;
registration_service->UpdateUI(
title, SkBitmap(),
base::BindOnce(&BackgroundFetchServiceTest::DidGetError,
base::Unretained(this), run_loop.QuitClosure(),
out_error));
run_loop.Run();
}
// Synchronous wrapper for BackgroundFetchServiceImpl::Abort().
void Abort(
const mojo::Remote<blink::mojom::BackgroundFetchRegistrationService>&
registration_service,
blink::mojom::BackgroundFetchError* out_error) {
DCHECK(registration_service);
DCHECK(out_error);
base::RunLoop run_loop;
registration_service->Abort(base::BindOnce(
&BackgroundFetchServiceTest::DidGetError, base::Unretained(this),
run_loop.QuitClosure(), out_error));
run_loop.Run();
}
// Synchronous wrapper for BackgroundFetchServiceImpl::GetRegistration().
void GetRegistration(
int64_t service_worker_registration_id,
const std::string& developer_id,
blink::mojom::BackgroundFetchError* out_error,
blink::mojom::BackgroundFetchRegistrationPtr* out_registration) {
DCHECK(out_error);
DCHECK(out_registration);
base::RunLoop run_loop;
service_->GetRegistration(
service_worker_registration_id, developer_id,
base::BindOnce(&BackgroundFetchServiceTest::DidGetRegistration,
base::Unretained(this), run_loop.QuitClosure(),
out_error, out_registration));
run_loop.Run();
}
// Synchronous wrapper for BackgroundFetchServiceImpl::GetDeveloperIds().
void GetDeveloperIds(int64_t service_worker_registration_id,
blink::mojom::BackgroundFetchError* out_error,
std::vector<std::string>* out_developer_ids) {
DCHECK(out_error);
DCHECK(out_developer_ids);
base::RunLoop run_loop;
service_->GetDeveloperIds(
service_worker_registration_id,
base::BindOnce(&BackgroundFetchServiceTest::DidGetDeveloperIds,
base::Unretained(this), run_loop.QuitClosure(),
out_error, out_developer_ids));
run_loop.Run();
}
// BackgroundFetchTestBase overrides:
void SetUp() override {
BackgroundFetchTestBase::SetUp();
context_ = base::MakeRefCounted<BackgroundFetchContext>(
browser_context(),
base::WrapRefCounted(embedded_worker_test_helper()->context_wrapper()),
/* cache_storage_context= */ nullptr,
/* quota_manager_proxy= */ nullptr, devtools_context());
context_->SetDataManagerForTesting(
std::make_unique<BackgroundFetchTestDataManager>(
browser_context(), storage_partition(),
embedded_worker_test_helper()->context_wrapper()));
context_->data_manager_->AddObserver(this);
embedded_worker_test_helper()->context_wrapper()->AddObserver(this);
devtools_context()->AddObserver(this);
context_->InitializeOnCoreThread();
service_ = std::make_unique<BackgroundFetchServiceImpl>(
context_, origin(),
/* render_frame_tree_node_id= */ 0,
/* wc_getter= */ base::NullCallback());
}
void TearDown() override {
BackgroundFetchTestBase::TearDown();
service_.reset();
devtools_context()->RemoveObserver(this);
embedded_worker_test_helper()->context_wrapper()->RemoveObserver(this);
context_->data_manager_->RemoveObserver(this);
context_ = nullptr;
// Give pending shutdown operations a chance to finish.
base::RunLoop().RunUntilIdle();
}
// BackgroundFetchDataManagerObserver implementation & mocks:
MOCK_METHOD6(OnRegistrationCreated,
void(const BackgroundFetchRegistrationId& registration_id,
const blink::mojom::BackgroundFetchRegistrationData&
registration_data,
blink::mojom::BackgroundFetchOptionsPtr options,
const SkBitmap& icon,
int num_requests,
bool start_paused));
MOCK_METHOD7(OnRegistrationLoadedAtStartup,
void(const BackgroundFetchRegistrationId& registration_id,
const blink::mojom::BackgroundFetchRegistrationData&
registration_data,
blink::mojom::BackgroundFetchOptionsPtr options,
const SkBitmap& icon,
int num_completed_requests,
int num_requests,
std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests));
MOCK_METHOD2(
OnRegistrationQueried,
void(const BackgroundFetchRegistrationId& registration_id,
blink::mojom::BackgroundFetchRegistrationData* registration_data));
MOCK_METHOD1(OnServiceWorkerDatabaseCorrupted,
void(int64_t service_worker_registration_id));
MOCK_METHOD3(OnRequestCompleted,
void(const std::string& unique_id,
blink::mojom::FetchAPIRequestPtr request,
blink::mojom::FetchAPIResponsePtr response));
// ServiceWorkerContextCoreObserver implementation.
MOCK_METHOD2(OnRegistrationDeleted,
void(int64_t registration_id, const GURL& pattern));
MOCK_METHOD0(OnStorageWiped, void());
// DevToolsBackgroundServicesContext::EventObserver implementation.
MOCK_METHOD1(OnEventReceived,
void(const devtools::proto::BackgroundServiceEvent&));
MOCK_METHOD2(OnRecordingStateChanged,
void(bool, devtools::proto::BackgroundService));
protected:
blink::mojom::FetchAPIRequestPtr CreateDefaultRequest() {
return CreateRequestWithProvidedResponse(
"GET", GURL("https://example.com/funny_cat.txt"),
TestResponseBuilder(200).MakeIndefinitelyPending().Build());
}
scoped_refptr<BackgroundFetchContext> context_;
private:
void DidGetRegistration(
base::OnceClosure quit_closure,
blink::mojom::BackgroundFetchError* out_error,
blink::mojom::BackgroundFetchRegistrationPtr* out_registration,
blink::mojom::BackgroundFetchError error,
blink::mojom::BackgroundFetchRegistrationPtr registration) {
*out_error = error;
*out_registration = registration
? std::move(registration)
: blink::mojom::BackgroundFetchRegistration::New();
std::move(quit_closure).Run();
}
void DidStartFetch(
base::OnceClosure quit_closure,
blink::mojom::BackgroundFetchError error,
blink::mojom::BackgroundFetchRegistrationDataPtr registration_data) {
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
std::move(quit_closure).Run();
}
void DidGetError(base::OnceClosure quit_closure,
blink::mojom::BackgroundFetchError* out_error,
blink::mojom::BackgroundFetchError error) {
*out_error = error;
std::move(quit_closure).Run();
}
void DidGetDeveloperIds(base::OnceClosure quit_closure,
blink::mojom::BackgroundFetchError* out_error,
std::vector<std::string>* out_developer_ids,
blink::mojom::BackgroundFetchError error,
const std::vector<std::string>& developer_ids) {
*out_error = error;
*out_developer_ids = developer_ids;
std::move(quit_closure).Run();
}
void DidMatchAllRequests(
base::OnceClosure quit_closure,
std::vector<blink::mojom::BackgroundFetchSettledFetchPtr>* out_fetches,
std::vector<blink::mojom::BackgroundFetchSettledFetchPtr> fetches) {
for (const auto& in_fetch : fetches) {
auto out_fetch = blink::mojom::BackgroundFetchSettledFetch::New();
out_fetch->request =
BackgroundFetchSettledFetch::CloneRequest(in_fetch->request);
out_fetch->response =
BackgroundFetchSettledFetch::CloneResponse(in_fetch->response);
out_fetches->push_back(std::move(out_fetch));
}
std::move(quit_closure).Run();
}
std::unique_ptr<BackgroundFetchServiceImpl> service_;
DISALLOW_COPY_AND_ASSIGN(BackgroundFetchServiceTest);
};
TEST_F(BackgroundFetchServiceTest, FetchInvalidArguments) {
// This test verifies that the Fetch() function will kill the renderer and
// return INVALID_ARGUMENT when invalid data is send over the Mojo channel.
auto options = blink::mojom::BackgroundFetchOptions::New();
// The |developer_id| must be a non-empty string.
{
FakeMojoMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateDefaultRequest());
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
Fetch(/* service_worker_registration_id= */ 42, /* developer_id= */ "",
std::move(requests), options.Clone(), SkBitmap(), &error,
&registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::INVALID_ARGUMENT);
EXPECT_EQ("Invalid developer_id", bad_message_observer.WaitForBadMessage());
}
// At least a single blink::mojom::FetchAPIRequestPtr must be given.
{
FakeMojoMessageDispatchContext fake_dispatch_context;
mojo::test::BadMessageObserver bad_message_observer;
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
// |requests| has deliberately been left empty.
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
Fetch(/* service_worker_registration_id= */ 42, kExampleDeveloperId,
std::move(requests), std::move(options), SkBitmap(), &error,
&registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::INVALID_ARGUMENT);
EXPECT_EQ("Invalid requests", bad_message_observer.WaitForBadMessage());
}
}
TEST_F(BackgroundFetchServiceTest, FetchRegistrationProperties) {
// This test starts a new Background Fetch and verifies that the returned
// blink::mojom::BackgroundFetchRegistration object matches the given options.
// Then gets the active Background Fetch with the same |developer_id|, and
// verifies that.
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateDefaultRequest());
auto options = blink::mojom::BackgroundFetchOptions::New();
options->icons.push_back(
CreateIcon("funny_cat.png", {{256, 256}}, "image/png"));
options->icons.push_back(
CreateIcon("silly_cat.gif", {{512, 512}}, "image/gif"));
options->title = "My Background Fetch!";
options->download_total = 9001;
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
Fetch(service_worker_registration_id, kExampleDeveloperId,
std::move(requests), options.Clone(), SkBitmap(), &error,
&registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
// The |registration| should reflect the options given in |options|.
EXPECT_EQ(registration->registration_data->developer_id, kExampleDeveloperId);
EXPECT_EQ(registration->registration_data->download_total,
options->download_total);
blink::mojom::BackgroundFetchError second_error;
blink::mojom::BackgroundFetchRegistrationPtr second_registration;
GetRegistration(service_worker_registration_id, kExampleDeveloperId,
&second_error, &second_registration);
ASSERT_EQ(second_error, blink::mojom::BackgroundFetchError::NONE);
// The |second_registration| should reflect the options given in |options|.
EXPECT_EQ(second_registration->registration_data->developer_id,
kExampleDeveloperId);
EXPECT_EQ(second_registration->registration_data->download_total,
options->download_total);
}
TEST_F(BackgroundFetchServiceTest, FetchDuplicatedRegistrationFailure) {
// This tests starts a new Background Fetch, verifies that a registration was
// successfully created, and then tries to start a second fetch for the same
// registration. This should fail with a DUPLICATED_DEVELOPER_ID error.
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateDefaultRequest());
auto options = blink::mojom::BackgroundFetchOptions::New();
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
// Create the first registration. This must succeed.
Fetch(service_worker_registration_id, kExampleDeveloperId,
CloneRequestVector(requests), options.Clone(), SkBitmap(), &error,
&registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
blink::mojom::BackgroundFetchError second_error;
blink::mojom::BackgroundFetchRegistrationPtr second_registration;
// Create the second registration with the same data. This must fail.
Fetch(service_worker_registration_id, kExampleDeveloperId,
std::move(requests), std::move(options), SkBitmap(), &second_error,
&second_registration);
ASSERT_EQ(second_error,
blink::mojom::BackgroundFetchError::DUPLICATED_DEVELOPER_ID);
}
TEST_F(BackgroundFetchServiceTest, FetchSuccessEventDispatch) {
// This test starts a new Background Fetch, completes the registration, then
// fetches all files to complete the job, and then verifies that the
// `backgroundfetchsuccess` event will be dispatched with the expected
// contents.
auto* worker =
embedded_worker_test_helper()
->AddNewPendingServiceWorker<BackgroundFetchTestServiceWorker>(
embedded_worker_test_helper());
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
// base::RunLoop that we'll run until the event has been dispatched. If this
// test times out, it means that the event could not be dispatched.
base::RunLoop event_dispatched_loop;
worker->set_fetched_event_closure(event_dispatched_loop.QuitClosure());
// Don't dispatch the event so the background fetch's response cache is not
// deleted.
worker->delay_dispatch();
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
constexpr int kFirstResponseCode = 200;
constexpr int kSecondResponseCode = 201;
constexpr int kThirdResponseCode = 200;
requests.push_back(CreateRequestWithProvidedResponse(
"GET", GURL("https://example.com/funny_cat.txt"),
TestResponseBuilder(kFirstResponseCode)
.SetResponseData(
"This text describes a scenario involving a funny cat.")
.AddResponseHeader("Content-Type", "text/plain")
.AddResponseHeader("X-Cat", "yes")
.Build()));
requests.push_back(CreateRequestWithProvidedResponse(
"GET", GURL("https://example.com/crazy_cat.txt"),
TestResponseBuilder(kSecondResponseCode)
.SetResponseData(
"This text describes another scenario that involves a crazy cat.")
.AddResponseHeader("Content-Type", "text/plain")
.Build()));
requests.push_back(CreateRequestWithProvidedResponse(
"GET", GURL("https://chrome.com/accessible_cross_origin_cat.txt"),
TestResponseBuilder(kThirdResponseCode)
.SetResponseData("This cat originates from another origin.")
.AddResponseHeader("Access-Control-Allow-Origin", "*")
.AddResponseHeader("Content-Type", "text/plain")
.Build()));
// Create the registration with the given |requests|.
blink::mojom::BackgroundFetchRegistrationPtr registration;
mojo::Remote<blink::mojom::BackgroundFetchRegistrationService>
registration_service;
auto options = blink::mojom::BackgroundFetchOptions::New();
blink::mojom::BackgroundFetchError error;
// Create the first registration. This must succeed.
registration_service =
Fetch(service_worker_registration_id, kExampleDeveloperId,
CloneRequestVector(requests), std::move(options), SkBitmap(),
&error, &registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
// Spin the |event_dispatched_loop| to wait for the dispatched event.
event_dispatched_loop.Run();
ASSERT_TRUE(worker->last_registration());
EXPECT_EQ(kExampleDeveloperId, registration->registration_data->developer_id);
// Get all the settled fetches and test properties.
std::vector<blink::mojom::BackgroundFetchSettledFetchPtr> fetches;
MatchAllRequests(registration_service, &fetches);
ASSERT_EQ(fetches.size(), requests.size());
for (size_t i = 0; i < fetches.size(); ++i) {
ASSERT_EQ(fetches[i]->request->url, requests[i]->url);
EXPECT_EQ(fetches[i]->request->method, requests[i]->method);
EXPECT_EQ(fetches[i]->response->url_list[0], fetches[i]->request->url);
EXPECT_EQ(fetches[i]->response->response_type,
network::mojom::FetchResponseType::kDefault);
switch (i) {
case 0:
EXPECT_EQ(fetches[i]->response->status_code, kFirstResponseCode);
EXPECT_TRUE(
ContainsHeader(fetches[i]->response->headers, "Content-Type"));
EXPECT_TRUE(ContainsHeader(fetches[i]->response->headers, "X-Cat"));
break;
case 1:
EXPECT_EQ(fetches[i]->response->status_code, kSecondResponseCode);
EXPECT_TRUE(
ContainsHeader(fetches[i]->response->headers, "Content-Type"));
EXPECT_FALSE(ContainsHeader(fetches[i]->response->headers, "X-Cat"));
break;
case 2:
EXPECT_EQ(fetches[i]->response->status_code, kThirdResponseCode);
EXPECT_TRUE(
ContainsHeader(fetches[i]->response->headers, "Content-Type"));
EXPECT_FALSE(ContainsHeader(fetches[i]->response->headers, "X-Cat"));
break;
default:
NOTREACHED();
}
// TODO(peter): change-detector tests for unsupported properties.
EXPECT_EQ(fetches[i]->response->error,
blink::mojom::ServiceWorkerResponseError::kUnknown);
// Verify that all properties have a sensible value.
EXPECT_FALSE(fetches[i]->response->response_time.is_null());
// Verify that the response blobs have been populated. We cannot consume
// their data here since the handles have already been released.
ASSERT_TRUE(fetches[i]->response->blob);
ASSERT_FALSE(fetches[i]->response->blob->uuid.empty());
ASSERT_GT(fetches[i]->response->blob->size, 0u);
}
}
TEST_F(BackgroundFetchServiceTest, FetchFailEventDispatch) {
// This test verifies that the fail event will be fired when a response either
// has a non-OK status code, or the response cannot be accessed due to CORS.
auto* worker =
embedded_worker_test_helper()
->AddNewPendingServiceWorker<BackgroundFetchTestServiceWorker>(
embedded_worker_test_helper());
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
// base::RunLoop that we'll run until the event has been dispatched. If this
// test times out, it means that the event could not be dispatched.
base::RunLoop event_dispatched_loop;
worker->set_fetch_fail_event_closure(event_dispatched_loop.QuitClosure());
// Don't dispatch the event so the background fetch's response cache is not
// deleted.
worker->delay_dispatch();
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
constexpr int kFirstResponseCode = 404;
constexpr int kSecondResponseCode = 200;
requests.push_back(CreateRequestWithProvidedResponse(
"GET", GURL("https://example.com/not_existing_cat.txt"),
TestResponseBuilder(kFirstResponseCode).Build()));
requests.push_back(CreateRequestWithProvidedResponse(
"GET", GURL("https://chrome.com/inaccessible_cross_origin_cat.txt"),
TestResponseBuilder(kSecondResponseCode)
.SetResponseData(
"This is a cross-origin response not accessible to the reader.")
.AddResponseHeader("Content-Type", "text/plain")
.Build()));
// Create the registration with the given |requests|.
blink::mojom::BackgroundFetchRegistrationPtr registration;
mojo::Remote<blink::mojom::BackgroundFetchRegistrationService>
registration_service;
{
auto options = blink::mojom::BackgroundFetchOptions::New();
blink::mojom::BackgroundFetchError error;
// Create the first registration. This must succeed.
registration_service =
Fetch(service_worker_registration_id, kExampleDeveloperId,
CloneRequestVector(requests), std::move(options), SkBitmap(),
&error, &registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
}
// Spin the |event_dispatched_loop| to wait for the dispatched event.
event_dispatched_loop.Run();
ASSERT_TRUE(worker->last_registration());
EXPECT_EQ(kExampleDeveloperId, registration->registration_data->developer_id);
// Get all the settled fetches and test properties.
std::vector<blink::mojom::BackgroundFetchSettledFetchPtr> fetches;
MatchAllRequests(registration_service, &fetches);
ASSERT_EQ(fetches.size(), 2u);
// Make sure the 404 request is first, which has a response.
if (!fetches[0]->response)
std::swap(fetches[0], fetches[1]);
for (size_t i = 0; i < fetches.size(); ++i) {
ASSERT_EQ(fetches[i]->request->url, requests[i]->url);
EXPECT_EQ(fetches[i]->request->method, requests[i]->method);
switch (i) {
case 0:
EXPECT_EQ(fetches[i]->response->status_code, 404);
EXPECT_EQ(fetches[i]->response->url_list[0], fetches[i]->request->url);
EXPECT_EQ(fetches[i]->response->response_type,
network::mojom::FetchResponseType::kDefault);
break;
case 1:
EXPECT_FALSE(fetches[i]->response);
continue;
default:
NOTREACHED();
}
EXPECT_TRUE(fetches[i]->response->headers.empty());
EXPECT_FALSE(fetches[i]->response->blob);
EXPECT_FALSE(fetches[i]->response->response_time.is_null());
// TODO(peter): change-detector tests for unsupported properties.
EXPECT_EQ(fetches[i]->response->error,
blink::mojom::ServiceWorkerResponseError::kUnknown);
EXPECT_TRUE(fetches[i]->response->cors_exposed_header_names.empty());
}
}
TEST_F(BackgroundFetchServiceTest, UpdateUI) {
// This test starts a new Background Fetch, completes the registration, and
// checks that updates to the title using UpdateUI are successfully reflected
// back when calling GetRegistration.
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateDefaultRequest());
auto options = blink::mojom::BackgroundFetchOptions::New();
options->title = "1st title";
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
// Create the registration.
auto registration_service = Fetch(
service_worker_registration_id, kExampleDeveloperId, std::move(requests),
std::move(options), SkBitmap(), &error, &registration);
ASSERT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
std::string second_title = "2nd title";
// Immediately update the title. This should succeed.
UpdateUI(registration_service, second_title, &error);
EXPECT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
blink::mojom::BackgroundFetchRegistrationPtr second_registration;
// GetRegistration should now resolve with the updated title.
GetRegistration(service_worker_registration_id, kExampleDeveloperId, &error,
&second_registration);
ASSERT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
}
TEST_F(BackgroundFetchServiceTest, Abort) {
// This test starts a new Background Fetch, completes the registration, and
// then aborts the Background Fetch mid-process.
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateDefaultRequest());
auto options = blink::mojom::BackgroundFetchOptions::New();
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
// Create the registration. This must succeed.
auto registration_service = Fetch(
service_worker_registration_id, kExampleDeveloperId, std::move(requests),
std::move(options), SkBitmap(), &error, &registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
blink::mojom::BackgroundFetchError abort_error;
// Immediately abort the registration. This also is expected to succeed.
Abort(registration_service, &abort_error);
ASSERT_EQ(abort_error, blink::mojom::BackgroundFetchError::NONE);
// Wait for the response of the Mojo IPC to dispatch
// BackgroundFetchAbortEvent.
base::RunLoop().RunUntilIdle();
blink::mojom::BackgroundFetchError second_error;
blink::mojom::BackgroundFetchRegistrationPtr second_registration;
// Now try to get the created registration, which is expected to fail.
GetRegistration(service_worker_registration_id, kExampleDeveloperId,
&second_error, &second_registration);
ASSERT_EQ(second_error, blink::mojom::BackgroundFetchError::INVALID_ID);
auto* delegate = static_cast<MockBackgroundFetchDelegate*>(
browser_context()->GetBackgroundFetchDelegate());
EXPECT_FALSE(delegate->completed_jobs().empty());
}
TEST_F(BackgroundFetchServiceTest, AbortEventDispatch) {
// Tests that the `backgroundfetchabort` event will be fired when a Background
// Fetch registration has been aborted by either the user or developer.
auto* worker =
embedded_worker_test_helper()
->AddNewPendingServiceWorker<BackgroundFetchTestServiceWorker>(
embedded_worker_test_helper());
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
// base::RunLoop that we'll run until the event has been dispatched. If this
// test times out, it means that the event could not be dispatched.
base::RunLoop event_dispatched_loop;
worker->set_abort_event_closure(event_dispatched_loop.QuitClosure());
constexpr int kResponseCode = 200;
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateRequestWithProvidedResponse(
"GET", GURL("https://example.com/funny_cat.txt"),
TestResponseBuilder(kResponseCode)
.SetResponseData("Random data about a funny cat.")
.Build()));
// Create the registration with the given |requests|.
mojo::Remote<blink::mojom::BackgroundFetchRegistrationService>
registration_service;
{
auto options = blink::mojom::BackgroundFetchOptions::New();
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
// Create the registration. This must succeed.
registration_service =
Fetch(service_worker_registration_id, kExampleDeveloperId,
std::move(requests), std::move(options), SkBitmap(), &error,
&registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
}
// Immediately abort the request created for the |registration_id|. Then wait
// for the `backgroundfetchabort` event to have been invoked.
{
blink::mojom::BackgroundFetchError error;
Abort(registration_service, &error);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
}
event_dispatched_loop.Run();
ASSERT_TRUE(worker->last_registration());
EXPECT_EQ(kExampleDeveloperId, worker->last_registration()->developer_id);
}
TEST_F(BackgroundFetchServiceTest, UniqueId) {
// Tests that Abort() and UpdateUI() update the correct Background Fetch
// registration, according to the registration's |unique_id|, rather than
// keying off the |developer_id| provided by JavaScript, since multiple
// registrations can share an |developer_id| if JavaScript holds a reference
// to a blink::mojom::BackgroundFetchRegistration object after that
// registration is completed/failed/aborted and then creates a new
// registration with the same |developer_id|.
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateDefaultRequest());
blink::mojom::BackgroundFetchError error;
// Create the first registration, that we will soon abort.
auto aborted_options = blink::mojom::BackgroundFetchOptions::New();
aborted_options->title = "Aborted";
blink::mojom::BackgroundFetchRegistrationPtr aborted_registration;
auto registration_service =
Fetch(service_worker_registration_id, kExampleDeveloperId,
CloneRequestVector(requests), std::move(aborted_options),
SkBitmap(), &error, &aborted_registration);
ASSERT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
// Immediately abort the registration so it is no longer active (everything
// that follows should behave the same if the registration had completed
// instead of being aborted).
Abort(registration_service, &error);
ASSERT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
// Wait for response of the Mojo IPC to dispatch BackgroundFetchAbortEvent.
base::RunLoop().RunUntilIdle();
// Create a second registration sharing the same |developer_id|. Should
// succeed.
auto second_options = blink::mojom::BackgroundFetchOptions::New();
second_options->title = "Second";
blink::mojom::BackgroundFetchRegistrationPtr second_registration;
auto second_registration_service = Fetch(
service_worker_registration_id, kExampleDeveloperId, std::move(requests),
std::move(second_options), SkBitmap(), &error, &second_registration);
EXPECT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
// Now try to get the registration using its |developer_id|. This should
// return the second registration since that is the active one.
blink::mojom::BackgroundFetchRegistrationPtr gotten_registration;
GetRegistration(service_worker_registration_id, kExampleDeveloperId, &error,
&gotten_registration);
EXPECT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
// Calling UpdateUI for the second registration should succeed, and update the
// title of the second registration only.
std::string updated_second_registration_title = "Foo";
UpdateUI(second_registration_service, updated_second_registration_title,
&error);
EXPECT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
// Verify that the second registration's title was indeed updated, and that it
// wasn't affected by the subsequent call to UpdateUI for the aborted
// registration, by getting the second registration again.
GetRegistration(service_worker_registration_id, kExampleDeveloperId, &error,
&gotten_registration);
EXPECT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
// Aborting the previously aborted registration should fail with INVALID_ID
// since it is no longer active.
Abort(registration_service, &error);
EXPECT_EQ(blink::mojom::BackgroundFetchError::INVALID_ID, error);
// Wait for response of the Mojo IPC to dispatch BackgroundFetchAbortEvent.
// (MockBackgroundFetchDelegate won't complete/fail second_registration in the
// meantime, since this test deliberately doesn't register a response).
base::RunLoop().RunUntilIdle();
// Getting the second registration should still succeed.
GetRegistration(service_worker_registration_id, kExampleDeveloperId, &error,
&gotten_registration);
EXPECT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
// Aborting the second registration should succeed.
Abort(second_registration_service, &error);
EXPECT_EQ(blink::mojom::BackgroundFetchError::NONE, error);
// Wait for response of the Mojo IPC to dispatch BackgroundFetchAbortEvent.
// (MockBackgroundFetchDelegate won't complete/fail second_registration in the
// meantime, since this test deliberately doesn't register a response).
base::RunLoop().RunUntilIdle();
// Getting the second registration should now fail as it is no longer active.
GetRegistration(service_worker_registration_id, kExampleDeveloperId, &error,
&gotten_registration);
EXPECT_EQ(blink::mojom::BackgroundFetchError::INVALID_ID, error);
}
TEST_F(BackgroundFetchServiceTest, GetDeveloperIds) {
// This test verifies that the list of active |developer_id|s can be retrieved
// from the service for a given Service Worker, as extracted from a
// registration.
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateDefaultRequest());
auto options = blink::mojom::BackgroundFetchOptions::New();
// Verify that there are no active |developer_id|s yet.
{
blink::mojom::BackgroundFetchError error;
std::vector<std::string> developer_ids;
GetDeveloperIds(service_worker_registration_id, &error, &developer_ids);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
ASSERT_EQ(developer_ids.size(), 0u);
}
// Start the Background Fetch for the |registration_id|.
{
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
Fetch(service_worker_registration_id, kExampleDeveloperId,
CloneRequestVector(requests), options.Clone(), SkBitmap(), &error,
&registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
}
// Verify that there is a single active fetch (the one we just started).
{
blink::mojom::BackgroundFetchError error;
std::vector<std::string> developer_ids;
GetDeveloperIds(service_worker_registration_id, &error, &developer_ids);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
ASSERT_EQ(developer_ids.size(), 1u);
EXPECT_EQ(developer_ids[0], kExampleDeveloperId);
}
// Start the Background Fetch for the |second_registration_id|.
{
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
Fetch(service_worker_registration_id, kAlternativeDeveloperId,
std::move(requests), std::move(options), SkBitmap(), &error,
&registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
}
// Verify that there are two active fetches.
{
blink::mojom::BackgroundFetchError error;
std::vector<std::string> developer_ids;
GetDeveloperIds(service_worker_registration_id, &error, &developer_ids);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
ASSERT_EQ(developer_ids.size(), 2u);
// Both |developer_id|s should be present, in either order.
EXPECT_TRUE(developer_ids[0] == kExampleDeveloperId ||
developer_ids[1] == kExampleDeveloperId);
EXPECT_TRUE(developer_ids[0] == kAlternativeDeveloperId ||
developer_ids[1] == kAlternativeDeveloperId);
}
// Verify that using the wrong origin does not return developer ids even if
// the service worker registration is correct.
{
ScopedCustomBackgroundFetchService scoped_bogus_url_service(
this, url::Origin::Create(GURL("https://www.bogus-origin.com")));
blink::mojom::BackgroundFetchError error;
std::vector<std::string> developer_ids;
GetDeveloperIds(service_worker_registration_id, &error, &developer_ids);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
// TODO(crbug.com/850076): The Storage Worker Database access is not
// checking the origin. In a non-test environment this won't happen since a
// ServiceWorker registration ID is tied to the origin.
ASSERT_EQ(developer_ids.size(), 2u);
}
// Verify that using the wrong service worker id does not return developer ids
// even if the origin is correct.
{
blink::mojom::BackgroundFetchError error;
std::vector<std::string> developer_ids;
int64_t bogus_service_worker_registration_id =
service_worker_registration_id + 1;
GetDeveloperIds(bogus_service_worker_registration_id, &error,
&developer_ids);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
ASSERT_EQ(developer_ids.size(), 0u);
}
}
TEST_F(BackgroundFetchServiceTest, UnregisterServiceWorker) {
// This test registers a service worker, and calls fetch, but unregisters the
// service worker before fetch has finished. We then verify that the
// appropriate error is returned from Fetch().
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateDefaultRequest());
auto options = blink::mojom::BackgroundFetchOptions::New();
options->icons.push_back(
CreateIcon("funny_cat.png", {{256, 256}}, "image/png"));
options->icons.push_back(
CreateIcon("silly_cat.gif", {{512, 512}}, "image/gif"));
options->title = "My Background Fetch!";
options->download_total = 9001;
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
{
using blink::mojom::BackgroundFetchError;
EXPECT_CALL(*this,
OnRegistrationDeleted(service_worker_registration_id, _));
FetchAndUnregisterServiceWorker(service_worker_registration_id,
kExampleDeveloperId, std::move(requests),
std::move(options), SkBitmap(), &error,
&registration);
// There's a race condition between aborting due to unregistering the
// Service Worker, and a STORAGE_ERROR caused by the DatabaseTask as a
// result.
ASSERT_TRUE(error == BackgroundFetchError::STORAGE_ERROR ||
error == BackgroundFetchError::SERVICE_WORKER_UNAVAILABLE);
}
}
TEST_F(BackgroundFetchServiceTest, JobsInitializedOnBrowserRestart) {
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateDefaultRequest());
auto options = blink::mojom::BackgroundFetchOptions::New();
blink::mojom::BackgroundFetchError error;
blink::mojom::BackgroundFetchRegistrationPtr registration;
// Start the fetch. The request is indefinitley pending so this will never
// finish.
{
EXPECT_CALL(*this, OnRegistrationCreated(_, _, _, _, _, _));
Fetch(service_worker_registration_id, kExampleDeveloperId,
std::move(requests), std::move(options), SkBitmap(), &error,
&registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
}
// Check that the registration is in the DB.
{
std::vector<std::string> developer_ids;
GetDeveloperIds(service_worker_registration_id, &error, &developer_ids);
EXPECT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
ASSERT_EQ(developer_ids.size(), 1u);
EXPECT_EQ(developer_ids[0], kExampleDeveloperId);
}
// Simulate browser restart by re-creating |context_| and |service_|.
context_->Shutdown();
task_environment_.RunUntilIdle();
TearDown();
SetUp();
// Overwrite pending request with a completable version.
CreateRequestWithProvidedResponse(
"GET", GURL("https://example.com/funny_cat.txt"),
TestResponseBuilder(200)
.SetResponseData("This request is no longer indefinitely pending.")
.Build());
{
EXPECT_CALL(*this, OnRegistrationLoadedAtStartup(_, _, _, _, _, _, _));
// Allow restart process to go through.
task_environment_.RunUntilIdle();
}
// Check that the registration is not in the DB, which means it completed.
{
std::vector<std::string> developer_ids;
GetDeveloperIds(service_worker_registration_id, &error, &developer_ids);
EXPECT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
ASSERT_EQ(developer_ids.size(), 0u);
}
}
TEST_F(BackgroundFetchServiceTest,
DevToolsContextReceivesBackgroundFetchEvents) {
// Allow the DevTools Context to log Background Fetch events.
devtools_context()->StartRecording(devtools::proto::BACKGROUND_FETCH);
// Start a fetch and wait for it to complete.
auto* worker =
embedded_worker_test_helper()
->AddNewPendingServiceWorker<BackgroundFetchTestServiceWorker>(
embedded_worker_test_helper());
int64_t service_worker_registration_id = RegisterServiceWorker();
ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId,
service_worker_registration_id);
// base::RunLoop that we'll run until the event has been dispatched. If this
// test times out, it means that the event could not be dispatched.
base::RunLoop event_dispatched_loop;
worker->set_fetched_event_closure(event_dispatched_loop.QuitClosure());
std::vector<blink::mojom::FetchAPIRequestPtr> requests;
requests.push_back(CreateRequestWithProvidedResponse(
"GET", GURL("https://example.com/funny_cat.txt?id=1"),
TestResponseBuilder(200)
.SetResponseData("Cat 1")
.AddResponseHeader("Content-Type", "text/plain")
.AddResponseHeader("X-Cat", "yes")
.Build()));
requests.push_back(CreateRequestWithProvidedResponse(
"GET", GURL("https://example.com/funny_cat.txt?id=2"),
TestResponseBuilder(200)
.SetResponseData("Cat 2")
.AddResponseHeader("Content-Type", "text/plain")
.AddResponseHeader("X-Cat", "yes")
.Build()));
// We expect to receive the following events:
// 1 Registration + 1 Scheduling
// 1 Start + 1 Finish for each request.
// 1 Fetch completion (event dispatch).
EXPECT_CALL(*this, OnEventReceived(_)).Times(7);
// Create the registration with the given |requests|.
blink::mojom::BackgroundFetchRegistrationPtr registration;
{
auto options = blink::mojom::BackgroundFetchOptions::New();
blink::mojom::BackgroundFetchError error;
// Create the first registration. This must succeed.
Fetch(service_worker_registration_id, kExampleDeveloperId,
CloneRequestVector(requests), std::move(options), SkBitmap(), &error,
&registration);
ASSERT_EQ(error, blink::mojom::BackgroundFetchError::NONE);
}
// Spin the |event_dispatched_loop| to wait for the dispatched event.
event_dispatched_loop.Run();
}
} // namespace content