blob: 028b022a040c18d0910c18a6903bc5595532fbb2 [file] [log] [blame]
// Copyright 2013 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_registration.h"
#include <stdint.h>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/compiler_specific.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "content/browser/service_worker/embedded_worker_status.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
#include "content/browser/service_worker/fake_service_worker.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_core_observer.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_provider_host.h"
#include "content/browser/service_worker/service_worker_registration_object_host.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/browser/service_worker/test_service_worker_observer.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/test_content_browser_client.h"
#include "mojo/core/embedder/embedder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
#include "url/gurl.h"
namespace content {
namespace service_worker_registration_unittest {
// From service_worker_registration.cc.
constexpr base::TimeDelta kMaxLameDuckTime = base::TimeDelta::FromMinutes(5);
// TODO(falken): Make this a common helper function.
void StartWorker(ServiceWorkerVersion* version,
ServiceWorkerMetrics::EventType purpose) {
base::RunLoop loop;
blink::ServiceWorkerStatusCode code;
version->StartWorker(
purpose,
base::BindOnce(
[](base::OnceClosure done, blink::ServiceWorkerStatusCode* out_code,
blink::ServiceWorkerStatusCode result_code) {
*out_code = result_code;
std::move(done).Run();
},
loop.QuitClosure(), &code));
loop.Run();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, code);
}
int CreateInflightRequest(ServiceWorkerVersion* version) {
StartWorker(version, ServiceWorkerMetrics::EventType::PUSH);
return version->StartRequest(ServiceWorkerMetrics::EventType::PUSH,
base::DoNothing());
}
static void SaveStatusCallback(bool* called,
blink::ServiceWorkerStatusCode* out,
blink::ServiceWorkerStatusCode status) {
*called = true;
*out = status;
}
class ServiceWorkerTestContentBrowserClient : public TestContentBrowserClient {
public:
bool AllowServiceWorker(
const GURL& scope,
const GURL& first_party,
content::ResourceContext* context,
base::RepeatingCallback<WebContents*()> wc_getter) override {
return false;
}
};
void RequestTermination(
blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtr* host) {
// We can't wait for the callback since Stop() arrives first which severs
// the connection.
(*host)->RequestTermination(base::DoNothing());
}
class MockServiceWorkerRegistrationObject
: public blink::mojom::ServiceWorkerRegistrationObject {
public:
explicit MockServiceWorkerRegistrationObject(
blink::mojom::ServiceWorkerRegistrationObjectAssociatedRequest request)
: binding_(this) {
binding_.Bind(std::move(request));
}
~MockServiceWorkerRegistrationObject() override = default;
int update_found_called_count() const { return update_found_called_count_; }
int set_version_attributes_called_count() const {
return set_version_attributes_called_count_;
}
int set_update_via_cache_called_count() const {
return set_update_via_cache_called_count_;
}
const blink::mojom::ChangedServiceWorkerObjectsMask& changed_mask() const {
return *changed_mask_;
}
const blink::mojom::ServiceWorkerObjectInfoPtr& installing() const {
return installing_;
}
const blink::mojom::ServiceWorkerObjectInfoPtr& waiting() const {
return waiting_;
}
const blink::mojom::ServiceWorkerObjectInfoPtr& active() const {
return active_;
}
blink::mojom::ServiceWorkerUpdateViaCache update_via_cache() const {
return update_via_cache_;
}
private:
// Implements blink::mojom::ServiceWorkerRegistrationObject.
void SetServiceWorkerObjects(
blink::mojom::ChangedServiceWorkerObjectsMaskPtr changed_mask,
blink::mojom::ServiceWorkerObjectInfoPtr installing,
blink::mojom::ServiceWorkerObjectInfoPtr waiting,
blink::mojom::ServiceWorkerObjectInfoPtr active) override {
set_version_attributes_called_count_++;
changed_mask_ = std::move(changed_mask);
installing_ = std::move(installing);
waiting_ = std::move(waiting);
active_ = std::move(active);
}
void SetUpdateViaCache(
blink::mojom::ServiceWorkerUpdateViaCache update_via_cache) override {
set_update_via_cache_called_count_++;
update_via_cache_ = update_via_cache;
}
void UpdateFound() override { update_found_called_count_++; }
int update_found_called_count_ = 0;
int set_version_attributes_called_count_ = 0;
int set_update_via_cache_called_count_ = 0;
blink::mojom::ChangedServiceWorkerObjectsMaskPtr changed_mask_;
blink::mojom::ServiceWorkerObjectInfoPtr installing_;
blink::mojom::ServiceWorkerObjectInfoPtr waiting_;
blink::mojom::ServiceWorkerObjectInfoPtr active_;
blink::mojom::ServiceWorkerUpdateViaCache update_via_cache_ =
blink::mojom::ServiceWorkerUpdateViaCache::kImports;
mojo::AssociatedBinding<blink::mojom::ServiceWorkerRegistrationObject>
binding_;
};
class ServiceWorkerRegistrationTest : public testing::Test {
public:
ServiceWorkerRegistrationTest()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
void SetUp() override {
helper_ = std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath());
context()->storage()->LazyInitializeForTest(base::DoNothing());
base::RunLoop().RunUntilIdle();
}
void TearDown() override {
helper_.reset();
base::RunLoop().RunUntilIdle();
}
ServiceWorkerContextCore* context() { return helper_->context(); }
ServiceWorkerStorage* storage() { return helper_->context()->storage(); }
class RegistrationListener : public ServiceWorkerRegistration::Listener {
public:
RegistrationListener() {}
~RegistrationListener() {
if (observed_registration_.get())
observed_registration_->RemoveListener(this);
}
void OnVersionAttributesChanged(
ServiceWorkerRegistration* registration,
blink::mojom::ChangedServiceWorkerObjectsMaskPtr changed_mask,
const ServiceWorkerRegistrationInfo& info) override {
observed_registration_ = registration;
observed_changed_mask_ = std::move(changed_mask);
observed_info_ = info;
}
void OnRegistrationFailed(
ServiceWorkerRegistration* registration) override {
NOTREACHED();
}
void OnUpdateFound(ServiceWorkerRegistration* registration) override {
NOTREACHED();
}
void Reset() {
observed_registration_ = nullptr;
observed_changed_mask_ = nullptr;
observed_info_ = ServiceWorkerRegistrationInfo();
}
scoped_refptr<ServiceWorkerRegistration> observed_registration_;
blink::mojom::ChangedServiceWorkerObjectsMaskPtr observed_changed_mask_;
ServiceWorkerRegistrationInfo observed_info_;
};
protected:
TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
};
TEST_F(ServiceWorkerRegistrationTest, SetAndUnsetVersions) {
const GURL kScope("http://www.example.not/");
const GURL kScript("http://www.example.not/service_worker.js");
int64_t kRegistrationId = 1L;
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = kScope;
scoped_refptr<ServiceWorkerRegistration> registration =
base::MakeRefCounted<ServiceWorkerRegistration>(options, kRegistrationId,
context()->AsWeakPtr());
const int64_t version_1_id = 1L;
const int64_t version_2_id = 2L;
scoped_refptr<ServiceWorkerVersion> version_1 =
base::MakeRefCounted<ServiceWorkerVersion>(
registration.get(), kScript, blink::mojom::ScriptType::kClassic,
version_1_id, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> version_2 =
base::MakeRefCounted<ServiceWorkerVersion>(
registration.get(), kScript, blink::mojom::ScriptType::kClassic,
version_2_id, context()->AsWeakPtr());
RegistrationListener listener;
registration->AddListener(&listener);
registration->SetActiveVersion(version_1);
EXPECT_EQ(version_1.get(), registration->active_version());
EXPECT_EQ(registration, listener.observed_registration_);
EXPECT_TRUE(listener.observed_changed_mask_->active);
EXPECT_EQ(kScope, listener.observed_info_.scope);
EXPECT_EQ(version_1_id, listener.observed_info_.active_version.version_id);
EXPECT_EQ(kScript, listener.observed_info_.active_version.script_url);
EXPECT_EQ(listener.observed_info_.installing_version.version_id,
blink::mojom::kInvalidServiceWorkerVersionId);
EXPECT_EQ(listener.observed_info_.waiting_version.version_id,
blink::mojom::kInvalidServiceWorkerVersionId);
listener.Reset();
registration->SetInstallingVersion(version_2);
EXPECT_EQ(version_2.get(), registration->installing_version());
EXPECT_TRUE(listener.observed_changed_mask_->installing);
EXPECT_EQ(version_1_id, listener.observed_info_.active_version.version_id);
EXPECT_EQ(version_2_id,
listener.observed_info_.installing_version.version_id);
EXPECT_EQ(listener.observed_info_.waiting_version.version_id,
blink::mojom::kInvalidServiceWorkerVersionId);
listener.Reset();
registration->SetWaitingVersion(version_2);
EXPECT_EQ(version_2.get(), registration->waiting_version());
EXPECT_FALSE(registration->installing_version());
EXPECT_TRUE(listener.observed_changed_mask_->waiting);
EXPECT_TRUE(listener.observed_changed_mask_->installing);
EXPECT_EQ(version_1_id, listener.observed_info_.active_version.version_id);
EXPECT_EQ(version_2_id, listener.observed_info_.waiting_version.version_id);
EXPECT_EQ(listener.observed_info_.installing_version.version_id,
blink::mojom::kInvalidServiceWorkerVersionId);
listener.Reset();
registration->UnsetVersion(version_2.get());
EXPECT_FALSE(registration->waiting_version());
EXPECT_TRUE(listener.observed_changed_mask_->waiting);
EXPECT_EQ(version_1_id, listener.observed_info_.active_version.version_id);
EXPECT_EQ(listener.observed_info_.waiting_version.version_id,
blink::mojom::kInvalidServiceWorkerVersionId);
EXPECT_EQ(listener.observed_info_.installing_version.version_id,
blink::mojom::kInvalidServiceWorkerVersionId);
}
TEST_F(ServiceWorkerRegistrationTest, FailedRegistrationNoCrash) {
const GURL kScope("http://www.example.not/");
int64_t kRegistrationId = 1L;
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = kScope;
auto registration = base::MakeRefCounted<ServiceWorkerRegistration>(
options, kRegistrationId, context()->AsWeakPtr());
// Prepare a ServiceWorkerProviderHost.
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
base::WeakPtr<ServiceWorkerProviderHost> provider_host =
CreateProviderHostForWindow(helper_->mock_render_process_id(),
true /* is_parent_frame_secure */,
context()->AsWeakPtr(), &remote_endpoint);
auto registration_object_host =
std::make_unique<ServiceWorkerRegistrationObjectHost>(
context()->AsWeakPtr(), provider_host.get(), registration);
// To enable the caller end point
// |registration_object_host->remote_registration_| to make calls safely with
// no need to pass |object_info_->request| through a message pipe endpoint.
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr object_info =
registration_object_host->CreateObjectInfo();
mojo::AssociateWithDisconnectedPipe(object_info->request.PassHandle());
registration->NotifyRegistrationFailed();
// Don't crash when |registration_object_host| gets destructed.
}
TEST_F(ServiceWorkerRegistrationTest, NavigationPreload) {
const GURL kScope("http://www.example.not/");
const GURL kScript("https://www.example.not/service_worker.js");
// Setup.
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = kScope;
scoped_refptr<ServiceWorkerRegistration> registration =
base::MakeRefCounted<ServiceWorkerRegistration>(
options, storage()->NewRegistrationId(), context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> version_1 =
base::MakeRefCounted<ServiceWorkerVersion>(
registration.get(), kScript, blink::mojom::ScriptType::kClassic,
storage()->NewVersionId(), context()->AsWeakPtr());
version_1->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
registration->SetActiveVersion(version_1);
version_1->SetStatus(ServiceWorkerVersion::ACTIVATED);
scoped_refptr<ServiceWorkerVersion> version_2 =
base::MakeRefCounted<ServiceWorkerVersion>(
registration.get(), kScript, blink::mojom::ScriptType::kClassic,
storage()->NewVersionId(), context()->AsWeakPtr());
version_2->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
registration->SetWaitingVersion(version_2);
version_2->SetStatus(ServiceWorkerVersion::INSTALLED);
// Navigation preload is disabled by default.
EXPECT_FALSE(version_1->navigation_preload_state().enabled);
// Enabling it sets the flag on the active version.
registration->EnableNavigationPreload(true);
EXPECT_TRUE(version_1->navigation_preload_state().enabled);
// A new active version gets the flag.
registration->SetActiveVersion(version_2);
version_2->SetStatus(ServiceWorkerVersion::ACTIVATING);
EXPECT_TRUE(version_2->navigation_preload_state().enabled);
// Disabling it unsets the flag on the active version.
registration->EnableNavigationPreload(false);
EXPECT_FALSE(version_2->navigation_preload_state().enabled);
}
// Sets up a registration with a waiting worker, and an active worker
// with a controllee and an inflight request.
class ServiceWorkerActivationTest : public ServiceWorkerRegistrationTest,
public testing::WithParamInterface<bool> {
public:
ServiceWorkerActivationTest() : ServiceWorkerRegistrationTest() {}
void SetUp() override {
ServiceWorkerRegistrationTest::SetUp();
const GURL kScope("https://www.example.not/");
const GURL kScript("https://www.example.not/service_worker.js");
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = kScope;
registration_ = base::MakeRefCounted<ServiceWorkerRegistration>(
options, storage()->NewRegistrationId(), context()->AsWeakPtr());
// Create an active version.
scoped_refptr<ServiceWorkerVersion> version_1 =
base::MakeRefCounted<ServiceWorkerVersion>(
registration_.get(), kScript, blink::mojom::ScriptType::kClassic,
storage()->NewVersionId(), context()->AsWeakPtr());
version_1->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
registration_->SetActiveVersion(version_1);
version_1->SetStatus(ServiceWorkerVersion::ACTIVATED);
// Store the registration.
std::vector<ServiceWorkerDatabase::ResourceRecord> records_1;
records_1.push_back(WriteToDiskCacheSync(
helper_->context()->storage(), version_1->script_url(),
helper_->context()->storage()->NewResourceId(), {} /* headers */,
"I'm the body", "I'm the meta data"));
version_1->script_cache_map()->SetResources(records_1);
version_1->SetMainScriptHttpResponseInfo(
EmbeddedWorkerTestHelper::CreateHttpResponseInfo());
base::Optional<blink::ServiceWorkerStatusCode> status;
base::RunLoop run_loop;
context()->storage()->StoreRegistration(
registration_.get(), version_1.get(),
ReceiveServiceWorkerStatus(&status, run_loop.QuitClosure()));
run_loop.Run();
ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
// Give the active version a controllee.
host_ = CreateProviderHostForWindow(
helper_->mock_render_process_id(), true /* is_parent_frame_secure */,
context()->AsWeakPtr(), &remote_endpoint_);
DCHECK(remote_endpoint_.client_request()->is_pending());
DCHECK(remote_endpoint_.host_ptr()->is_bound());
version_1->AddControllee(host_.get());
// Setup the Mojo implementation fakes for the renderer-side service worker.
// These will be bound once the service worker starts.
version_1_client_ =
helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
helper_.get());
version_1_service_worker_ =
helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
// Start the active version and give it an in-flight request.
inflight_request_id_ = CreateInflightRequest(version_1.get());
// Create a waiting version.
scoped_refptr<ServiceWorkerVersion> version_2 =
base::MakeRefCounted<ServiceWorkerVersion>(
registration_.get(), kScript, blink::mojom::ScriptType::kClassic,
storage()->NewVersionId(), context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records_2;
records_2.push_back(WriteToDiskCacheSync(
helper_->context()->storage(), version_2->script_url(),
helper_->context()->storage()->NewResourceId(), {} /* headers */,
"I'm the body", "I'm the meta data"));
version_2->script_cache_map()->SetResources(records_2);
version_2->SetMainScriptHttpResponseInfo(
EmbeddedWorkerTestHelper::CreateHttpResponseInfo());
version_2->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
registration_->SetWaitingVersion(version_2);
// Setup the Mojo implementation fakes for the renderer-side service worker.
// These will be bound once the service worker starts.
version_2_client_ =
helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
helper_.get());
version_2_service_worker_ =
helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
// Start the worker.
StartWorker(version_2.get(), ServiceWorkerMetrics::EventType::INSTALL);
version_2->SetStatus(ServiceWorkerVersion::INSTALLED);
// Set it to activate when ready. The original version should still be
// active.
registration_->ActivateWaitingVersionWhenReady();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(version_1.get(), registration_->active_version());
if (devtools_should_be_attached()) {
// Attach DevTools to the active worker. This shouldn't prevent from
// promoting the waiting worker to active.
version_1->SetDevToolsAttached(true);
}
}
void TearDown() override {
registration_->active_version()->RemoveObserver(registration_.get());
ServiceWorkerRegistrationTest::TearDown();
}
bool devtools_should_be_attached() const { return GetParam(); }
ServiceWorkerRegistration* registration() { return registration_.get(); }
ServiceWorkerProviderHost* controllee() { return host_.get(); }
int inflight_request_id() const { return inflight_request_id_; }
bool IsLameDuckTimerRunning() {
return registration_->lame_duck_timer_.IsRunning();
}
void RunLameDuckTimer() { registration_->RemoveLameDuckIfNeeded(); }
// Simulates skipWaiting(). Note that skipWaiting() might not try to activate
// the worker "immediately", if it can't yet be activated yet. If activation
// is delayed, |out_result| will not be set. If activation is attempted,
// |out_result| is generally true but false in case of a fatal/unexpected
// error like ServiceWorkerContext shutdown.
void SimulateSkipWaiting(ServiceWorkerVersion* version,
base::Optional<bool>* out_result) {
SimulateSkipWaitingWithCallback(version, out_result, base::DoNothing());
}
void SimulateSkipWaitingWithCallback(ServiceWorkerVersion* version,
base::Optional<bool>* out_result,
base::OnceClosure done_callback) {
version->SkipWaiting(base::BindOnce(
[](base::OnceClosure done_callback, base::Optional<bool>* out_result,
bool success) {
*out_result = success;
std::move(done_callback).Run();
},
std::move(done_callback), out_result));
base::RunLoop().RunUntilIdle();
}
FakeEmbeddedWorkerInstanceClient* version_1_client() {
return version_1_client_;
}
FakeEmbeddedWorkerInstanceClient* version_2_client() {
return version_2_client_;
}
FakeServiceWorker* version_1_service_worker() {
return version_1_service_worker_;
}
FakeServiceWorker* version_2_service_worker() {
return version_2_service_worker_;
}
private:
scoped_refptr<ServiceWorkerRegistration> registration_;
// Mojo implementation fakes for the renderer-side service workers. Their
// lifetime is bound to the Mojo connection.
FakeEmbeddedWorkerInstanceClient* version_1_client_ = nullptr;
FakeServiceWorker* version_1_service_worker_ = nullptr;
FakeEmbeddedWorkerInstanceClient* version_2_client_ = nullptr;
FakeServiceWorker* version_2_service_worker_ = nullptr;
base::WeakPtr<ServiceWorkerProviderHost> host_;
ServiceWorkerRemoteProviderEndpoint remote_endpoint_;
int inflight_request_id_ = -1;
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerActivationTest);
};
// Test activation triggered by finishing all requests.
TEST_P(ServiceWorkerActivationTest, NoInflightRequest) {
scoped_refptr<ServiceWorkerRegistration> reg = registration();
scoped_refptr<ServiceWorkerVersion> version_1 = reg->active_version();
scoped_refptr<ServiceWorkerVersion> version_2 = reg->waiting_version();
auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
reg->SetTaskRunnerForTest(runner);
// Remove the controllee. Since there is an in-flight request,
// activation should not yet happen.
version_1->RemoveControllee(controllee()->client_uuid());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(version_1.get(), reg->active_version());
// The idle timer living in the renderer is requested to notify the idle state
// to the browser ASAP.
EXPECT_TRUE(version_1_service_worker()->is_zero_idle_timer_delay());
// Finish the request. Activation should happen.
version_1->FinishRequest(inflight_request_id(), true /* was_handled */);
EXPECT_EQ(version_1.get(), reg->active_version());
RequestTermination(&version_1_client()->host());
TestServiceWorkerObserver observer(helper_->context_wrapper());
observer.RunUntilActivated(version_2.get(), runner);
EXPECT_EQ(version_2.get(), reg->active_version());
}
// Test activation triggered by skipWaiting and finishing requests.
TEST_P(ServiceWorkerActivationTest, SkipWaitingWithInflightRequest) {
scoped_refptr<ServiceWorkerRegistration> reg = registration();
scoped_refptr<ServiceWorkerVersion> version_1 = reg->active_version();
scoped_refptr<ServiceWorkerVersion> version_2 = reg->waiting_version();
base::Optional<bool> result;
base::RunLoop skip_waiting_loop;
// Set skip waiting flag. Since there is still an in-flight request,
// activation should not happen.
SimulateSkipWaitingWithCallback(version_2.get(), &result,
skip_waiting_loop.QuitClosure());
EXPECT_FALSE(result.has_value());
EXPECT_EQ(version_1.get(), reg->active_version());
EXPECT_TRUE(version_1_service_worker()->is_zero_idle_timer_delay());
// Finish the request. FinishRequest() doesn't immediately make the worker
// reach the "no work" state. It needs to be notfied of the idle state by
// RequestTermination().
version_1->FinishRequest(inflight_request_id(), true /* was_handled */);
EXPECT_EQ(version_1.get(), reg->active_version());
RequestTermination(&version_1_client()->host());
// Wait until SkipWaiting resolves.
skip_waiting_loop.Run();
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(*result);
EXPECT_EQ(version_2.get(), reg->active_version());
}
// Test activation triggered by skipWaiting.
TEST_P(ServiceWorkerActivationTest, SkipWaiting) {
scoped_refptr<ServiceWorkerRegistration> reg = registration();
scoped_refptr<ServiceWorkerVersion> version_1 = reg->active_version();
scoped_refptr<ServiceWorkerVersion> version_2 = reg->waiting_version();
// Finish the in-flight request. Since there is a controllee,
// activation should not happen.
version_1->FinishRequest(inflight_request_id(), true /* was_handled */);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(version_1.get(), reg->active_version());
// Call skipWaiting. Activation happens after RequestTermination is triggered.
base::Optional<bool> result;
base::RunLoop skip_waiting_loop;
SimulateSkipWaitingWithCallback(version_2.get(), &result,
skip_waiting_loop.QuitClosure());
EXPECT_TRUE(version_1_service_worker()->is_zero_idle_timer_delay());
EXPECT_FALSE(result.has_value());
EXPECT_EQ(version_1.get(), reg->active_version());
RequestTermination(&version_1_client()->host());
// Wait until SkipWaiting resolves.
skip_waiting_loop.Run();
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(*result);
EXPECT_EQ(version_2.get(), reg->active_version());
}
TEST_P(ServiceWorkerActivationTest, TimeSinceSkipWaiting_Installing) {
scoped_refptr<ServiceWorkerRegistration> reg = registration();
scoped_refptr<ServiceWorkerVersion> version = reg->waiting_version();
base::SimpleTestTickClock clock;
clock.SetNowTicks(base::TimeTicks::Now());
version->SetTickClockForTesting(&clock);
// Reset version to the installing phase.
reg->UnsetVersion(version.get());
version->SetStatus(ServiceWorkerVersion::INSTALLING);
base::Optional<bool> result;
// Call skipWaiting(). The time ticks since skip waiting shouldn't start
// since the version is not yet installed.
SimulateSkipWaiting(version.get(), &result);
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(*result);
clock.Advance(base::TimeDelta::FromSeconds(11));
EXPECT_EQ(base::TimeDelta(), version->TimeSinceSkipWaiting());
// Install the version. Now the skip waiting time starts ticking.
version->SetStatus(ServiceWorkerVersion::INSTALLED);
reg->SetWaitingVersion(version);
base::RunLoop().RunUntilIdle();
clock.Advance(base::TimeDelta::FromSeconds(33));
EXPECT_EQ(base::TimeDelta::FromSeconds(33), version->TimeSinceSkipWaiting());
result.reset();
// Call skipWaiting() again. It doesn't reset the time.
SimulateSkipWaiting(version.get(), &result);
EXPECT_FALSE(result.has_value());
EXPECT_EQ(base::TimeDelta::FromSeconds(33), version->TimeSinceSkipWaiting());
}
// Test lame duck timer triggered by skip waiting.
TEST_P(ServiceWorkerActivationTest, LameDuckTime_SkipWaiting) {
scoped_refptr<ServiceWorkerRegistration> reg = registration();
scoped_refptr<ServiceWorkerVersion> version_1 = reg->active_version();
scoped_refptr<ServiceWorkerVersion> version_2 = reg->waiting_version();
base::SimpleTestTickClock clock_1;
base::SimpleTestTickClock clock_2;
clock_1.SetNowTicks(base::TimeTicks::Now());
clock_2.SetNowTicks(clock_1.NowTicks());
version_1->SetTickClockForTesting(&clock_1);
version_2->SetTickClockForTesting(&clock_2);
base::Optional<bool> result;
// Set skip waiting flag. Since there is still an in-flight request,
// activation should not happen. But the lame duck timer should start.
EXPECT_FALSE(IsLameDuckTimerRunning());
SimulateSkipWaiting(version_2.get(), &result);
EXPECT_FALSE(result.has_value());
EXPECT_EQ(version_1.get(), reg->active_version());
EXPECT_TRUE(IsLameDuckTimerRunning());
// Move forward by lame duck time.
clock_2.Advance(kMaxLameDuckTime + base::TimeDelta::FromSeconds(1));
// Activation should happen by the lame duck timer.
RunLameDuckTimer();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(*result);
EXPECT_EQ(version_2.get(), reg->active_version());
EXPECT_FALSE(IsLameDuckTimerRunning());
}
// Test lame duck timer triggered by loss of controllee.
TEST_P(ServiceWorkerActivationTest, LameDuckTime_NoControllee) {
scoped_refptr<ServiceWorkerRegistration> reg = registration();
scoped_refptr<ServiceWorkerVersion> version_1 = reg->active_version();
scoped_refptr<ServiceWorkerVersion> version_2 = reg->waiting_version();
base::SimpleTestTickClock clock_1;
base::SimpleTestTickClock clock_2;
clock_1.SetNowTicks(base::TimeTicks::Now());
clock_2.SetNowTicks(clock_1.NowTicks());
version_1->SetTickClockForTesting(&clock_1);
version_2->SetTickClockForTesting(&clock_2);
// Remove the controllee. Since there is still an in-flight request,
// activation should not happen. But the lame duck timer should start.
EXPECT_FALSE(IsLameDuckTimerRunning());
version_1->RemoveControllee(controllee()->client_uuid());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(version_1.get(), reg->active_version());
EXPECT_TRUE(IsLameDuckTimerRunning());
// Move clock forward by a little bit.
constexpr base::TimeDelta kLittleBit = base::TimeDelta::FromMinutes(1);
clock_1.Advance(kLittleBit);
// Add a controllee again to reset the lame duck period.
version_1->AddControllee(controllee());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(IsLameDuckTimerRunning());
// Remove the controllee.
version_1->RemoveControllee(controllee()->client_uuid());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(IsLameDuckTimerRunning());
// Move clock forward to the next lame duck timer tick.
clock_1.Advance(kMaxLameDuckTime - kLittleBit +
base::TimeDelta::FromSeconds(1));
// Run the lame duck timer. Activation should not yet happen
// since the lame duck period has not expired.
RunLameDuckTimer();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(version_1.get(), reg->active_version());
EXPECT_TRUE(IsLameDuckTimerRunning());
// Continue on to the next lame duck timer tick.
clock_1.Advance(kMaxLameDuckTime + base::TimeDelta::FromSeconds(1));
// Activation should happen by the lame duck timer.
RunLameDuckTimer();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(version_2.get(), reg->active_version());
EXPECT_FALSE(IsLameDuckTimerRunning());
}
INSTANTIATE_TEST_SUITE_P(ServiceWorkerActivationTestWithDevTools,
ServiceWorkerActivationTest,
testing::Bool());
// Sets up a registration with a ServiceWorkerRegistrationObjectHost to hold it.
class ServiceWorkerRegistrationObjectHostTest
: public ServiceWorkerRegistrationTest {
protected:
void SetUp() override {
ServiceWorkerRegistrationTest::SetUp();
mojo::core::SetDefaultProcessErrorCallback(base::AdaptCallbackForRepeating(
base::BindOnce(&ServiceWorkerRegistrationObjectHostTest::OnMojoError,
base::Unretained(this))));
}
void TearDown() override {
mojo::core::SetDefaultProcessErrorCallback(
mojo::core::ProcessErrorCallback());
ServiceWorkerRegistrationTest::TearDown();
}
blink::mojom::ServiceWorkerErrorType CallUpdate(
blink::mojom::ServiceWorkerRegistrationObjectHost* registration_host) {
blink::mojom::ServiceWorkerErrorType error =
blink::mojom::ServiceWorkerErrorType::kUnknown;
registration_host->Update(base::BindOnce(
[](blink::mojom::ServiceWorkerErrorType* out_error,
blink::mojom::ServiceWorkerErrorType error,
const base::Optional<std::string>& error_msg) {
*out_error = error;
},
&error));
base::RunLoop().RunUntilIdle();
return error;
}
blink::ServiceWorkerStatusCode CallDelayUpdate(
blink::mojom::ServiceWorkerProviderType provider_type,
ServiceWorkerRegistration* registration,
ServiceWorkerVersion* version) {
base::Optional<blink::ServiceWorkerStatusCode> status;
base::RunLoop run_loop;
ServiceWorkerRegistrationObjectHost::DelayUpdate(
blink::mojom::ServiceWorkerProviderType::kForServiceWorker,
registration, version,
base::BindOnce(
[](base::Optional<blink::ServiceWorkerStatusCode>* out_status,
base::OnceClosure callback,
blink::ServiceWorkerStatusCode status) {
*out_status = status;
std::move(callback).Run();
},
&status, run_loop.QuitClosure()));
run_loop.Run();
return status.value();
}
blink::mojom::ServiceWorkerErrorType CallUnregister(
blink::mojom::ServiceWorkerRegistrationObjectHost* registration_host) {
blink::mojom::ServiceWorkerErrorType error =
blink::mojom::ServiceWorkerErrorType::kUnknown;
registration_host->Unregister(base::BindOnce(
[](blink::mojom::ServiceWorkerErrorType* out_error,
blink::mojom::ServiceWorkerErrorType error,
const base::Optional<std::string>& error_msg) {
*out_error = error;
},
&error));
base::RunLoop().RunUntilIdle();
return error;
}
blink::ServiceWorkerStatusCode FindRegistrationInStorage(
int64_t registration_id,
const GURL& scope) {
base::Optional<blink::ServiceWorkerStatusCode> status;
storage()->FindRegistrationForId(
registration_id, scope,
base::AdaptCallbackForRepeating(base::BindOnce(
[](base::Optional<blink::ServiceWorkerStatusCode>* out_status,
blink::ServiceWorkerStatusCode status,
scoped_refptr<ServiceWorkerRegistration> registration) {
*out_status = status;
},
&status)));
base::RunLoop().RunUntilIdle();
return status.value();
}
scoped_refptr<ServiceWorkerRegistration> CreateRegistration(
const GURL& scope) {
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = scope;
return base::MakeRefCounted<ServiceWorkerRegistration>(
options, storage()->NewRegistrationId(), context()->AsWeakPtr());
}
scoped_refptr<ServiceWorkerVersion> CreateVersion(
ServiceWorkerRegistration* registration,
const GURL& script_url) {
scoped_refptr<ServiceWorkerVersion> version =
base::MakeRefCounted<ServiceWorkerVersion>(
registration, script_url, blink::mojom::ScriptType::kClassic,
storage()->NewVersionId(), context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records;
records.push_back(WriteToDiskCacheSync(
storage(), version->script_url(), storage()->NewResourceId(),
{} /* headers */, "I'm the body", "I'm the meta data"));
version->script_cache_map()->SetResources(records);
version->SetMainScriptHttpResponseInfo(
EmbeddedWorkerTestHelper::CreateHttpResponseInfo());
version->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version->SetStatus(ServiceWorkerVersion::INSTALLING);
return version;
}
int64_t SetUpRegistration(const GURL& scope, const GURL& script_url) {
storage()->LazyInitializeForTest(base::DoNothing());
base::RunLoop().RunUntilIdle();
// Prepare ServiceWorkerRegistration and ServiceWorkerVersion.
scoped_refptr<ServiceWorkerRegistration> registration =
CreateRegistration(scope);
scoped_refptr<ServiceWorkerVersion> version =
CreateVersion(registration.get(), script_url);
// Make the registration findable via storage functions.
bool called = false;
blink::ServiceWorkerStatusCode status =
blink::ServiceWorkerStatusCode::kErrorFailed;
storage()->StoreRegistration(
registration.get(), version.get(),
base::BindOnce(&SaveStatusCallback, &called, &status));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status);
return registration->id();
}
ServiceWorkerRemoteProviderEndpoint PrepareProviderHost(
const GURL& document_url,
base::WeakPtr<ServiceWorkerProviderHost>* out_host) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
base::WeakPtr<ServiceWorkerProviderHost> host = CreateProviderHostForWindow(
helper_->mock_render_process_id(), true /* is_parent_frame_secure */,
context()->AsWeakPtr(), &remote_endpoint);
host->UpdateUrls(document_url, document_url);
if (out_host)
*out_host = host;
return remote_endpoint;
}
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr
GetRegistrationFromRemote(
blink::mojom::ServiceWorkerContainerHost* container_host,
const GURL& url) {
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr registration_info;
container_host->GetRegistration(
url, base::BindOnce(
[](blink::mojom::ServiceWorkerRegistrationObjectInfoPtr*
out_registration_info,
blink::mojom::ServiceWorkerErrorType error,
const base::Optional<std::string>& error_msg,
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr
registration) {
ASSERT_EQ(blink::mojom::ServiceWorkerErrorType::kNone,
error);
*out_registration_info = std::move(registration);
},
&registration_info));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(registration_info->host_ptr_info.is_valid());
return registration_info;
}
void OnMojoError(const std::string& error) { bad_messages_.push_back(error); }
std::vector<std::string> bad_messages_;
};
TEST_F(ServiceWorkerRegistrationObjectHostTest, BreakConnection_Destroy) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
int64_t registration_id = SetUpRegistration(kScope, kScriptUrl);
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, nullptr /* out_host */);
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedPtr
registration_host_ptr;
registration_host_ptr.Bind(std::move(info->host_ptr_info));
EXPECT_NE(nullptr, context()->GetLiveRegistration(registration_id));
registration_host_ptr.reset();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, context()->GetLiveRegistration(registration_id));
}
TEST_F(ServiceWorkerRegistrationObjectHostTest, Update_Success) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
SetUpRegistration(kScope, kScriptUrl);
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, nullptr /* out_host */);
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedPtr
registration_host_ptr;
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
registration_host_ptr.Bind(std::move(info->host_ptr_info));
// Ignore the messages to the registration object, otherwise the callbacks
// issued from |registration_host_ptr| may wait for receiving the messages to
// |info->request|.
info->request = nullptr;
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kNone,
CallUpdate(registration_host_ptr.get()));
}
TEST_F(ServiceWorkerRegistrationObjectHostTest, Update_CrossOriginShouldFail) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
SetUpRegistration(kScope, kScriptUrl);
base::WeakPtr<ServiceWorkerProviderHost> provider_host;
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, &provider_host);
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedPtr
registration_host_ptr;
registration_host_ptr.Bind(std::move(info->host_ptr_info));
ASSERT_TRUE(bad_messages_.empty());
GURL url("https://does.not.exist/");
provider_host->UpdateUrls(url, url);
CallUpdate(registration_host_ptr.get());
EXPECT_EQ(1u, bad_messages_.size());
}
TEST_F(ServiceWorkerRegistrationObjectHostTest,
Update_ContentSettingsDisallowsServiceWorker) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
SetUpRegistration(kScope, kScriptUrl);
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, nullptr /* out_host */);
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedPtr
registration_host_ptr;
registration_host_ptr.Bind(std::move(info->host_ptr_info));
ServiceWorkerTestContentBrowserClient test_browser_client;
ContentBrowserClient* old_browser_client =
SetBrowserClientForTesting(&test_browser_client);
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kDisabled,
CallUpdate(registration_host_ptr.get()));
SetBrowserClientForTesting(old_browser_client);
}
TEST_F(ServiceWorkerRegistrationObjectHostTest, Update_NoDelayFromControllee) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
int64_t registration_id = SetUpRegistration(kScope, kScriptUrl);
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, nullptr /* out_host */);
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedPtr
registration_host_ptr;
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
registration_host_ptr.Bind(std::move(info->host_ptr_info));
// Ignore the messages to the registration object, otherwise the callbacks
// issued from |registration_host_ptr| may wait for receiving the messages to
// |info->request|.
info->request = nullptr;
// Get registration and set |self_update_delay| to zero.
ServiceWorkerRegistration* registration =
context()->GetLiveRegistration(registration_id);
ASSERT_TRUE(registration);
registration->set_self_update_delay(base::TimeDelta());
EXPECT_EQ(base::TimeDelta(), registration->self_update_delay());
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kNone,
CallUpdate(registration_host_ptr.get()));
EXPECT_EQ(base::TimeDelta(), registration->self_update_delay());
}
TEST_F(ServiceWorkerRegistrationObjectHostTest,
Update_DelayFromWorkerWithoutControllee) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
scoped_refptr<ServiceWorkerRegistration> registration =
CreateRegistration(kScope);
scoped_refptr<ServiceWorkerVersion> version =
CreateVersion(registration.get(), kScriptUrl);
// Initially set |self_update_delay| to zero.
registration->set_self_update_delay(base::TimeDelta());
EXPECT_EQ(base::TimeDelta(), registration->self_update_delay());
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
CallDelayUpdate(
blink::mojom::ServiceWorkerProviderType::kForServiceWorker,
registration.get(), version.get()));
EXPECT_LT(base::TimeDelta(), registration->self_update_delay());
// TODO(falken): Add a test verifying that a delayed update will be executed
// eventually.
// Set |self_update_delay| to a time so that update() will reject immediately.
registration->set_self_update_delay(base::TimeDelta::FromMinutes(5));
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout,
CallDelayUpdate(
blink::mojom::ServiceWorkerProviderType::kForServiceWorker,
registration.get(), version.get()));
EXPECT_LE(base::TimeDelta::FromMinutes(5), registration->self_update_delay());
}
TEST_F(ServiceWorkerRegistrationObjectHostTest,
Update_NoDelayFromWorkerWithControllee) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
scoped_refptr<ServiceWorkerRegistration> registration =
CreateRegistration(kScope);
scoped_refptr<ServiceWorkerVersion> version =
CreateVersion(registration.get(), kScriptUrl);
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
base::WeakPtr<ServiceWorkerProviderHost> host = CreateProviderHostForWindow(
helper_->mock_render_process_id(), true /* is_parent_frame_secure */,
context()->AsWeakPtr(), &remote_endpoint);
host->UpdateUrls(kScope, kScope);
version->AddControllee(host.get());
// Initially set |self_update_delay| to zero.
registration->set_self_update_delay(base::TimeDelta());
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
CallDelayUpdate(
blink::mojom::ServiceWorkerProviderType::kForServiceWorker,
registration.get(), version.get()));
EXPECT_EQ(base::TimeDelta(), registration->self_update_delay());
// Set |self_update_delay| to a time so that update() will reject immediately
// if the worker doesn't have at least one controlee.
registration->set_self_update_delay(base::TimeDelta::FromMinutes(5));
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
CallDelayUpdate(
blink::mojom::ServiceWorkerProviderType::kForServiceWorker,
registration.get(), version.get()));
EXPECT_EQ(base::TimeDelta::FromMinutes(5), registration->self_update_delay());
}
TEST_F(ServiceWorkerRegistrationObjectHostTest, Unregister_Success) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
int64_t registration_id = SetUpRegistration(kScope, kScriptUrl);
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, nullptr /* out_host */);
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedPtr
registration_host_ptr;
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
registration_host_ptr.Bind(std::move(info->host_ptr_info));
// Ignore the messages to the registration object and corresponding service
// worker objects, otherwise the callbacks issued from |registration_host_ptr|
// may wait for receiving the messages to them.
info->request = nullptr;
info->waiting->request = nullptr;
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
FindRegistrationInStorage(registration_id, kScope));
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kNone,
CallUnregister(registration_host_ptr.get()));
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorNotFound,
FindRegistrationInStorage(registration_id, kScope));
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kNotFound,
CallUnregister(registration_host_ptr.get()));
}
TEST_F(ServiceWorkerRegistrationObjectHostTest,
Unregister_CrossOriginShouldFail) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
SetUpRegistration(kScope, kScriptUrl);
base::WeakPtr<ServiceWorkerProviderHost> provider_host;
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, &provider_host);
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedPtr
registration_host_ptr;
registration_host_ptr.Bind(std::move(info->host_ptr_info));
ASSERT_TRUE(bad_messages_.empty());
provider_host->UpdateUrls(GURL("https://does.not.exist/"),
GURL("https://does.not.exist/"));
CallUnregister(registration_host_ptr.get());
EXPECT_EQ(1u, bad_messages_.size());
}
TEST_F(ServiceWorkerRegistrationObjectHostTest,
Unregister_ContentSettingsDisallowsServiceWorker) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
SetUpRegistration(kScope, kScriptUrl);
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, nullptr /* out_host */);
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedPtr
registration_host_ptr;
registration_host_ptr.Bind(std::move(info->host_ptr_info));
ServiceWorkerTestContentBrowserClient test_browser_client;
ContentBrowserClient* old_browser_client =
SetBrowserClientForTesting(&test_browser_client);
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kDisabled,
CallUnregister(registration_host_ptr.get()));
SetBrowserClientForTesting(old_browser_client);
}
TEST_F(ServiceWorkerRegistrationObjectHostTest, SetVersionAttributes) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
int64_t registration_id = SetUpRegistration(kScope, kScriptUrl);
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, nullptr /* out_host */);
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
EXPECT_EQ(registration_id, info->registration_id);
EXPECT_TRUE(info->request.is_pending());
auto mock_registration_object =
std::make_unique<MockServiceWorkerRegistrationObject>(
std::move(info->request));
ServiceWorkerRegistration* registration =
context()->GetLiveRegistration(registration_id);
ASSERT_NE(nullptr, registration);
const int64_t version_1_id = 1L;
const int64_t version_2_id = 2L;
scoped_refptr<ServiceWorkerVersion> version_1 =
base::MakeRefCounted<ServiceWorkerVersion>(
registration, kScriptUrl, blink::mojom::ScriptType::kClassic,
version_1_id, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> version_2 =
base::MakeRefCounted<ServiceWorkerVersion>(
registration, kScriptUrl, blink::mojom::ScriptType::kClassic,
version_2_id, context()->AsWeakPtr());
// Set an active worker.
registration->SetActiveVersion(version_1);
EXPECT_EQ(version_1.get(), registration->active_version());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mock_registration_object->set_version_attributes_called_count());
EXPECT_FALSE(mock_registration_object->changed_mask().installing);
EXPECT_FALSE(mock_registration_object->installing());
EXPECT_FALSE(mock_registration_object->changed_mask().waiting);
EXPECT_FALSE(mock_registration_object->waiting());
EXPECT_TRUE(mock_registration_object->changed_mask().active);
EXPECT_TRUE(mock_registration_object->active());
EXPECT_EQ(version_1_id, mock_registration_object->active()->version_id);
EXPECT_EQ(kScriptUrl, mock_registration_object->active()->url);
// Set an installing worker.
registration->SetInstallingVersion(version_2);
EXPECT_EQ(version_2.get(), registration->installing_version());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2, mock_registration_object->set_version_attributes_called_count());
EXPECT_TRUE(mock_registration_object->changed_mask().installing);
EXPECT_TRUE(mock_registration_object->installing());
EXPECT_FALSE(mock_registration_object->changed_mask().waiting);
EXPECT_FALSE(mock_registration_object->waiting());
EXPECT_FALSE(mock_registration_object->changed_mask().active);
EXPECT_FALSE(mock_registration_object->active());
EXPECT_EQ(version_2_id, mock_registration_object->installing()->version_id);
EXPECT_EQ(kScriptUrl, mock_registration_object->installing()->url);
// Promote the installing worker to waiting.
registration->SetWaitingVersion(version_2);
EXPECT_EQ(version_2.get(), registration->waiting_version());
EXPECT_FALSE(registration->installing_version());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(3, mock_registration_object->set_version_attributes_called_count());
EXPECT_TRUE(mock_registration_object->changed_mask().installing);
EXPECT_FALSE(mock_registration_object->installing());
EXPECT_TRUE(mock_registration_object->changed_mask().waiting);
EXPECT_TRUE(mock_registration_object->waiting());
EXPECT_FALSE(mock_registration_object->changed_mask().active);
EXPECT_FALSE(mock_registration_object->active());
EXPECT_EQ(version_2_id, mock_registration_object->waiting()->version_id);
EXPECT_EQ(kScriptUrl, mock_registration_object->waiting()->url);
// Remove the waiting worker.
registration->UnsetVersion(version_2.get());
EXPECT_FALSE(registration->waiting_version());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(4, mock_registration_object->set_version_attributes_called_count());
EXPECT_FALSE(mock_registration_object->changed_mask().installing);
EXPECT_FALSE(mock_registration_object->installing());
EXPECT_TRUE(mock_registration_object->changed_mask().waiting);
EXPECT_FALSE(mock_registration_object->waiting());
EXPECT_FALSE(mock_registration_object->changed_mask().active);
EXPECT_FALSE(mock_registration_object->active());
}
TEST_F(ServiceWorkerRegistrationObjectHostTest, SetUpdateViaCache) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
int64_t registration_id = SetUpRegistration(kScope, kScriptUrl);
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, nullptr /* out_host */);
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
EXPECT_EQ(registration_id, info->registration_id);
EXPECT_TRUE(info->request.is_pending());
auto mock_registration_object =
std::make_unique<MockServiceWorkerRegistrationObject>(
std::move(info->request));
ServiceWorkerRegistration* registration =
context()->GetLiveRegistration(registration_id);
ASSERT_EQ(blink::mojom::ServiceWorkerUpdateViaCache::kImports,
registration->update_via_cache());
ASSERT_EQ(0, mock_registration_object->set_update_via_cache_called_count());
ASSERT_EQ(blink::mojom::ServiceWorkerUpdateViaCache::kImports,
mock_registration_object->update_via_cache());
registration->SetUpdateViaCache(
blink::mojom::ServiceWorkerUpdateViaCache::kImports);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, mock_registration_object->set_update_via_cache_called_count());
EXPECT_EQ(blink::mojom::ServiceWorkerUpdateViaCache::kImports,
mock_registration_object->update_via_cache());
registration->SetUpdateViaCache(
blink::mojom::ServiceWorkerUpdateViaCache::kAll);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mock_registration_object->set_update_via_cache_called_count());
EXPECT_EQ(blink::mojom::ServiceWorkerUpdateViaCache::kAll,
mock_registration_object->update_via_cache());
registration->SetUpdateViaCache(
blink::mojom::ServiceWorkerUpdateViaCache::kNone);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2, mock_registration_object->set_update_via_cache_called_count());
EXPECT_EQ(blink::mojom::ServiceWorkerUpdateViaCache::kNone,
mock_registration_object->update_via_cache());
registration->SetUpdateViaCache(
blink::mojom::ServiceWorkerUpdateViaCache::kImports);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(3, mock_registration_object->set_update_via_cache_called_count());
EXPECT_EQ(blink::mojom::ServiceWorkerUpdateViaCache::kImports,
mock_registration_object->update_via_cache());
}
TEST_F(ServiceWorkerRegistrationObjectHostTest, UpdateFound) {
const GURL kScope("https://www.example.com/");
const GURL kScriptUrl("https://www.example.com/sw.js");
int64_t registration_id = SetUpRegistration(kScope, kScriptUrl);
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareProviderHost(kScope, nullptr /* out_host */);
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info =
GetRegistrationFromRemote(remote_endpoint.host_ptr()->get(), kScope);
EXPECT_EQ(registration_id, info->registration_id);
EXPECT_TRUE(info->request.is_pending());
auto mock_registration_object =
std::make_unique<MockServiceWorkerRegistrationObject>(
std::move(info->request));
ServiceWorkerRegistration* registration =
context()->GetLiveRegistration(registration_id);
ASSERT_NE(nullptr, registration);
EXPECT_EQ(0, mock_registration_object->update_found_called_count());
registration->NotifyUpdateFound();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mock_registration_object->update_found_called_count());
}
} // namespace service_worker_registration_unittest
} // namespace content