| // 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/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/service_worker_event_dispatcher.mojom.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/blink/public/mojom/service_worker/service_worker.mojom.h" |
| #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| void SaveStatusAndCall(blink::ServiceWorkerStatusCode* out, |
| base::OnceClosure callback, |
| blink::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(); |
| 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 EnsureControllerServiceWorker( |
| mojom::ControllerServiceWorkerRequest request, |
| mojom::ControllerServiceWorkerPurpose purpose) override { |
| NOTIMPLEMENTED(); |
| } |
| void CloneForWorker( |
| mojom::ServiceWorkerContainerHostRequest request) override { |
| NOTIMPLEMENTED(); |
| } |
| void Ping(PingCallback callback) override { NOTIMPLEMENTED(); } |
| |
| mojom::ServiceWorkerContainerAssociatedPtr client_; |
| mojo::AssociatedBinding<mojom::ServiceWorkerContainerHost> binding_; |
| |
| 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); |
| } |
| |
| 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; |
| } |
| |
| blink::ServiceWorkerStatusCode StartWorker(EmbeddedWorkerInstance* worker, |
| int id, |
| const GURL& pattern, |
| const GURL& url) { |
| blink::ServiceWorkerStatusCode status; |
| base::RunLoop run_loop; |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(id, pattern, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure())); |
| run_loop.Run(); |
| return status; |
| } |
| |
| mojom::EmbeddedWorkerStartParamsPtr |
| CreateStartParams(int version_id, const GURL& scope, const GURL& script_url) { |
| auto params = mojom::EmbeddedWorkerStartParams::New(); |
| params->service_worker_version_id = version_id; |
| params->scope = scope; |
| params->script_url = script_url; |
| params->pause_after_download = false; |
| params->is_installed = false; |
| |
| params->dispatcher_request = CreateEventDispatcher(); |
| params->controller_request = CreateController(); |
| params->installed_scripts_info = GetInstalledScriptsInfoPtr(); |
| return params; |
| } |
| |
| mojom::ServiceWorkerProviderInfoForStartWorkerPtr CreateProviderInfo( |
| int /* process_id */, |
| scoped_refptr<network::SharedURLLoaderFactory>) { |
| 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; |
| } |
| |
| 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; |
| } |
| |
| ServiceWorkerContextCore* context() { return helper_->context(); } |
| |
| EmbeddedWorkerRegistry* embedded_worker_registry() { |
| DCHECK(context()); |
| return context()->embedded_worker_registry(); |
| } |
| |
| 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<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, |
| 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(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->AddObserver(this); |
| |
| // Start should succeed. |
| blink::ServiceWorkerStatusCode status; |
| base::RunLoop run_loop; |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(service_worker_version_id, pattern, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure())); |
| EXPECT_EQ(EmbeddedWorkerStatus::STARTING, worker->status()); |
| run_loop.Run(); |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, 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()); |
| |
| { |
| // Start once normally. |
| blink::ServiceWorkerStatusCode status = |
| blink::ServiceWorkerStatusCode::kMax; |
| base::RunLoop run_loop; |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(service_worker_version_id, pattern, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure())); |
| run_loop.Run(); |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, 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, blink::ServiceWorkerStatusCode::kErrorFailed); |
| context()->UpdateVersionFailureCount( |
| service_worker_version_id, blink::ServiceWorkerStatusCode::kErrorFailed); |
| |
| { |
| // Start again. |
| blink::ServiceWorkerStatusCode status; |
| base::RunLoop run_loop; |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(service_worker_version_id, pattern, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure())); |
| EXPECT_EQ(EmbeddedWorkerStatus::STARTING, worker->status()); |
| run_loop.Run(); |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, 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()); |
| |
| // Start the worker and then call StopIfNotAttachedToDevTools(). |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, |
| 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(blink::ServiceWorkerStatusCode::kOk, |
| 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(); |
| |
| { |
| // Start worker1. |
| blink::ServiceWorkerStatusCode status; |
| base::RunLoop run_loop; |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id1, pattern, url); |
| worker1->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure())); |
| run_loop.Run(); |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status); |
| } |
| |
| { |
| // Start worker2. |
| blink::ServiceWorkerStatusCode status; |
| base::RunLoop run_loop; |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id2, pattern, url); |
| worker2->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, run_loop.QuitClosure())); |
| run_loop.Run(); |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, 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->AddObserver(this); |
| |
| // Run the start worker sequence and detach during process allocation. |
| blink::ServiceWorkerStatusCode status = blink::ServiceWorkerStatusCode::kMax; |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id, scope, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, base::DoNothing::Once<>())); |
| 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(blink::ServiceWorkerStatusCode::kMax, 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->AddObserver(this); |
| |
| // Run the start worker sequence until a start worker message is sent. |
| blink::ServiceWorkerStatusCode status = blink::ServiceWorkerStatusCode::kMax; |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id, scope, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, base::DoNothing::Once<>())); |
| 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(blink::ServiceWorkerStatusCode::kMax, 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->AddObserver(this); |
| |
| // Stop the start worker sequence before a process is allocated. |
| blink::ServiceWorkerStatusCode status = blink::ServiceWorkerStatusCode::kMax; |
| |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id, scope, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, base::DoNothing::Once<>())); |
| 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(blink::ServiceWorkerStatusCode::kMax, 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 = blink::ServiceWorkerStatusCode::kMax; |
| std::unique_ptr<base::RunLoop> run_loop(new base::RunLoop); |
| params = CreateStartParams(version_id, scope, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, run_loop->QuitClosure())); |
| run_loop->Run(); |
| |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, 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->AddObserver(this); |
| |
| // Run the start worker sequence until pause after download. |
| blink::ServiceWorkerStatusCode status = blink::ServiceWorkerStatusCode::kMax; |
| |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id, scope, url); |
| params->pause_after_download = true; |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, base::DoNothing::Once<>())); |
| 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->AddObserver(this); |
| |
| // Run the start worker sequence until a start worker message is sent. |
| blink::ServiceWorkerStatusCode status = blink::ServiceWorkerStatusCode::kMax; |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id, scope, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, base::DoNothing::Once<>())); |
| 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(blink::ServiceWorkerStatusCode::kMax, 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 = blink::ServiceWorkerStatusCode::kMax; |
| std::unique_ptr<base::RunLoop> run_loop(new base::RunLoop); |
| |
| params = CreateStartParams(version_id, scope, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| base::BindOnce(&SaveStatusAndCall, &status, run_loop->QuitClosure())); |
| run_loop->Run(); |
| |
| // The worker should be started. |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, 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()); |
| blink::ServiceWorkerStatusCode status = |
| blink::ServiceWorkerStatusCode::kErrorFailed; |
| worker->AddObserver(this); |
| |
| // Start the worker. |
| base::RunLoop run_loop; |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id, pattern, url); |
| worker->Start( |
| std::move(params), CreateProviderInfoGetter(), |
| 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()); |
| worker->AddObserver(this); |
| |
| // Attempt to start the worker. |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id, pattern, url); |
| worker->Start(std::move(params), CreateProviderInfoGetter(), |
| base::DoNothing()); |
| 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(mojom::EmbeddedWorkerStartParamsPtr) 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()); |
| worker->AddObserver(this); |
| |
| // Attempt to start the worker. |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id, pattern, url); |
| worker->Start(std::move(params), CreateProviderInfoGetter(), |
| base::DoNothing()); |
| 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()); |
| worker->AddObserver(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, ""); |
| mojom::EmbeddedWorkerStartParamsPtr params = |
| CreateStartParams(version_id, pattern, url); |
| worker->Start(std::move(params), CreateProviderInfoGetter(), |
| base::DoNothing()); |
| 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()); |
| } |
| |
| } // namespace content |