blob: 03b8dd1c932eb1f0d39473d4a2eeb986de489c5b [file] [log] [blame]
// Copyright 2014 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_version.h"
#include <stdint.h>
#include <memory>
#include <tuple>
#include <utility>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h"
#include "content/browser/service_worker/embedded_worker_registry.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/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_dispatcher_host.h"
#include "content/browser/service_worker/service_worker_ping_controller.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_service.mojom.h"
#include "content/public/test/test_utils.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/service_worker/service_worker_utils.h"
#include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
namespace content {
namespace service_worker_version_unittest {
class MessageReceiver : public EmbeddedWorkerTestHelper {
public:
MessageReceiver() : EmbeddedWorkerTestHelper(base::FilePath()) {}
~MessageReceiver() override {}
void SimulateSetCachedMetadata(int embedded_worker_id,
const GURL& url,
const std::vector<uint8_t>& data) {
GetServiceWorkerHost(embedded_worker_id)->SetCachedMetadata(url, data);
}
void SimulateClearCachedMetadata(int embedded_worker_id, const GURL& url) {
GetServiceWorkerHost(embedded_worker_id)->ClearCachedMetadata(url);
}
DISALLOW_COPY_AND_ASSIGN(MessageReceiver);
};
void VerifyCalled(bool* called) {
*called = true;
}
void ObserveStatusChanges(ServiceWorkerVersion* version,
std::vector<ServiceWorkerVersion::Status>* statuses) {
statuses->push_back(version->status());
version->RegisterStatusChangeCallback(base::BindOnce(
&ObserveStatusChanges, base::Unretained(version), statuses));
}
base::Time GetYesterday() {
return base::Time::Now() - base::TimeDelta::FromDays(1) -
base::TimeDelta::FromSeconds(1);
}
class TestServiceImpl : public mojom::TestService {
public:
static void Create(mojo::InterfaceRequest<mojom::TestService> request) {
mojo::MakeStrongBinding(base::WrapUnique(new TestServiceImpl),
std::move(request));
}
void DoSomething(DoSomethingCallback callback) override {
std::move(callback).Run();
}
void DoTerminateProcess(DoTerminateProcessCallback callback) override {
NOTREACHED();
}
void DoCrashImmediately(DoCrashImmediatelyCallback callback) override {
NOTREACHED();
}
void CreateFolder(CreateFolderCallback callback) override { NOTREACHED(); }
void GetRequestorName(GetRequestorNameCallback callback) override {
std::move(callback).Run("");
}
void CreateSharedBuffer(const std::string& message,
CreateSharedBufferCallback callback) override {
NOTREACHED();
}
private:
TestServiceImpl() {}
};
void StartWorker(ServiceWorkerVersion* version,
ServiceWorkerMetrics::EventType purpose) {
base::Optional<blink::ServiceWorkerStatusCode> status;
version->StartWorker(purpose, CreateReceiverOnCurrentThread(&status));
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, version->running_status());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version->running_status());
}
class ServiceWorkerVersionTest : public testing::Test {
protected:
struct RunningStateListener : public ServiceWorkerVersion::Observer {
RunningStateListener() : last_status(EmbeddedWorkerStatus::STOPPED) {}
~RunningStateListener() override {}
void OnRunningStateChanged(ServiceWorkerVersion* version) override {
last_status = version->running_status();
}
EmbeddedWorkerStatus last_status;
};
struct CachedMetadataUpdateListener : public ServiceWorkerVersion::Observer {
CachedMetadataUpdateListener() = default;
~CachedMetadataUpdateListener() override = default;
void OnCachedMetadataUpdated(ServiceWorkerVersion* version,
size_t size) override {
++updated_count;
}
int updated_count = 0;
};
ServiceWorkerVersionTest()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
void SetUp() override {
helper_ = GetMessageReceiver();
helper_->context()->storage()->LazyInitializeForTest(base::DoNothing());
base::RunLoop().RunUntilIdle();
scope_ = GURL("https://www.example.com/test/");
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = scope_;
registration_ = new ServiceWorkerRegistration(
options, helper_->context()->storage()->NewRegistrationId(),
helper_->context()->AsWeakPtr());
version_ = new ServiceWorkerVersion(
registration_.get(),
GURL("https://www.example.com/test/service_worker.js"),
blink::mojom::ScriptType::kClassic,
helper_->context()->storage()->NewVersionId(),
helper_->context()->AsWeakPtr());
EXPECT_EQ(url::Origin::Create(scope_), version_->script_origin());
std::vector<ServiceWorkerDatabase::ResourceRecord> records;
records.push_back(WriteToDiskCacheSync(
helper_->context()->storage(), version_->script_url(), 10,
{} /* headers */, "I'm a body", "I'm a meta data"));
version_->script_cache_map()->SetResources(records);
version_->SetMainScriptHttpResponseInfo(
EmbeddedWorkerTestHelper::CreateHttpResponseInfo());
version_->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
// Make the registration findable via storage functions.
base::Optional<blink::ServiceWorkerStatusCode> status;
helper_->context()->storage()->StoreRegistration(
registration_.get(),
version_.get(),
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
}
virtual std::unique_ptr<MessageReceiver> GetMessageReceiver() {
return std::make_unique<MessageReceiver>();
}
void TearDown() override {
version_ = nullptr;
registration_ = nullptr;
helper_.reset();
}
bool IsPingActivated(ServiceWorkerVersion* version) const {
return version->ping_controller_.IsActivated();
}
void NotifyScriptEvaluationStart(ServiceWorkerVersion* version) {
version->OnScriptEvaluationStart();
}
void SimulateDispatchEvent(ServiceWorkerMetrics::EventType event_type) {
base::Optional<blink::ServiceWorkerStatusCode> status;
// Make sure worker is running.
version_->RunAfterStartWorker(event_type,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// Start request, as if an event is being dispatched.
int request_id = version_->StartRequest(
event_type, CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
// And finish request, as if a response to the event was received.
EXPECT_TRUE(version_->FinishRequest(request_id, true /* was_handled */));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
}
void SetTickClockForTesting(base::SimpleTestTickClock* tick_clock) {
version_->SetTickClockForTesting(tick_clock);
}
TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<MessageReceiver> helper_;
scoped_refptr<ServiceWorkerRegistration> registration_;
scoped_refptr<ServiceWorkerVersion> version_;
GURL scope_;
private:
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerVersionTest);
};
class MessageReceiverDisallowStart : public MessageReceiver {
public:
MessageReceiverDisallowStart() : MessageReceiver() {}
~MessageReceiverDisallowStart() override {}
enum class StartMode { STALL, FAIL, SUCCEED };
void OnStartWorker(
int embedded_worker_id,
int64_t service_worker_version_id,
const GURL& scope,
const GURL& script_url,
bool pause_after_download,
blink::mojom::ServiceWorkerRequest service_worker_request,
blink::mojom::ControllerServiceWorkerRequest controller_request,
mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
override {
switch (mode_) {
case StartMode::STALL:
// Prepare for OnStopWorker().
instance_host_ptr_map_[embedded_worker_id].Bind(
std::move(instance_host));
// Just keep the connection alive.
service_worker_request_map_[embedded_worker_id] =
std::move(service_worker_request);
controller_request_map_[embedded_worker_id] =
std::move(controller_request);
break;
case StartMode::FAIL:
ASSERT_EQ(current_mock_instance_index_ + 1,
mock_instance_clients()->size());
// Remove the connection by peer
mock_instance_clients()->at(current_mock_instance_index_).reset();
break;
case StartMode::SUCCEED:
MessageReceiver::OnStartWorker(
embedded_worker_id, service_worker_version_id, scope, script_url,
pause_after_download, std::move(service_worker_request),
std::move(controller_request), std::move(instance_host),
std::move(provider_info), std::move(installed_scripts_info));
break;
}
current_mock_instance_index_++;
}
void OnStopWorker(int embedded_worker_id) override {
if (instance_host_ptr_map_[embedded_worker_id]) {
instance_host_ptr_map_[embedded_worker_id]->OnStopped();
base::RunLoop().RunUntilIdle();
return;
}
EmbeddedWorkerTestHelper::OnStopWorker(embedded_worker_id);
}
void set_start_mode(StartMode mode) { mode_ = mode; }
private:
uint32_t current_mock_instance_index_ = 0;
StartMode mode_ = StartMode::STALL;
std::map<
int /* embedded_worker_id */,
mojom::EmbeddedWorkerInstanceHostAssociatedPtr /* instance_host_ptr */>
instance_host_ptr_map_;
std::map<int /* embedded_worker_id */, blink::mojom::ServiceWorkerRequest>
service_worker_request_map_;
std::map<int /* embedded_worker_id */,
blink::mojom::ControllerServiceWorkerRequest>
controller_request_map_;
DISALLOW_COPY_AND_ASSIGN(MessageReceiverDisallowStart);
};
class ServiceWorkerFailToStartTest : public ServiceWorkerVersionTest {
protected:
ServiceWorkerFailToStartTest() : ServiceWorkerVersionTest() {}
void set_start_mode(MessageReceiverDisallowStart::StartMode mode) {
MessageReceiverDisallowStart* helper =
static_cast<MessageReceiverDisallowStart*>(helper_.get());
helper->set_start_mode(mode);
}
std::unique_ptr<MessageReceiver> GetMessageReceiver() override {
return std::make_unique<MessageReceiverDisallowStart>();
}
private:
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerFailToStartTest);
};
class NoOpStopWorkerEmbeddedWorkerInstanceClient
: public EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient {
public:
explicit NoOpStopWorkerEmbeddedWorkerInstanceClient(
base::WeakPtr<EmbeddedWorkerTestHelper> helper)
: EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient(helper) {}
~NoOpStopWorkerEmbeddedWorkerInstanceClient() override {
}
protected:
void StopWorker() override {
// Do nothing.
}
private:
DISALLOW_COPY_AND_ASSIGN(NoOpStopWorkerEmbeddedWorkerInstanceClient);
};
class MessageReceiverDisallowStop : public MessageReceiver {
public:
MessageReceiverDisallowStop() : MessageReceiver() {
CreateAndRegisterMockInstanceClient<
NoOpStopWorkerEmbeddedWorkerInstanceClient>(AsWeakPtr());
}
~MessageReceiverDisallowStop() override {}
void OnStopWorker(int embedded_worker_id) override {
// Do nothing.
}
private:
DISALLOW_COPY_AND_ASSIGN(MessageReceiverDisallowStop);
};
class ServiceWorkerStallInStoppingTest : public ServiceWorkerVersionTest {
protected:
ServiceWorkerStallInStoppingTest() : ServiceWorkerVersionTest() {}
std::unique_ptr<MessageReceiver> GetMessageReceiver() override {
return std::make_unique<MessageReceiverDisallowStop>();
}
private:
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerStallInStoppingTest);
};
TEST_F(ServiceWorkerVersionTest, ConcurrentStartAndStop) {
// Call StartWorker() multiple times.
base::Optional<blink::ServiceWorkerStatusCode> status1;
base::Optional<blink::ServiceWorkerStatusCode> status2;
base::Optional<blink::ServiceWorkerStatusCode> status3;
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status1));
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status2));
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, version_->running_status());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// Call StartWorker() after it's started.
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status3));
base::RunLoop().RunUntilIdle();
// All should just succeed.
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status1.value());
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status2.value());
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status3.value());
{
// Call StopWorker() multiple times.
bool has_stopped1 = false;
bool has_stopped2 = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped1));
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped2));
EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, version_->running_status());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
// All StopWorker should just succeed.
EXPECT_TRUE(has_stopped1);
EXPECT_TRUE(has_stopped2);
}
// Start worker again.
status1.reset();
status2.reset();
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status1));
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, version_->running_status());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
{
// Call StopWorker()
bool has_stopped = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
// And try calling StartWorker while StopWorker is in queue.
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status2));
EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, version_->running_status());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// All should just succeed.
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status1.value());
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status2.value());
EXPECT_TRUE(has_stopped);
}
}
TEST_F(ServiceWorkerVersionTest, DispatchEventToStoppedWorker) {
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
// Dispatch an event without starting the worker.
version_->SetStatus(ServiceWorkerVersion::INSTALLING);
EXPECT_TRUE(version_->HasNoWork());
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::INSTALL);
if (blink::ServiceWorkerUtils::IsServicificationEnabled()) {
// The worker may still be handling events dispatched directly from
// controllees. We cannot say the version doesn't handle any tasks until the
// worker reports "No Work" (= ServiceWorkerVersion::OnRequestTermination()
// is called).
EXPECT_FALSE(version_->HasNoWork());
} else {
// In non-S13nServiceWorker case, ServiceWorkerVersion manages all of events
// dispatched to the service worker. Once all events have finished in the
// browser, the version should have no work.
EXPECT_TRUE(version_->HasNoWork());
}
// The worker should be now started.
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// Stop the worker, and then dispatch an event immediately after that.
bool has_stopped = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::INSTALL);
EXPECT_TRUE(has_stopped);
if (blink::ServiceWorkerUtils::IsServicificationEnabled()) {
// The worker may still be handling events dispatched directly from
// controllees. We cannot say the version doesn't handle any tasks until the
// worker reports "No Work" (= ServiceWorkerVersion::OnRequestTermination()
// is called).
EXPECT_FALSE(version_->HasNoWork());
} else {
// In non-S13nServiceWorker case, ServiceWorkerVersion manages all of events
// dispatched to the service worker. Once all events have finished in the
// browser, the version should have no work.
EXPECT_TRUE(version_->HasNoWork());
}
// The worker should be now started again.
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
}
TEST_F(ServiceWorkerVersionTest, StartUnregisteredButStillLiveWorker) {
// Start the worker.
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
// Delete the registration.
base::Optional<blink::ServiceWorkerStatusCode> status;
helper_->context()->storage()->DeleteRegistration(
registration_->id(), registration_->scope().GetOrigin(),
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
// The live registration is marked as deleted, but still exists.
ASSERT_TRUE(registration_->is_deleted());
// Stop the worker.
bool has_stopped = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(has_stopped);
// Dispatch an event on the unregistered and stopped but still live worker.
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME);
// The worker should be now started again.
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
}
TEST_F(ServiceWorkerVersionTest, InstallAndWaitCompletion) {
version_->SetStatus(ServiceWorkerVersion::INSTALLING);
// Wait for the completion.
bool status_change_called = false;
version_->RegisterStatusChangeCallback(
base::BindOnce(&VerifyCalled, &status_change_called));
// Dispatch an install event.
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::INSTALL);
// Version's status must not have changed during installation.
EXPECT_FALSE(status_change_called);
EXPECT_EQ(ServiceWorkerVersion::INSTALLING, version_->status());
}
TEST_F(ServiceWorkerVersionTest, ActivateAndWaitCompletion) {
// TODO(mek): This test (and the one above for the install event) made more
// sense back when ServiceWorkerVersion was responsible for updating the
// status. Now a better version of this test should probably be added to
// ServiceWorkerRegistrationTest instead.
version_->SetStatus(ServiceWorkerVersion::ACTIVATING);
// Wait for the completion.
bool status_change_called = false;
version_->RegisterStatusChangeCallback(
base::BindOnce(&VerifyCalled, &status_change_called));
// Dispatch an activate event.
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::ACTIVATE);
// Version's status must not have changed during activation.
EXPECT_FALSE(status_change_called);
EXPECT_EQ(ServiceWorkerVersion::ACTIVATING, version_->status());
}
TEST_F(ServiceWorkerVersionTest, RepeatedlyObserveStatusChanges) {
EXPECT_EQ(ServiceWorkerVersion::NEW, version_->status());
// Repeatedly observe status changes (the callback re-registers itself).
std::vector<ServiceWorkerVersion::Status> statuses;
version_->RegisterStatusChangeCallback(base::BindOnce(
&ObserveStatusChanges, base::RetainedRef(version_), &statuses));
version_->SetStatus(ServiceWorkerVersion::INSTALLING);
version_->SetStatus(ServiceWorkerVersion::INSTALLED);
version_->SetStatus(ServiceWorkerVersion::ACTIVATING);
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
version_->SetStatus(ServiceWorkerVersion::REDUNDANT);
// Verify that we could successfully observe repeated status changes.
ASSERT_EQ(5U, statuses.size());
ASSERT_EQ(ServiceWorkerVersion::INSTALLING, statuses[0]);
ASSERT_EQ(ServiceWorkerVersion::INSTALLED, statuses[1]);
ASSERT_EQ(ServiceWorkerVersion::ACTIVATING, statuses[2]);
ASSERT_EQ(ServiceWorkerVersion::ACTIVATED, statuses[3]);
ASSERT_EQ(ServiceWorkerVersion::REDUNDANT, statuses[4]);
}
TEST_F(ServiceWorkerVersionTest, Doom) {
// Add a controllee.
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration_->SetActiveVersion(version_);
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
std::unique_ptr<ServiceWorkerProviderHost> host = CreateProviderHostForWindow(
33 /* dummy render process id */, 1 /* dummy provider_id */,
true /* is_parent_frame_secure */, helper_->context()->AsWeakPtr(),
&remote_endpoint);
host->UpdateUrls(registration_->scope(), registration_->scope());
host->SetControllerRegistration(registration_, false);
EXPECT_TRUE(version_->HasControllee());
EXPECT_TRUE(host->controller());
// Doom the version.
version_->Doom();
// The controllee should have been removed.
EXPECT_EQ(ServiceWorkerVersion::REDUNDANT, version_->status());
EXPECT_FALSE(version_->HasControllee());
EXPECT_FALSE(host->controller());
}
TEST_F(ServiceWorkerVersionTest, IdleTimeout) {
// Used to reliably test when the idle time gets reset regardless of clock
// granularity.
const base::TimeDelta kOneSecond = base::TimeDelta::FromSeconds(1);
// Verify the timer is not running when version initializes its status.
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
EXPECT_FALSE(version_->timeout_timer_.IsRunning());
// Verify the timer is running after the worker is started.
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
EXPECT_FALSE(version_->idle_time_.is_null());
// The idle time should be reset if the worker is restarted without
// controllee.
bool has_stopped = false;
version_->idle_time_ -= kOneSecond;
base::TimeTicks idle_time = version_->idle_time_;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(has_stopped);
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
EXPECT_LT(idle_time, version_->idle_time_);
// Adding a controllee resets the idle time.
version_->idle_time_ -= kOneSecond;
idle_time = version_->idle_time_;
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
std::unique_ptr<ServiceWorkerProviderHost> host = CreateProviderHostForWindow(
33 /* dummy render process id */, 1 /* dummy provider_id */,
true /* is_parent_frame_secure */, helper_->context()->AsWeakPtr(),
&remote_endpoint);
version_->AddControllee(host.get());
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
EXPECT_LT(idle_time, version_->idle_time_);
// Completing an event resets the idle time.
version_->idle_time_ -= kOneSecond;
idle_time = version_->idle_time_;
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME);
EXPECT_LT(idle_time, version_->idle_time_);
// Starting and finishing a request resets the idle time.
version_->idle_time_ -= kOneSecond;
idle_time = version_->idle_time_;
base::Optional<blink::ServiceWorkerStatusCode> status;
int request_id =
version_->StartRequest(ServiceWorkerMetrics::EventType::SYNC,
CreateReceiverOnCurrentThread(&status));
EXPECT_TRUE(version_->FinishRequest(request_id, true /* was_handled */));
EXPECT_LT(idle_time, version_->idle_time_);
}
TEST_F(ServiceWorkerVersionTest, SetDevToolsAttached) {
base::Optional<blink::ServiceWorkerStatusCode> status;
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status));
ASSERT_EQ(EmbeddedWorkerStatus::STARTING, version_->running_status());
ASSERT_TRUE(version_->timeout_timer_.IsRunning());
ASSERT_FALSE(version_->start_time_.is_null());
ASSERT_FALSE(version_->skip_recording_startup_time_);
// Simulate DevTools is attached. This should deactivate the timer for start
// timeout, but not stop the timer itself.
version_->SetDevToolsAttached(true);
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
EXPECT_TRUE(version_->start_time_.is_null());
EXPECT_TRUE(version_->skip_recording_startup_time_);
// Simulate DevTools is detached. This should reactivate the timer for start
// timeout.
version_->SetDevToolsAttached(false);
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
EXPECT_FALSE(version_->start_time_.is_null());
EXPECT_TRUE(version_->skip_recording_startup_time_);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
}
TEST_F(ServiceWorkerVersionTest, StoppingBeforeDestruct) {
RunningStateListener listener;
version_->AddObserver(&listener);
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, listener.last_status);
// Destruct |version_| by releasing all references, including the provider
// host's.
helper_->context()->RemoveProviderHost(
version_->provider_host()->process_id(),
version_->provider_host()->provider_id());
version_ = nullptr;
EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, listener.last_status);
}
// Test that update isn't triggered for a non-stale worker.
TEST_F(ServiceWorkerVersionTest, StaleUpdate_FreshWorker) {
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration_->SetActiveVersion(version_);
registration_->set_last_update_check(base::Time::Now());
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::PUSH);
EXPECT_TRUE(version_->stale_time_.is_null());
EXPECT_FALSE(version_->update_timer_.IsRunning());
}
// Test that update isn't triggered for a non-active worker.
TEST_F(ServiceWorkerVersionTest, StaleUpdate_NonActiveWorker) {
version_->SetStatus(ServiceWorkerVersion::INSTALLING);
registration_->SetInstallingVersion(version_);
registration_->set_last_update_check(GetYesterday());
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::INSTALL);
EXPECT_TRUE(version_->stale_time_.is_null());
EXPECT_FALSE(version_->update_timer_.IsRunning());
}
// Test that staleness is detected when starting a worker.
TEST_F(ServiceWorkerVersionTest, StaleUpdate_StartWorker) {
// Starting the worker marks it as stale.
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration_->SetActiveVersion(version_);
registration_->set_last_update_check(GetYesterday());
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::PUSH);
EXPECT_FALSE(version_->stale_time_.is_null());
EXPECT_FALSE(version_->update_timer_.IsRunning());
// Update is actually scheduled after the worker stops.
bool has_stopped = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(has_stopped);
EXPECT_TRUE(version_->stale_time_.is_null());
EXPECT_TRUE(version_->update_timer_.IsRunning());
}
// Test that staleness is detected on a running worker.
TEST_F(ServiceWorkerVersionTest, StaleUpdate_RunningWorker) {
// Start a fresh worker.
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration_->SetActiveVersion(version_);
registration_->set_last_update_check(base::Time::Now());
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::PUSH);
EXPECT_TRUE(version_->stale_time_.is_null());
// Simulate it running for a day. It will be marked stale.
registration_->set_last_update_check(GetYesterday());
version_->OnTimeoutTimer();
EXPECT_FALSE(version_->stale_time_.is_null());
EXPECT_FALSE(version_->update_timer_.IsRunning());
// Simulate it running for past the wait threshold. The update will be
// scheduled.
version_->stale_time_ = base::TimeTicks::Now() -
ServiceWorkerVersion::kStartNewWorkerTimeout -
base::TimeDelta::FromMinutes(1);
version_->OnTimeoutTimer();
EXPECT_TRUE(version_->stale_time_.is_null());
EXPECT_TRUE(version_->update_timer_.IsRunning());
}
// Test that a stream of events doesn't restart the timer.
TEST_F(ServiceWorkerVersionTest, StaleUpdate_DoNotDeferTimer) {
// Make a stale worker.
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration_->SetActiveVersion(version_);
registration_->set_last_update_check(GetYesterday());
base::TimeTicks stale_time = base::TimeTicks::Now() -
ServiceWorkerVersion::kStartNewWorkerTimeout -
base::TimeDelta::FromMinutes(1);
version_->stale_time_ = stale_time;
// Stale time is not deferred.
version_->RunAfterStartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
base::DoNothing());
version_->RunAfterStartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
base::DoNothing());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(stale_time, version_->stale_time_);
// Timeout triggers the update.
version_->OnTimeoutTimer();
EXPECT_TRUE(version_->stale_time_.is_null());
EXPECT_TRUE(version_->update_timer_.IsRunning());
// Update timer is not deferred.
base::TimeTicks run_time = version_->update_timer_.desired_run_time();
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::PUSH);
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::PUSH);
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::PUSH);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(version_->stale_time_.is_null());
EXPECT_EQ(run_time, version_->update_timer_.desired_run_time());
}
TEST_F(ServiceWorkerVersionTest, StartRequestWithNullContext) {
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
version_->context_ = nullptr;
version_->StartRequest(ServiceWorkerMetrics::EventType::PUSH,
base::DoNothing());
// Test passes if it doesn't crash.
}
// Tests the delay mechanism for self-updating service workers, to prevent
// them from running forever (see https://crbug.com/805496).
TEST_F(ServiceWorkerVersionTest, ResetUpdateDelay) {
const base::TimeDelta kMinute = base::TimeDelta::FromMinutes(1);
const base::TimeDelta kNoDelay = base::TimeDelta();
// Initialize the delay.
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration_->SetActiveVersion(version_);
registration_->set_self_update_delay(kMinute);
// Events that can be triggered by a worker should not reset the delay.
// See the comment in ServiceWorkerVersion::StartRequestWithCustomTimeout.
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::INSTALL);
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::ACTIVATE);
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::MESSAGE);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(kMinute, registration_->self_update_delay());
// Events that can only be triggered externally reset the delay.
// Repeat the test for several such events.
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::SYNC);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(kNoDelay, registration_->self_update_delay());
registration_->set_self_update_delay(kMinute);
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::NOTIFICATION_CLICK);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(kNoDelay, registration_->self_update_delay());
registration_->set_self_update_delay(kMinute);
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::PUSH);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(kNoDelay, registration_->self_update_delay());
}
TEST_F(ServiceWorkerVersionTest, UpdateCachedMetadata) {
CachedMetadataUpdateListener listener;
version_->AddObserver(&listener);
ASSERT_EQ(0, listener.updated_count);
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
// Simulate requesting SetCachedMetadata from the service worker global scope.
std::vector<uint8_t> data{1, 2, 3};
helper_->SimulateSetCachedMetadata(
version_->embedded_worker()->embedded_worker_id(), version_->script_url(),
data);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, listener.updated_count);
// Simulate requesting ClearCachedMetadata from the service worker global
// scope.
helper_->SimulateClearCachedMetadata(
version_->embedded_worker()->embedded_worker_id(),
version_->script_url());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2, listener.updated_count);
version_->RemoveObserver(&listener);
}
TEST_F(ServiceWorkerVersionTest, RestartWorker) {
StartWorker(version_.get(),
ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME);
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
base::Optional<blink::ServiceWorkerStatusCode> event_status;
version_->StartRequest(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME,
CreateReceiverOnCurrentThread(&event_status));
// Restart the worker. The inflight event should have been failed.
bool has_stopped = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, version_->running_status());
base::Optional<blink::ServiceWorkerStatusCode> start_status;
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&start_status));
base::RunLoop().RunUntilIdle();
// All inflight events should have been aborted.
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorFailed, event_status.value());
// The worker should have been stopped.
EXPECT_TRUE(has_stopped);
// The worker should have been successfully re-started after stopped.
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, start_status.value());
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// SetAllRequestExpirations() after restarting should not crash since all
// events should have been removed at this point: crbug.com/791451.
version_->SetAllRequestExpirations(base::TimeTicks());
}
class MessageReceiverControlEvents : public MessageReceiver {
public:
MessageReceiverControlEvents() : MessageReceiver() {}
~MessageReceiverControlEvents() override {}
void OnExtendableMessageEvent(
blink::mojom::ExtendableMessageEventPtr event,
blink::mojom::ServiceWorker::DispatchExtendableMessageEventCallback
callback) override {
EXPECT_FALSE(extendable_message_event_callback_);
extendable_message_event_callback_ = std::move(callback);
}
void OnStopWorker(int embedded_worker_id) override {
EXPECT_FALSE(stop_worker_callback_);
stop_worker_callback_ =
base::BindOnce(&MessageReceiverControlEvents::SimulateWorkerStopped,
base::Unretained(this), embedded_worker_id);
}
bool has_extendable_message_event_callback() {
return !extendable_message_event_callback_.is_null();
}
blink::mojom::ServiceWorker::DispatchExtendableMessageEventCallback
TakeExtendableMessageEventCallback() {
return std::move(extendable_message_event_callback_);
}
base::OnceClosure stop_worker_callback() {
return std::move(stop_worker_callback_);
}
private:
blink::mojom::ServiceWorker::DispatchExtendableMessageEventCallback
extendable_message_event_callback_;
base::OnceClosure stop_worker_callback_;
};
class ServiceWorkerRequestTimeoutTest : public ServiceWorkerVersionTest {
protected:
ServiceWorkerRequestTimeoutTest() : ServiceWorkerVersionTest() {}
std::unique_ptr<MessageReceiver> GetMessageReceiver() override {
return std::make_unique<MessageReceiverControlEvents>();
}
bool has_extendable_message_event_callback() {
return static_cast<MessageReceiverControlEvents*>(helper_.get())
->has_extendable_message_event_callback();
}
blink::mojom::ServiceWorker::DispatchExtendableMessageEventCallback
TakeExtendableMessageEventCallback() {
return static_cast<MessageReceiverControlEvents*>(helper_.get())
->TakeExtendableMessageEventCallback();
}
base::OnceClosure stop_worker_callback() {
return static_cast<MessageReceiverControlEvents*>(helper_.get())
->stop_worker_callback();
}
private:
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRequestTimeoutTest);
};
TEST_F(ServiceWorkerRequestTimeoutTest, RequestTimeout) {
base::Optional<blink::ServiceWorkerStatusCode> error_status;
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
StartWorker(version_.get(),
ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME);
// Create a request.
int request_id =
version_->StartRequest(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME,
CreateReceiverOnCurrentThread(&error_status));
// Dispatch a dummy event whose response will be received by SWVersion.
EXPECT_FALSE(has_extendable_message_event_callback());
version_->endpoint()->DispatchExtendableMessageEvent(
blink::mojom::ExtendableMessageEvent::New(),
version_->CreateSimpleEventCallback(request_id));
base::RunLoop().RunUntilIdle();
// The renderer should have received an ExtendableMessageEvent request.
EXPECT_TRUE(has_extendable_message_event_callback());
// Callback has not completed yet.
EXPECT_FALSE(error_status);
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// Simulate timeout.
EXPECT_FALSE(stop_worker_callback());
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
version_->SetAllRequestExpirations(base::TimeTicks::Now());
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
base::OnceClosure callback = stop_worker_callback();
// The renderer should have received a StopWorker request.
EXPECT_TRUE(callback);
// The request should have timed out.
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout,
error_status.value());
// Calling FinishRequest should be no-op, since the request timed out.
EXPECT_FALSE(version_->FinishRequest(request_id, true /* was_handled */));
// Simulate the renderer aborting the inflight event.
// This should not crash: https://crbug.com/676984.
TakeExtendableMessageEventCallback().Run(
blink::mojom::ServiceWorkerEventStatus::ABORTED);
base::RunLoop().RunUntilIdle();
// Simulate the renderer stopping the worker.
std::move(callback).Run();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
}
TEST_F(ServiceWorkerVersionTest, RequestNowTimeout) {
base::Optional<blink::ServiceWorkerStatusCode> status;
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::SYNC);
// Create a request that should expire Now().
int request_id = version_->StartRequestWithCustomTimeout(
ServiceWorkerMetrics::EventType::SYNC,
CreateReceiverOnCurrentThread(&status), base::TimeDelta(),
ServiceWorkerVersion::CONTINUE_ON_TIMEOUT);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout, status.value());
EXPECT_FALSE(version_->FinishRequest(request_id, true /* was_handled */));
// CONTINUE_ON_TIMEOUT timeouts don't stop the service worker.
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
}
TEST_F(ServiceWorkerVersionTest, RequestNowTimeoutKill) {
base::Optional<blink::ServiceWorkerStatusCode> status;
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::SYNC);
// Create a request that should expire Now().
int request_id = version_->StartRequestWithCustomTimeout(
ServiceWorkerMetrics::EventType::SYNC,
CreateReceiverOnCurrentThread(&status), base::TimeDelta(),
ServiceWorkerVersion::KILL_ON_TIMEOUT);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout, status.value());
EXPECT_FALSE(version_->FinishRequest(request_id, true /* was_handled */));
// KILL_ON_TIMEOUT timeouts should stop the service worker.
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
}
TEST_F(ServiceWorkerVersionTest, RequestCustomizedTimeout) {
base::Optional<blink::ServiceWorkerStatusCode> first_status;
base::Optional<blink::ServiceWorkerStatusCode> second_status;
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::SYNC);
base::SimpleTestTickClock tick_clock;
SetTickClockForTesting(&tick_clock);
// Create two requests. One which times out in 10 seconds, one in 20 seconds.
int timeout_seconds = 10;
int first_request_id = version_->StartRequestWithCustomTimeout(
ServiceWorkerMetrics::EventType::SYNC,
CreateReceiverOnCurrentThread(&first_status),
base::TimeDelta::FromSeconds(2 * timeout_seconds),
ServiceWorkerVersion::KILL_ON_TIMEOUT);
int second_request_id = version_->StartRequestWithCustomTimeout(
ServiceWorkerMetrics::EventType::SYNC,
CreateReceiverOnCurrentThread(&second_status),
base::TimeDelta::FromSeconds(timeout_seconds),
ServiceWorkerVersion::CONTINUE_ON_TIMEOUT);
// The status should not have changed since neither task has timed out yet.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(first_status);
EXPECT_FALSE(second_status);
// Now advance time until the second task timeout should expire.
tick_clock.Advance(base::TimeDelta::FromSeconds(timeout_seconds + 1));
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(first_status);
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout,
second_status.value());
// CONTINUE_ON_TIMEOUT timeouts don't stop the service worker.
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// Now advance time until both tasks should be expired.
tick_clock.Advance(base::TimeDelta::FromSeconds(timeout_seconds + 1));
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout,
first_status.value());
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout,
second_status.value());
EXPECT_FALSE(
version_->FinishRequest(first_request_id, true /* was_handled */));
EXPECT_FALSE(
version_->FinishRequest(second_request_id, true /* was_handled */));
// KILL_ON_TIMEOUT timeouts should stop the service worker.
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
}
TEST_F(ServiceWorkerVersionTest, MixedRequestTimeouts) {
base::Optional<blink::ServiceWorkerStatusCode> sync_status;
base::Optional<blink::ServiceWorkerStatusCode> fetch_status;
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
StartWorker(version_.get(),
ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME);
// Create a fetch request that should expire sometime later.
int fetch_request_id =
version_->StartRequest(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME,
CreateReceiverOnCurrentThread(&fetch_status));
// Create a request that should expire Now().
int sync_request_id = version_->StartRequestWithCustomTimeout(
ServiceWorkerMetrics::EventType::SYNC,
CreateReceiverOnCurrentThread(&sync_status), base::TimeDelta(),
ServiceWorkerVersion::CONTINUE_ON_TIMEOUT);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(sync_status);
// Verify the sync has timed out but not the fetch.
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout, sync_status.value());
EXPECT_FALSE(fetch_status);
// Background sync timeouts don't stop the service worker.
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// Gracefully handle the sync event finishing after the timeout.
EXPECT_FALSE(
version_->FinishRequest(sync_request_id, true /* was_handled */));
// Verify that the fetch times out later.
version_->SetAllRequestExpirations(base::TimeTicks::Now());
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout,
fetch_status.value());
// Fetch request should no longer exist.
EXPECT_FALSE(
version_->FinishRequest(fetch_request_id, true /* was_handled */));
// Other timeouts do stop the service worker.
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
}
TEST_F(ServiceWorkerFailToStartTest, RendererCrash) {
base::Optional<blink::ServiceWorkerStatusCode> status;
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
// Callback has not completed yet.
EXPECT_FALSE(status);
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, version_->running_status());
// Simulate renderer crash: break EmbeddedWorkerInstance's Mojo connection to
// the renderer-side client.
helper_->mock_instance_clients()->clear();
base::RunLoop().RunUntilIdle();
// Callback completed.
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed,
status.value());
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
}
TEST_F(ServiceWorkerFailToStartTest, Timeout) {
base::Optional<blink::ServiceWorkerStatusCode> status;
// Start starting the worker.
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
// Callback has not completed yet.
EXPECT_FALSE(status);
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, version_->running_status());
// Simulate timeout.
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
version_->start_time_ = base::TimeTicks::Now() -
ServiceWorkerVersion::kStartNewWorkerTimeout -
base::TimeDelta::FromMinutes(1);
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout, status.value());
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
}
// Test that a service worker stalled in stopping will timeout and not get in a
// sticky error state.
TEST_F(ServiceWorkerStallInStoppingTest, DetachThenStart) {
// Start a worker.
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
// Try to stop the worker.
bool has_stopped = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, version_->running_status());
base::RunLoop().RunUntilIdle();
// Worker is now stalled in stopping. Verify a fast timeout is in place.
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
EXPECT_EQ(ServiceWorkerVersion::kStopWorkerTimeout,
version_->timeout_timer_.GetCurrentDelay());
// Simulate timeout.
version_->stop_time_ = base::TimeTicks::Now() -
ServiceWorkerVersion::kStopWorkerTimeout -
base::TimeDelta::FromSeconds(1);
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(has_stopped);
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
// Try to start the worker again. It should work.
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
// The timeout interval should be reset to normal.
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
EXPECT_EQ(ServiceWorkerVersion::kTimeoutTimerDelay,
version_->timeout_timer_.GetCurrentDelay());
}
// Test that a service worker stalled in stopping with a start worker
// request queued up will timeout and restart.
TEST_F(ServiceWorkerStallInStoppingTest, DetachThenRestart) {
// Start a worker.
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
// Try to stop the worker.
bool has_stopped = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, version_->running_status());
// Worker is now stalled in stopping. Add a start worker request.
base::Optional<blink::ServiceWorkerStatusCode> start_status;
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&start_status));
// Simulate timeout. The worker should stop and get restarted.
EXPECT_TRUE(version_->timeout_timer_.IsRunning());
version_->stop_time_ = base::TimeTicks::Now() -
ServiceWorkerVersion::kStopWorkerTimeout -
base::TimeDelta::FromSeconds(1);
version_->timeout_timer_.user_task().Run();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(has_stopped);
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, start_status.value());
}
TEST_F(ServiceWorkerVersionTest, RendererCrashDuringEvent) {
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
StartWorker(version_.get(), ServiceWorkerMetrics::EventType::SYNC);
base::Optional<blink::ServiceWorkerStatusCode> status;
int request_id =
version_->StartRequest(ServiceWorkerMetrics::EventType::SYNC,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
// Callback has not completed yet.
EXPECT_FALSE(status);
// Simulate renderer crash: break EmbeddedWorkerInstance's Mojo connection to
// the renderer-side client.
helper_->mock_instance_clients()->clear();
base::RunLoop().RunUntilIdle();
// Callback completed.
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorFailed, status.value());
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
// Request already failed, calling finsh should return false.
EXPECT_FALSE(version_->FinishRequest(request_id, true /* was_handled */));
}
TEST_F(ServiceWorkerVersionTest, PingController) {
// Start starting an worker. Ping should not be active.
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
base::DoNothing());
EXPECT_FALSE(IsPingActivated(version_.get()));
// Start script evaluation. Ping should be active.
NotifyScriptEvaluationStart(version_.get());
EXPECT_TRUE(IsPingActivated(version_.get()));
// Finish starting the worker. Ping should still be active.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
EXPECT_TRUE(IsPingActivated(version_.get()));
}
// Test starting a service worker from a disallowed origin.
TEST_F(ServiceWorkerVersionTest, BadOrigin) {
const GURL scope("bad-origin://www.example.com/test/");
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = scope;
auto registration = base::MakeRefCounted<ServiceWorkerRegistration>(
options, helper_->context()->storage()->NewRegistrationId(),
helper_->context()->AsWeakPtr());
auto version = base::MakeRefCounted<ServiceWorkerVersion>(
registration_.get(),
GURL("bad-origin://www.example.com/test/service_worker.js"),
blink::mojom::ScriptType::kClassic,
helper_->context()->storage()->NewVersionId(),
helper_->context()->AsWeakPtr());
base::Optional<blink::ServiceWorkerStatusCode> status;
version->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorDisallowed, status.value());
}
TEST_F(ServiceWorkerFailToStartTest, FailingWorkerUsesNewRendererProcess) {
base::Optional<blink::ServiceWorkerStatusCode> status;
ServiceWorkerContextCore* context = helper_->context();
int64_t id = version_->version_id();
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
// Start once. It should choose the "existing process".
set_start_mode(MessageReceiverDisallowStart::StartMode::SUCCEED);
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
EXPECT_EQ(helper_->mock_render_process_id(),
version_->embedded_worker()->process_id());
version_->StopWorker(base::DoNothing());
base::RunLoop().RunUntilIdle();
// Fail once.
status.reset();
set_start_mode(MessageReceiverDisallowStart::StartMode::FAIL);
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed,
status.value());
EXPECT_EQ(1, context->GetVersionFailureCount(id));
// Fail again.
status.reset();
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed,
status.value());
EXPECT_EQ(2, context->GetVersionFailureCount(id));
// Succeed. It should choose the "new process".
status.reset();
set_start_mode(MessageReceiverDisallowStart::StartMode::SUCCEED);
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
EXPECT_EQ(helper_->new_render_process_id(),
version_->embedded_worker()->process_id());
EXPECT_EQ(0, context->GetVersionFailureCount(id));
version_->StopWorker(base::DoNothing());
base::RunLoop().RunUntilIdle();
// Start again. It should choose the "existing process" again as we no longer
// force creation of a new process.
status.reset();
version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
EXPECT_EQ(helper_->mock_render_process_id(),
version_->embedded_worker()->process_id());
version_->StopWorker(base::DoNothing());
base::RunLoop().RunUntilIdle();
}
TEST_F(ServiceWorkerFailToStartTest, RestartStalledWorker) {
base::Optional<blink::ServiceWorkerStatusCode> status;
version_->StartWorker(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME,
CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
// The default start mode is StartMode::STALL. So the callback of StartWorker
// is not called yet.
EXPECT_FALSE(status);
// Set StartMode::SUCCEED. So the next start worker will be successful.
set_start_mode(MessageReceiverDisallowStart::StartMode::SUCCEED);
// StartWorker message will be sent again because OnStopped is called before
// OnStarted.
bool has_stopped = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
EXPECT_TRUE(has_stopped);
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
}
class ServiceWorkerNavigationHintUMATest : public ServiceWorkerVersionTest {
protected:
ServiceWorkerNavigationHintUMATest() : ServiceWorkerVersionTest() {}
void StartWorker(ServiceWorkerMetrics::EventType purpose) {
base::Optional<blink::ServiceWorkerStatusCode> status;
version_->StartWorker(purpose, CreateReceiverOnCurrentThread(&status));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
}
void StopWorker() {
bool has_stopped = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(has_stopped);
}
static const char kStartHintPrecision[];
base::HistogramTester histogram_tester_;
private:
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerNavigationHintUMATest);
};
const char ServiceWorkerNavigationHintUMATest::kStartHintPrecision[] =
"ServiceWorker.StartHintPrecision";
TEST_F(ServiceWorkerNavigationHintUMATest, Precision) {
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
StartWorker(ServiceWorkerMetrics::EventType::NAVIGATION_HINT);
StopWorker();
histogram_tester_.ExpectBucketCount(kStartHintPrecision, true, 0);
histogram_tester_.ExpectBucketCount(kStartHintPrecision, false, 1);
StartWorker(ServiceWorkerMetrics::EventType::NAVIGATION_HINT);
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::MESSAGE);
StopWorker();
histogram_tester_.ExpectBucketCount(kStartHintPrecision, true, 0);
histogram_tester_.ExpectBucketCount(kStartHintPrecision, false, 2);
StartWorker(ServiceWorkerMetrics::EventType::NAVIGATION_HINT);
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME);
StopWorker();
histogram_tester_.ExpectBucketCount(kStartHintPrecision, true, 1);
histogram_tester_.ExpectBucketCount(kStartHintPrecision, false, 2);
StartWorker(ServiceWorkerMetrics::EventType::NAVIGATION_HINT);
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::FETCH_SUB_FRAME);
StopWorker();
histogram_tester_.ExpectBucketCount(kStartHintPrecision, true, 2);
histogram_tester_.ExpectBucketCount(kStartHintPrecision, false, 2);
}
TEST_F(ServiceWorkerNavigationHintUMATest, ConcurrentStart) {
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
base::Optional<blink::ServiceWorkerStatusCode> status1;
base::Optional<blink::ServiceWorkerStatusCode> status2;
version_->StartWorker(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME,
CreateReceiverOnCurrentThread(&status1));
version_->StartWorker(ServiceWorkerMetrics::EventType::NAVIGATION_HINT,
CreateReceiverOnCurrentThread(&status2));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status1.value());
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status2.value());
StopWorker();
// The first purpose of starting worker was not a navigation hint.
histogram_tester_.ExpectTotalCount(kStartHintPrecision, 0);
status1.reset();
status2.reset();
version_->StartWorker(ServiceWorkerMetrics::EventType::NAVIGATION_HINT,
CreateReceiverOnCurrentThread(&status2));
version_->StartWorker(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME,
CreateReceiverOnCurrentThread(&status1));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status1.value());
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status2.value());
SimulateDispatchEvent(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME);
StopWorker();
// The first purpose of starting worker was a navigation hint.
histogram_tester_.ExpectTotalCount(kStartHintPrecision, 1);
histogram_tester_.ExpectBucketCount(kStartHintPrecision, true, 1);
histogram_tester_.ExpectBucketCount(kStartHintPrecision, false, 0);
}
TEST_F(ServiceWorkerNavigationHintUMATest, StartWhileStopping) {
StartWorker(ServiceWorkerMetrics::EventType::NAVIGATION_HINT);
bool has_stopped = false;
version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, version_->running_status());
histogram_tester_.ExpectTotalCount(kStartHintPrecision, 0);
StartWorker(ServiceWorkerMetrics::EventType::NAVIGATION_HINT);
// The UMA must be recorded while restarting.
histogram_tester_.ExpectTotalCount(kStartHintPrecision, 1);
EXPECT_TRUE(has_stopped);
StopWorker();
// The UMA must be recorded when the worker stopped.
histogram_tester_.ExpectTotalCount(kStartHintPrecision, 2);
}
} // namespace service_worker_version_unittest
} // namespace content