blob: cac0d65d7b8701d8efa4028547342944cbd0131b [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/embedded_worker_instance.h"
#include <stdint.h>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/stl_util.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_context_wrapper.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/common/service_worker/embedded_worker.mojom.h"
#include "content/common/service_worker/embedded_worker_messages.h"
#include "content/common/service_worker/embedded_worker_start_params.h"
#include "content/common/service_worker/service_worker_event_dispatcher.mojom.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/common/service_worker/service_worker.mojom.h"
#include "third_party/WebKit/common/service_worker/service_worker_registration.mojom.h"
namespace content {
namespace {
void SaveStatusAndCall(ServiceWorkerStatusCode* out,
base::OnceClosure callback,
ServiceWorkerStatusCode status) {
*out = status;
std::move(callback).Run();
}
} // namespace
class ProviderHostEndpoints : public mojom::ServiceWorkerContainerHost {
public:
ProviderHostEndpoints() : binding_(this) {}
~ProviderHostEndpoints() override {}
mojom::ServiceWorkerProviderInfoForStartWorkerPtr CreateProviderInfoPtr() {
DCHECK(!binding_.is_bound());
DCHECK(!client_.is_bound());
// Just keep the endpoints.
mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info =
mojom::ServiceWorkerProviderInfoForStartWorker::New();
provider_info->registration =
blink::mojom::ServiceWorkerRegistrationObjectInfo::New();
provider_info->registration->options =
blink::mojom::ServiceWorkerRegistrationOptions::New();
registration_object_host_request_ =
mojo::MakeRequest(&(provider_info->registration->host_ptr_info));
provider_info->registration->installing =
blink::mojom::ServiceWorkerObjectInfo::New();
provider_info->registration->waiting =
blink::mojom::ServiceWorkerObjectInfo::New();
provider_info->registration->active =
blink::mojom::ServiceWorkerObjectInfo::New();
binding_.Bind(mojo::MakeRequest(&provider_info->host_ptr_info));
provider_info->client_request = mojo::MakeRequest(&client_);
mojo::MakeRequest(&provider_info->interface_provider);
return provider_info;
}
private:
// Implements mojom::ServiceWorkerContainerHost.
void Register(const GURL& script_url,
blink::mojom::ServiceWorkerRegistrationOptionsPtr options,
RegisterCallback callback) override {
NOTIMPLEMENTED();
}
void GetRegistration(const GURL& client_url,
GetRegistrationCallback callback) override {
NOTIMPLEMENTED();
}
void GetRegistrations(GetRegistrationsCallback callback) override {
NOTIMPLEMENTED();
}
void GetRegistrationForReady(
GetRegistrationForReadyCallback callback) override {
NOTIMPLEMENTED();
}
void GetControllerServiceWorker(
mojom::ControllerServiceWorkerRequest request) override {
NOTIMPLEMENTED();
}
void CloneForWorker(
mojom::ServiceWorkerContainerHostRequest request) override {
NOTIMPLEMENTED();
}
mojom::ServiceWorkerContainerAssociatedPtr client_;
mojo::AssociatedBinding<mojom::ServiceWorkerContainerHost> binding_;
blink::mojom::ServiceWorkerRegistrationObjectHostAssociatedRequest
registration_object_host_request_;
DISALLOW_COPY_AND_ASSIGN(ProviderHostEndpoints);
};
class EmbeddedWorkerInstanceTest : public testing::Test,
public EmbeddedWorkerInstance::Listener {
protected:
EmbeddedWorkerInstanceTest()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
enum EventType {
PROCESS_ALLOCATED,
START_WORKER_MESSAGE_SENT,
STARTED,
STOPPED,
DETACHED,
};
struct EventLog {
EventType type;
EmbeddedWorkerStatus status;
};
void RecordEvent(
EventType type,
EmbeddedWorkerStatus status = EmbeddedWorkerStatus::STOPPED) {
EventLog log = {type, status};
events_.push_back(log);
}
void OnProcessAllocated() override { RecordEvent(PROCESS_ALLOCATED); }
void OnStartWorkerMessageSent() override {
RecordEvent(START_WORKER_MESSAGE_SENT);
}
void OnStarted() override { RecordEvent(STARTED); }
void OnStopped(EmbeddedWorkerStatus old_status) override {
RecordEvent(STOPPED, old_status);
}
void OnDetached(EmbeddedWorkerStatus old_status) override {
RecordEvent(DETACHED, old_status);
}
bool OnMessageReceived(const IPC::Message&) override { return false; }
void SetUp() override {
helper_.reset(new EmbeddedWorkerTestHelper(base::FilePath()));
}
void TearDown() override { helper_.reset(); }
using RegistrationAndVersionPair =
std::pair<scoped_refptr<ServiceWorkerRegistration>,
scoped_refptr<ServiceWorkerVersion>>;
RegistrationAndVersionPair PrepareRegistrationAndVersion(
const GURL& scope,
const GURL& script_url) {
base::RunLoop loop;
if (!context()->storage()->LazyInitializeForTest(loop.QuitClosure())) {
loop.Run();
}
RegistrationAndVersionPair pair;
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = scope;
pair.first = base::MakeRefCounted<ServiceWorkerRegistration>(
options, context()->storage()->NewRegistrationId(),
context()->AsWeakPtr());
pair.second = base::MakeRefCounted<ServiceWorkerVersion>(
pair.first.get(), script_url, context()->storage()->NewVersionId(),
context()->AsWeakPtr());
return pair;
}
ServiceWorkerStatusCode StartWorker(EmbeddedWorkerInstance* worker,
int id, const GURL& pattern,
const GURL& url) {
ServiceWorkerStatusCode status;
base::RunLoop run_loop;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(id, pattern, url);
worker->Start(
std::move(params), CreateProviderInfoGetter(), CreateEventDispatcher(),
CreateController(), GetInstalledScriptsInfoPtr(),
GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure()));
run_loop.Run();
return status;
}
std::unique_ptr<EmbeddedWorkerStartParams>
CreateStartParams(int version_id, const GURL& scope, const GURL& script_url) {
std::unique_ptr<EmbeddedWorkerStartParams> params =
std::make_unique<EmbeddedWorkerStartParams>();
params->service_worker_version_id = version_id;
params->scope = scope;
params->script_url = script_url;
params->pause_after_download = false;
params->is_installed = false;
return params;
}
mojom::ServiceWorkerProviderInfoForStartWorkerPtr CreateProviderInfo(
int /* process_id */) {
provider_host_endpoints_.emplace_back(
std::make_unique<ProviderHostEndpoints>());
return provider_host_endpoints_.back()->CreateProviderInfoPtr();
}
EmbeddedWorkerInstance::ProviderInfoGetter CreateProviderInfoGetter() {
return base::BindOnce(&EmbeddedWorkerInstanceTest::CreateProviderInfo,
base::Unretained(this));
}
mojom::ServiceWorkerEventDispatcherRequest CreateEventDispatcher() {
dispatchers_.emplace_back();
return mojo::MakeRequest(&dispatchers_.back());
}
mojom::ControllerServiceWorkerRequest CreateController() {
controllers_.emplace_back();
return mojo::MakeRequest(&controllers_.back());
}
void SetWorkerStatus(EmbeddedWorkerInstance* worker,
EmbeddedWorkerStatus status) {
worker->status_ = status;
}
ServiceWorkerStatusCode SimulateSendStartWorker(
EmbeddedWorkerInstance* worker,
std::unique_ptr<EmbeddedWorkerStartParams> params) {
return worker->SendStartWorker(std::move(params));
}
blink::mojom::ServiceWorkerInstalledScriptsInfoPtr
GetInstalledScriptsInfoPtr() {
installed_scripts_managers_.emplace_back();
auto info = blink::mojom::ServiceWorkerInstalledScriptsInfo::New();
info->manager_request =
mojo::MakeRequest(&installed_scripts_managers_.back());
installed_scripts_manager_host_requests_.push_back(
mojo::MakeRequest(&info->manager_host_ptr));
return info;
}
blink::mojom::ServiceWorkerHostAssociatedPtrInfo
GetServiceWorkerHostPtrInfo() {
blink::mojom::ServiceWorkerHostAssociatedPtrInfo ptr_info;
service_worker_host_requests_.push_back(mojo::MakeRequest(&ptr_info));
return ptr_info;
}
ServiceWorkerContextCore* context() { return helper_->context(); }
EmbeddedWorkerRegistry* embedded_worker_registry() {
DCHECK(context());
return context()->embedded_worker_registry();
}
IPC::TestSink* ipc_sink() { return helper_->ipc_sink(); }
std::vector<std::unique_ptr<
EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient>>*
mock_instance_clients() {
return helper_->mock_instance_clients();
}
// Mojo endpoints.
std::vector<mojom::ServiceWorkerEventDispatcherPtr> dispatchers_;
std::vector<mojom::ControllerServiceWorkerPtr> controllers_;
std::vector<blink::mojom::ServiceWorkerInstalledScriptsManagerPtr>
installed_scripts_managers_;
std::vector<blink::mojom::ServiceWorkerInstalledScriptsManagerHostRequest>
installed_scripts_manager_host_requests_;
std::vector<blink::mojom::ServiceWorkerHostAssociatedRequest>
service_worker_host_requests_;
std::vector<std::unique_ptr<ProviderHostEndpoints>> provider_host_endpoints_;
TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
std::vector<EventLog> events_;
private:
DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerInstanceTest);
};
// A helper to simulate the start worker sequence is stalled in a worker
// process.
class StalledInStartWorkerHelper : public EmbeddedWorkerTestHelper {
public:
StalledInStartWorkerHelper() : EmbeddedWorkerTestHelper(base::FilePath()) {}
~StalledInStartWorkerHelper() override {}
void OnStartWorker(
int embedded_worker_id,
int64_t service_worker_version_id,
const GURL& scope,
const GURL& script_url,
bool pause_after_download,
mojom::ServiceWorkerEventDispatcherRequest dispatcher_request,
mojom::ControllerServiceWorkerRequest controller_request,
blink::mojom::ServiceWorkerHostAssociatedPtrInfo service_worker_host,
mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
override {
if (force_stall_in_start_) {
// Prepare for OnStopWorker().
instance_host_ptr_map_[embedded_worker_id].Bind(std::move(instance_host));
// Do nothing to simulate a stall in the worker process.
return;
}
EmbeddedWorkerTestHelper::OnStartWorker(
embedded_worker_id, service_worker_version_id, scope, script_url,
pause_after_download, std::move(dispatcher_request),
std::move(controller_request), std::move(service_worker_host),
std::move(instance_host), std::move(provider_info),
std::move(installed_scripts_info));
}
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_force_stall_in_start(bool force_stall_in_start) {
force_stall_in_start_ = force_stall_in_start;
}
private:
bool force_stall_in_start_ = true;
std::map<
int /* embedded_worker_id */,
mojom::EmbeddedWorkerInstanceHostAssociatedPtr /* instance_host_ptr */>
instance_host_ptr_map_;
};
TEST_F(EmbeddedWorkerInstanceTest, StartAndStop) {
const GURL pattern("http://example.com/");
const GURL url("http://example.com/worker.js");
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(pattern, url);
const int64_t service_worker_version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
worker->AddListener(this);
// Simulate adding one process to the pattern.
helper_->SimulateAddProcessToPattern(pattern,
helper_->mock_render_process_id());
// Start should succeed.
ServiceWorkerStatusCode status;
base::RunLoop run_loop;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(service_worker_version_id, pattern, url);
worker->Start(
std::move(params), CreateProviderInfoGetter(), CreateEventDispatcher(),
CreateController(), GetInstalledScriptsInfoPtr(),
GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure()));
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, worker->status());
run_loop.Run();
EXPECT_EQ(SERVICE_WORKER_OK, status);
// The 'WorkerStarted' message should have been sent by
// EmbeddedWorkerTestHelper.
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, worker->status());
EXPECT_EQ(helper_->mock_render_process_id(), worker->process_id());
// Stop the worker.
worker->Stop();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, worker->status());
base::RunLoop().RunUntilIdle();
// The 'WorkerStopped' message should have been sent by
// EmbeddedWorkerTestHelper.
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
// Check if the IPCs are fired in expected order.
ASSERT_EQ(4u, events_.size());
EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
EXPECT_EQ(START_WORKER_MESSAGE_SENT, events_[1].type);
EXPECT_EQ(STARTED, events_[2].type);
EXPECT_EQ(STOPPED, events_[3].type);
}
// Test that a worker that failed twice will use a new render process
// on the next attempt.
TEST_F(EmbeddedWorkerInstanceTest, ForceNewProcess) {
const GURL pattern("http://example.com/");
const GURL url("http://example.com/worker.js");
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(pattern, url);
const int64_t service_worker_version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
// Simulate adding one process to the pattern.
helper_->SimulateAddProcessToPattern(pattern,
helper_->mock_render_process_id());
// Also simulate adding a "newly created" process to the pattern because
// unittests can't actually create a new process itself.
// ServiceWorkerProcessManager only chooses this process id in unittests if
// can_use_existing_process is false.
helper_->SimulateAddProcessToPattern(pattern,
helper_->new_render_process_id());
{
// Start once normally.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
base::RunLoop run_loop;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(service_worker_version_id, pattern, url);
worker->Start(
std::move(params), CreateProviderInfoGetter(), CreateEventDispatcher(),
CreateController(), GetInstalledScriptsInfoPtr(),
GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, worker->status());
// The worker should be using the default render process.
EXPECT_EQ(helper_->mock_render_process_id(), worker->process_id());
worker->Stop();
base::RunLoop().RunUntilIdle();
}
// Fail twice.
context()->UpdateVersionFailureCount(service_worker_version_id,
SERVICE_WORKER_ERROR_FAILED);
context()->UpdateVersionFailureCount(service_worker_version_id,
SERVICE_WORKER_ERROR_FAILED);
{
// Start again.
ServiceWorkerStatusCode status;
base::RunLoop run_loop;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(service_worker_version_id, pattern, url);
worker->Start(
std::move(params), CreateProviderInfoGetter(), CreateEventDispatcher(),
CreateController(), GetInstalledScriptsInfoPtr(),
GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure()));
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, worker->status());
run_loop.Run();
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, worker->status());
// The worker should be using the new render process.
EXPECT_EQ(helper_->new_render_process_id(), worker->process_id());
worker->Stop();
base::RunLoop().RunUntilIdle();
}
}
TEST_F(EmbeddedWorkerInstanceTest, StopWhenDevToolsAttached) {
const GURL pattern("http://example.com/");
const GURL url("http://example.com/worker.js");
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(pattern, url);
const int64_t service_worker_version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
// Simulate adding one process to the pattern.
helper_->SimulateAddProcessToPattern(pattern,
helper_->mock_render_process_id());
// Start the worker and then call StopIfNotAttachedToDevTools().
EXPECT_EQ(SERVICE_WORKER_OK,
StartWorker(worker.get(), service_worker_version_id, pattern, url));
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, worker->status());
EXPECT_EQ(helper_->mock_render_process_id(), worker->process_id());
worker->StopIfNotAttachedToDevTools();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, worker->status());
base::RunLoop().RunUntilIdle();
// The worker must be stopped now.
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
// Set devtools_attached to true, and do the same.
worker->SetDevToolsAttached(true);
EXPECT_EQ(SERVICE_WORKER_OK,
StartWorker(worker.get(), service_worker_version_id, pattern, url));
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, worker->status());
EXPECT_EQ(helper_->mock_render_process_id(), worker->process_id());
worker->StopIfNotAttachedToDevTools();
base::RunLoop().RunUntilIdle();
// The worker must not be stopped this time.
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, worker->status());
// Calling Stop() actually stops the worker regardless of whether devtools
// is attached or not.
worker->Stop();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
}
// Test that the removal of a worker from the registry doesn't remove
// other workers in the same process.
TEST_F(EmbeddedWorkerInstanceTest, RemoveWorkerInSharedProcess) {
const GURL pattern("http://example.com/");
const GURL url("http://example.com/worker.js");
RegistrationAndVersionPair pair1 =
PrepareRegistrationAndVersion(pattern, url);
std::unique_ptr<EmbeddedWorkerInstance> worker1 =
embedded_worker_registry()->CreateWorker(pair1.second.get());
RegistrationAndVersionPair pair2 =
PrepareRegistrationAndVersion(pattern, url);
std::unique_ptr<EmbeddedWorkerInstance> worker2 =
embedded_worker_registry()->CreateWorker(pair2.second.get());
const int64_t version_id1 = pair1.second->version_id();
const int64_t version_id2 = pair2.second->version_id();
int process_id = helper_->mock_render_process_id();
helper_->SimulateAddProcessToPattern(pattern, process_id);
{
// Start worker1.
ServiceWorkerStatusCode status;
base::RunLoop run_loop;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id1, pattern, url);
worker1->Start(
std::move(params), CreateProviderInfoGetter(), CreateEventDispatcher(),
CreateController(), GetInstalledScriptsInfoPtr(),
GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(SERVICE_WORKER_OK, status);
}
{
// Start worker2.
ServiceWorkerStatusCode status;
base::RunLoop run_loop;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id2, pattern, url);
worker2->Start(
std::move(params), CreateProviderInfoGetter(), CreateEventDispatcher(),
CreateController(), GetInstalledScriptsInfoPtr(),
GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(SERVICE_WORKER_OK, status);
}
// The two workers share the same process.
EXPECT_EQ(worker1->process_id(), worker2->process_id());
// Destroy worker1. It removes itself from the registry.
int worker1_id = worker1->embedded_worker_id();
worker1->Stop();
worker1.reset();
// Only worker1 should be removed from the registry's process_map.
EmbeddedWorkerRegistry* registry =
helper_->context()->embedded_worker_registry();
EXPECT_EQ(0UL, registry->worker_process_map_[process_id].count(worker1_id));
EXPECT_EQ(1UL, registry->worker_process_map_[process_id].count(
worker2->embedded_worker_id()));
worker2->Stop();
}
TEST_F(EmbeddedWorkerInstanceTest, DetachDuringProcessAllocation) {
const GURL scope("http://example.com/");
const GURL url("http://example.com/worker.js");
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
const int64_t version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
worker->AddListener(this);
// Run the start worker sequence and detach during process allocation.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id, scope, url);
worker->Start(std::move(params), CreateProviderInfoGetter(),
CreateEventDispatcher(), CreateController(),
GetInstalledScriptsInfoPtr(), GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status,
base::BindOnce(&base::DoNothing)));
worker->Detach();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
EXPECT_EQ(ChildProcessHost::kInvalidUniqueID, worker->process_id());
// The start callback should not be aborted by detach (see a comment on the
// dtor of EmbeddedWorkerInstance::StartTask).
EXPECT_EQ(SERVICE_WORKER_ERROR_MAX_VALUE, status);
// "PROCESS_ALLOCATED" event should not be recorded.
ASSERT_EQ(1u, events_.size());
EXPECT_EQ(DETACHED, events_[0].type);
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, events_[0].status);
}
TEST_F(EmbeddedWorkerInstanceTest, DetachAfterSendingStartWorkerMessage) {
const GURL scope("http://example.com/");
const GURL url("http://example.com/worker.js");
helper_.reset(new StalledInStartWorkerHelper());
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
const int64_t version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
worker->AddListener(this);
// Run the start worker sequence until a start worker message is sent.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id, scope, url);
worker->Start(std::move(params), CreateProviderInfoGetter(),
CreateEventDispatcher(), CreateController(),
GetInstalledScriptsInfoPtr(), GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status,
base::BindOnce(&base::DoNothing)));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(2u, events_.size());
EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
EXPECT_EQ(START_WORKER_MESSAGE_SENT, events_[1].type);
events_.clear();
worker->Detach();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
EXPECT_EQ(ChildProcessHost::kInvalidUniqueID, worker->process_id());
// The start callback should not be aborted by detach (see a comment on the
// dtor of EmbeddedWorkerInstance::StartTask).
EXPECT_EQ(SERVICE_WORKER_ERROR_MAX_VALUE, status);
// "STARTED" event should not be recorded.
ASSERT_EQ(1u, events_.size());
EXPECT_EQ(DETACHED, events_[0].type);
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, events_[0].status);
}
TEST_F(EmbeddedWorkerInstanceTest, StopDuringProcessAllocation) {
const GURL scope("http://example.com/");
const GURL url("http://example.com/worker.js");
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
const int64_t version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
worker->AddListener(this);
// Stop the start worker sequence before a process is allocated.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id, scope, url);
worker->Start(std::move(params), CreateProviderInfoGetter(),
CreateEventDispatcher(), CreateController(),
GetInstalledScriptsInfoPtr(), GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status,
base::BindOnce(&base::DoNothing)));
worker->Stop();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
EXPECT_EQ(ChildProcessHost::kInvalidUniqueID, worker->process_id());
// The start callback should not be aborted by stop (see a comment on the dtor
// of EmbeddedWorkerInstance::StartTask).
EXPECT_EQ(SERVICE_WORKER_ERROR_MAX_VALUE, status);
// "PROCESS_ALLOCATED" event should not be recorded.
ASSERT_EQ(1u, events_.size());
EXPECT_EQ(STOPPED, events_[0].type);
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, events_[0].status);
events_.clear();
// Restart the worker.
status = SERVICE_WORKER_ERROR_MAX_VALUE;
std::unique_ptr<base::RunLoop> run_loop(new base::RunLoop);
params = CreateStartParams(version_id, scope, url);
worker->Start(
std::move(params), CreateProviderInfoGetter(), CreateEventDispatcher(),
CreateController(), GetInstalledScriptsInfoPtr(),
GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status, run_loop->QuitClosure()));
run_loop->Run();
EXPECT_EQ(SERVICE_WORKER_OK, status);
ASSERT_EQ(3u, events_.size());
EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
EXPECT_EQ(START_WORKER_MESSAGE_SENT, events_[1].type);
EXPECT_EQ(STARTED, events_[2].type);
// Tear down the worker.
worker->Stop();
}
class DontReceiveResumeAfterDownloadInstanceClient
: public EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient {
public:
explicit DontReceiveResumeAfterDownloadInstanceClient(
base::WeakPtr<EmbeddedWorkerTestHelper> helper,
bool* was_resume_after_download_called)
: EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient(helper),
was_resume_after_download_called_(was_resume_after_download_called) {}
private:
void ResumeAfterDownload() override {
*was_resume_after_download_called_ = true;
}
bool* const was_resume_after_download_called_;
};
TEST_F(EmbeddedWorkerInstanceTest, StopDuringPausedAfterDownload) {
const GURL scope("http://example.com/");
const GURL url("http://example.com/worker.js");
bool was_resume_after_download_called = false;
helper_->RegisterMockInstanceClient(
std::make_unique<DontReceiveResumeAfterDownloadInstanceClient>(
helper_->AsWeakPtr(), &was_resume_after_download_called));
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
const int64_t version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
worker->AddListener(this);
// Run the start worker sequence until pause after download.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id, scope, url);
params->pause_after_download = true;
worker->Start(std::move(params), CreateProviderInfoGetter(),
CreateEventDispatcher(), CreateController(),
GetInstalledScriptsInfoPtr(), GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status,
base::BindOnce(&base::DoNothing)));
base::RunLoop().RunUntilIdle();
// Make the worker stopping and attempt to send a resume after download
// message.
worker->Stop();
worker->ResumeAfterDownload();
base::RunLoop().RunUntilIdle();
// The resume after download message should not have been sent.
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
EXPECT_FALSE(was_resume_after_download_called);
}
TEST_F(EmbeddedWorkerInstanceTest, StopAfterSendingStartWorkerMessage) {
const GURL scope("http://example.com/");
const GURL url("http://example.com/worker.js");
helper_.reset(new StalledInStartWorkerHelper);
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
const int64_t version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
worker->AddListener(this);
// Run the start worker sequence until a start worker message is sent.
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id, scope, url);
worker->Start(std::move(params), CreateProviderInfoGetter(),
CreateEventDispatcher(), CreateController(),
GetInstalledScriptsInfoPtr(), GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status,
base::BindOnce(&base::DoNothing)));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(2u, events_.size());
EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
EXPECT_EQ(START_WORKER_MESSAGE_SENT, events_[1].type);
events_.clear();
worker->Stop();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
EXPECT_EQ(ChildProcessHost::kInvalidUniqueID, worker->process_id());
// The start callback should not be aborted by stop (see a comment on the dtor
// of EmbeddedWorkerInstance::StartTask).
EXPECT_EQ(SERVICE_WORKER_ERROR_MAX_VALUE, status);
// "STARTED" event should not be recorded.
ASSERT_EQ(1u, events_.size());
EXPECT_EQ(STOPPED, events_[0].type);
EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, events_[0].status);
events_.clear();
// Restart the worker.
static_cast<StalledInStartWorkerHelper*>(helper_.get())
->set_force_stall_in_start(false);
status = SERVICE_WORKER_ERROR_MAX_VALUE;
std::unique_ptr<base::RunLoop> run_loop(new base::RunLoop);
params = CreateStartParams(version_id, scope, url);
worker->Start(
std::move(params), CreateProviderInfoGetter(), CreateEventDispatcher(),
CreateController(), GetInstalledScriptsInfoPtr(),
GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status, run_loop->QuitClosure()));
run_loop->Run();
// The worker should be started.
EXPECT_EQ(SERVICE_WORKER_OK, status);
ASSERT_EQ(3u, events_.size());
EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
EXPECT_EQ(START_WORKER_MESSAGE_SENT, events_[1].type);
EXPECT_EQ(STARTED, events_[2].type);
// Tear down the worker.
worker->Stop();
}
TEST_F(EmbeddedWorkerInstanceTest, Detach) {
const GURL pattern("http://example.com/");
const GURL url("http://example.com/worker.js");
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(pattern, url);
const int64_t version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
helper_->SimulateAddProcessToPattern(pattern,
helper_->mock_render_process_id());
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_FAILED;
worker->AddListener(this);
// Start the worker.
base::RunLoop run_loop;
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id, pattern, url);
worker->Start(
std::move(params), CreateProviderInfoGetter(), CreateEventDispatcher(),
CreateController(), GetInstalledScriptsInfoPtr(),
GetServiceWorkerHostPtrInfo(),
base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure()));
run_loop.Run();
// Detach.
int process_id = worker->process_id();
worker->Detach();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
// Send the registry a message from the detached worker. Nothing should
// happen.
embedded_worker_registry()->OnWorkerStarted(process_id,
worker->embedded_worker_id());
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
}
// Test for when sending the start IPC failed.
TEST_F(EmbeddedWorkerInstanceTest, FailToSendStartIPC) {
const GURL pattern("http://example.com/");
const GURL url("http://example.com/worker.js");
// Let StartWorker fail; mojo IPC fails to connect to a remote interface.
helper_->RegisterMockInstanceClient(nullptr);
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(pattern, url);
const int64_t version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
helper_->SimulateAddProcessToPattern(pattern,
helper_->mock_render_process_id());
worker->AddListener(this);
// Attempt to start the worker.
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id, pattern, url);
worker->Start(std::move(params), CreateProviderInfoGetter(),
CreateEventDispatcher(), CreateController(),
GetInstalledScriptsInfoPtr(), GetServiceWorkerHostPtrInfo(),
base::BindOnce(&ServiceWorkerUtils::NoOpStatusCallback));
base::RunLoop().RunUntilIdle();
// Worker should handle the failure of binding on the remote side as detach.
ASSERT_EQ(3u, events_.size());
EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
EXPECT_EQ(START_WORKER_MESSAGE_SENT, events_[1].type);
EXPECT_EQ(DETACHED, events_[2].type);
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, events_[2].status);
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
}
class FailEmbeddedWorkerInstanceClientImpl
: public EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient {
public:
explicit FailEmbeddedWorkerInstanceClientImpl(
base::WeakPtr<EmbeddedWorkerTestHelper> helper)
: EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient(helper) {}
private:
void StartWorker(
const EmbeddedWorkerStartParams&,
mojom::ServiceWorkerEventDispatcherRequest,
mojom::ControllerServiceWorkerRequest,
blink::mojom::ServiceWorkerInstalledScriptsInfoPtr /* unused */,
blink::mojom::ServiceWorkerHostAssociatedPtrInfo,
mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo,
mojom::ServiceWorkerProviderInfoForStartWorkerPtr,
blink::mojom::WorkerContentSettingsProxyPtr content_settings_proxy)
override {
helper_->mock_instance_clients()->clear();
}
};
TEST_F(EmbeddedWorkerInstanceTest, RemoveRemoteInterface) {
const GURL pattern("http://example.com/");
const GURL url("http://example.com/worker.js");
// Let StartWorker fail; binding is discarded in the middle of IPC
helper_->RegisterMockInstanceClient(
std::make_unique<FailEmbeddedWorkerInstanceClientImpl>(
helper_->AsWeakPtr()));
ASSERT_EQ(mock_instance_clients()->size(), 1UL);
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(pattern, url);
const int64_t version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
helper_->SimulateAddProcessToPattern(pattern,
helper_->mock_render_process_id());
worker->AddListener(this);
// Attempt to start the worker.
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id, pattern, url);
worker->Start(std::move(params), CreateProviderInfoGetter(),
CreateEventDispatcher(), CreateController(),
GetInstalledScriptsInfoPtr(), GetServiceWorkerHostPtrInfo(),
base::BindOnce(&ServiceWorkerUtils::NoOpStatusCallback));
base::RunLoop().RunUntilIdle();
// Worker should handle the sudden shutdown as detach.
ASSERT_EQ(3u, events_.size());
EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
EXPECT_EQ(START_WORKER_MESSAGE_SENT, events_[1].type);
EXPECT_EQ(DETACHED, events_[2].type);
EXPECT_EQ(EmbeddedWorkerStatus::STARTING, events_[2].status);
}
class StoreMessageInstanceClient
: public EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient {
public:
explicit StoreMessageInstanceClient(
base::WeakPtr<EmbeddedWorkerTestHelper> helper)
: EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient(helper) {}
const std::vector<std::pair<blink::WebConsoleMessage::Level, std::string>>&
message() {
return messages_;
}
private:
void AddMessageToConsole(blink::WebConsoleMessage::Level level,
const std::string& message) override {
messages_.push_back(std::make_pair(level, message));
}
std::vector<std::pair<blink::WebConsoleMessage::Level, std::string>>
messages_;
};
TEST_F(EmbeddedWorkerInstanceTest, AddMessageToConsole) {
const GURL pattern("http://example.com/");
const GURL url("http://example.com/worker.js");
std::unique_ptr<StoreMessageInstanceClient> instance_client =
std::make_unique<StoreMessageInstanceClient>(helper_->AsWeakPtr());
StoreMessageInstanceClient* instance_client_rawptr = instance_client.get();
helper_->RegisterMockInstanceClient(std::move(instance_client));
ASSERT_EQ(mock_instance_clients()->size(), 1UL);
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(pattern, url);
const int64_t version_id = pair.second->version_id();
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
helper_->SimulateAddProcessToPattern(pattern,
helper_->mock_render_process_id());
worker->AddListener(this);
// Attempt to start the worker and immediate AddMessageToConsole should not
// cause a crash.
std::pair<blink::WebConsoleMessage::Level, std::string> test_message =
std::make_pair(blink::WebConsoleMessage::kLevelVerbose, "");
std::unique_ptr<EmbeddedWorkerStartParams> params =
CreateStartParams(version_id, pattern, url);
worker->Start(std::move(params), CreateProviderInfoGetter(),
CreateEventDispatcher(), CreateController(),
GetInstalledScriptsInfoPtr(), GetServiceWorkerHostPtrInfo(),
base::BindOnce(&ServiceWorkerUtils::NoOpStatusCallback));
worker->AddMessageToConsole(test_message.first, test_message.second);
base::RunLoop().RunUntilIdle();
// Messages sent before sending StartWorker message won't be dispatched.
ASSERT_EQ(0UL, instance_client_rawptr->message().size());
ASSERT_EQ(3UL, events_.size());
EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
EXPECT_EQ(START_WORKER_MESSAGE_SENT, events_[1].type);
EXPECT_EQ(STARTED, events_[2].type);
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, worker->status());
worker->AddMessageToConsole(test_message.first, test_message.second);
base::RunLoop().RunUntilIdle();
// Messages sent after sending StartWorker message should be reached to
// the renderer.
ASSERT_EQ(1UL, instance_client_rawptr->message().size());
EXPECT_EQ(test_message, instance_client_rawptr->message()[0]);
// Ensure the worker is stopped.
worker->Stop();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
}
// Test that SendStartWorker checks if dispatcher host exists.
TEST_F(EmbeddedWorkerInstanceTest, NoDispatcherHost) {
const GURL scope("http://example.com/");
const GURL url("http://example.com/worker.js");
RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
std::unique_ptr<EmbeddedWorkerInstance> worker =
embedded_worker_registry()->CreateWorker(pair.second.get());
SetWorkerStatus(worker.get(), EmbeddedWorkerStatus::STARTING);
auto params = std::make_unique<EmbeddedWorkerStartParams>();
ServiceWorkerStatusCode result =
SimulateSendStartWorker(worker.get(), std::move(params));
EXPECT_EQ(SERVICE_WORKER_ERROR_IPC_FAILED, result);
// Set to STOPPED because EWInstance's destructor DCHECKs status.
SetWorkerStatus(worker.get(), EmbeddedWorkerStatus::STOPPED);
}
} // namespace content