| // Copyright 2015 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 "third_party/blink/renderer/modules/exported/web_embedded_worker_impl.h" |
| |
| #include <memory> |
| |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/messaging/message_port_channel.h" |
| #include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom-blink.h" |
| #include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/web_content_settings_client.h" |
| #include "third_party/blink/public/platform/web_url_loader_mock_factory.h" |
| #include "third_party/blink/public/platform/web_url_response.h" |
| #include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_client.h" |
| #include "third_party/blink/public/web/modules/service_worker/web_service_worker_context_proxy.h" |
| #include "third_party/blink/public/web/web_embedded_worker_start_data.h" |
| #include "third_party/blink/public/web/web_settings.h" |
| #include "third_party/blink/renderer/modules/service_worker/service_worker_installed_scripts_manager.h" |
| #include "third_party/blink/renderer/modules/service_worker/thread_safe_script_container.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_error.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" |
| #include "third_party/blink/renderer/platform/testing/url_test_helpers.h" |
| #include "third_party/blink/renderer/platform/waitable_event.h" |
| |
| namespace blink { |
| namespace { |
| |
| // Fake network provider for service worker execution contexts. |
| class FakeServiceWorkerNetworkProvider |
| : public WebServiceWorkerNetworkProvider { |
| public: |
| FakeServiceWorkerNetworkProvider() = default; |
| ~FakeServiceWorkerNetworkProvider() override = default; |
| |
| // Return a loader from the mock factory. In production code, this uses the |
| // factory provided at worker startup to load non-installed scripts via |
| // ServiceWorkerScriptLoaderFactory. |
| std::unique_ptr<WebURLLoader> CreateURLLoader( |
| const WebURLRequest& request, |
| std::unique_ptr<scheduler::WebResourceLoadingTaskRunnerHandle>) override { |
| return Platform::Current()->GetURLLoaderMockFactory()->CreateURLLoader( |
| nullptr); |
| } |
| }; |
| |
| class MockServiceWorkerContextClient : public WebServiceWorkerContextClient { |
| public: |
| MockServiceWorkerContextClient() = default; |
| ~MockServiceWorkerContextClient() override = default; |
| |
| MOCK_METHOD0(WorkerReadyForInspection, void()); |
| MOCK_METHOD0(WorkerContextFailedToStart, void()); |
| MOCK_METHOD0(WorkerScriptLoaded, void()); |
| |
| void WorkerContextStarted(WebServiceWorkerContextProxy* proxy) override { |
| // In production code, ReadyToEvaluateScript() is called when |
| // ServiceWorkerContextClient receives the InitializeGlobalScope() IPC |
| // message. |
| proxy->ReadyToEvaluateScript(); |
| } |
| void DidEvaluateScript(bool /* success */) override { |
| script_evaluated_event_.Signal(); |
| } |
| |
| std::unique_ptr<WebServiceWorkerNetworkProvider> |
| CreateServiceWorkerNetworkProvider() override { |
| return std::make_unique<FakeServiceWorkerNetworkProvider>(); |
| } |
| void WorkerContextDestroyed() override { termination_event_.Signal(); } |
| |
| void WaitUntilScriptEvaluated() { script_evaluated_event_.Wait(); } |
| void WaitUntilThreadTermination() { termination_event_.Wait(); } |
| |
| private: |
| WaitableEvent script_evaluated_event_; |
| WaitableEvent termination_event_; |
| }; |
| |
| class MockServiceWorkerInstalledScriptsManager |
| : public ServiceWorkerInstalledScriptsManager { |
| public: |
| MockServiceWorkerInstalledScriptsManager() |
| : ServiceWorkerInstalledScriptsManager( |
| Vector<KURL>() /* installed_urls */, |
| mojom::blink::ServiceWorkerInstalledScriptsManagerRequest( |
| mojo::MessagePipe().handle1), |
| mojom::blink::ServiceWorkerInstalledScriptsManagerHostPtrInfo( |
| mojo::MessagePipe().handle0, |
| mojom::blink::ServiceWorkerInstalledScriptsManagerHost:: |
| Version_), |
| // Pass a temporary task runner to ensure |
| // ServiceWorkerInstalledScriptsManager construction succeeds. |
| Platform::Current() |
| ->CreateThread(ThreadCreationParams(WebThreadType::kTestThread) |
| .SetThreadNameForTest("io thread")) |
| ->GetTaskRunner()){}; |
| MOCK_CONST_METHOD1(IsScriptInstalled, bool(const KURL& script_url)); |
| MOCK_METHOD1(GetRawScriptData, |
| std::unique_ptr<ThreadSafeScriptContainer::RawScriptData>( |
| const KURL& script_url)); |
| }; |
| |
| class WebEmbeddedWorkerImplTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| auto client = std::make_unique<MockServiceWorkerContextClient>(); |
| auto installed_scripts_manager = |
| std::make_unique<MockServiceWorkerInstalledScriptsManager>(); |
| mock_client_ = client.get(); |
| mock_installed_scripts_manager_ = installed_scripts_manager.get(); |
| worker_ = WebEmbeddedWorkerImpl::CreateForTesting( |
| std::move(client), std::move(installed_scripts_manager)); |
| |
| WebURL script_url = |
| url_test_helpers::ToKURL("https://www.example.com/sw.js"); |
| WebURLResponse response(script_url); |
| response.SetMIMEType("text/javascript"); |
| response.SetHTTPStatusCode(200); |
| Platform::Current()->GetURLLoaderMockFactory()->RegisterURL(script_url, |
| response, ""); |
| |
| start_data_.script_url = script_url; |
| start_data_.user_agent = WebString("dummy user agent"); |
| start_data_.script_type = mojom::ScriptType::kClassic; |
| start_data_.pause_after_download_mode = |
| WebEmbeddedWorkerStartData::kDontPauseAfterDownload; |
| start_data_.wait_for_debugger_mode = |
| WebEmbeddedWorkerStartData::kDontWaitForDebugger; |
| start_data_.v8_cache_options = WebSettings::V8CacheOptions::kDefault; |
| } |
| |
| void TearDown() override { |
| Platform::Current() |
| ->GetURLLoaderMockFactory() |
| ->UnregisterAllURLsAndClearMemoryCache(); |
| } |
| |
| WebEmbeddedWorkerStartData start_data_; |
| MockServiceWorkerContextClient* mock_client_; |
| MockServiceWorkerInstalledScriptsManager* mock_installed_scripts_manager_; |
| std::unique_ptr<WebEmbeddedWorkerImpl> worker_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(WebEmbeddedWorkerImplTest, TerminateSoonAfterStart) { |
| EXPECT_CALL(*mock_client_, WorkerReadyForInspection()).Times(1); |
| worker_->StartWorkerContext(start_data_); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| |
| EXPECT_CALL(*mock_client_, WorkerContextFailedToStart()).Times(1); |
| worker_->TerminateWorkerContext(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| } |
| |
| TEST_F(WebEmbeddedWorkerImplTest, TerminateWhileWaitingForDebugger) { |
| EXPECT_CALL(*mock_client_, WorkerReadyForInspection()).Times(1); |
| start_data_.wait_for_debugger_mode = |
| WebEmbeddedWorkerStartData::kWaitForDebugger; |
| worker_->StartWorkerContext(start_data_); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| |
| EXPECT_CALL(*mock_client_, WorkerContextFailedToStart()).Times(1); |
| worker_->TerminateWorkerContext(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| } |
| |
| TEST_F(WebEmbeddedWorkerImplTest, TerminateWhileLoadingScript) { |
| EXPECT_CALL(*mock_client_, WorkerReadyForInspection()).Times(1); |
| worker_->StartWorkerContext(start_data_); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| |
| // Load the shadow page. |
| EXPECT_CALL(*mock_installed_scripts_manager_, |
| IsScriptInstalled(KURL(start_data_.script_url))) |
| .Times(testing::AtLeast(1)) |
| .WillRepeatedly(testing::Return(false)); |
| Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| testing::Mock::VerifyAndClearExpectations(mock_installed_scripts_manager_); |
| |
| // Terminate before loading the script. |
| EXPECT_CALL(*mock_client_, WorkerContextFailedToStart()).Times(1); |
| worker_->TerminateWorkerContext(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| } |
| |
| TEST_F(WebEmbeddedWorkerImplTest, TerminateWhilePausedAfterDownload) { |
| EXPECT_CALL(*mock_client_, WorkerReadyForInspection()).Times(1); |
| start_data_.pause_after_download_mode = |
| WebEmbeddedWorkerStartData::kPauseAfterDownload; |
| worker_->StartWorkerContext(start_data_); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| |
| // Load the shadow page. |
| EXPECT_CALL(*mock_installed_scripts_manager_, |
| IsScriptInstalled(KURL(start_data_.script_url))) |
| .Times(testing::AtLeast(1)) |
| .WillRepeatedly(testing::Return(false)); |
| |
| Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| testing::Mock::VerifyAndClearExpectations(mock_installed_scripts_manager_); |
| |
| // Load the script. |
| EXPECT_CALL(*mock_client_, WorkerScriptLoaded()).Times(1); |
| Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| |
| // Terminate before resuming after download. |
| EXPECT_CALL(*mock_client_, WorkerContextFailedToStart()).Times(1); |
| worker_->TerminateWorkerContext(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| } |
| |
| TEST_F(WebEmbeddedWorkerImplTest, ScriptNotFound) { |
| WebURL script_url = |
| url_test_helpers::ToKURL("https://www.example.com/sw-404.js"); |
| WebURLResponse response; |
| response.SetMIMEType("text/javascript"); |
| response.SetHTTPStatusCode(404); |
| ResourceError error = ResourceError::Failure(script_url); |
| Platform::Current()->GetURLLoaderMockFactory()->RegisterErrorURL( |
| script_url, response, error); |
| start_data_.script_url = script_url; |
| |
| EXPECT_CALL(*mock_client_, WorkerReadyForInspection()).Times(1); |
| worker_->StartWorkerContext(start_data_); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| |
| // Load the shadow page. |
| EXPECT_CALL(*mock_installed_scripts_manager_, |
| IsScriptInstalled(KURL(start_data_.script_url))) |
| .Times(testing::AtLeast(1)) |
| .WillRepeatedly(testing::Return(false)); |
| |
| Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| testing::Mock::VerifyAndClearExpectations(mock_installed_scripts_manager_); |
| |
| // Load the script. |
| EXPECT_CALL(*mock_client_, WorkerScriptLoaded()).Times(0); |
| EXPECT_CALL(*mock_client_, WorkerContextFailedToStart()).Times(1); |
| Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| } |
| |
| // The running worker is detected as a memory leak. crbug.com/586897 and |
| // crbug.com/807754. |
| #if defined(ADDRESS_SANITIZER) |
| #define MAYBE_DontPauseAfterDownload DISABLED_DontPauseAfterDownload |
| #else |
| #define MAYBE_DontPauseAfterDownload DontPauseAfterDownload |
| #endif |
| TEST_F(WebEmbeddedWorkerImplTest, MAYBE_DontPauseAfterDownload) { |
| EXPECT_CALL(*mock_client_, WorkerReadyForInspection()).Times(1); |
| worker_->StartWorkerContext(start_data_); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| |
| // Load the shadow page. |
| EXPECT_CALL(*mock_installed_scripts_manager_, |
| IsScriptInstalled(KURL(start_data_.script_url))) |
| .Times(testing::AtLeast(1)) |
| .WillRepeatedly(testing::Return(false)); |
| Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| testing::Mock::VerifyAndClearExpectations(mock_installed_scripts_manager_); |
| |
| // Load the script. |
| EXPECT_CALL(*mock_client_, WorkerScriptLoaded()).Times(1); |
| // This is called on the worker thread. |
| EXPECT_CALL(*mock_installed_scripts_manager_, |
| IsScriptInstalled(KURL(start_data_.script_url))) |
| .Times(testing::AtLeast(1)) |
| .WillRepeatedly(testing::Return(false)); |
| Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); |
| mock_client_->WaitUntilScriptEvaluated(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| testing::Mock::VerifyAndClearExpectations(mock_installed_scripts_manager_); |
| |
| // Terminate the running worker thread. |
| EXPECT_CALL(*mock_client_, WorkerContextFailedToStart()).Times(0); |
| worker_->TerminateWorkerContext(); |
| mock_client_->WaitUntilThreadTermination(); |
| } |
| |
| // The running worker is detected as a memory leak. crbug.com/586897 and |
| // crbug.com/807754. |
| #if defined(ADDRESS_SANITIZER) |
| #define MAYBE_PauseAfterDownload DISABLED_PauseAfterDownload |
| #else |
| #define MAYBE_PauseAfterDownload PauseAfterDownload |
| #endif |
| TEST_F(WebEmbeddedWorkerImplTest, MAYBE_PauseAfterDownload) { |
| EXPECT_CALL(*mock_client_, WorkerReadyForInspection()).Times(1); |
| start_data_.pause_after_download_mode = |
| WebEmbeddedWorkerStartData::kPauseAfterDownload; |
| worker_->StartWorkerContext(start_data_); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| |
| // Load the shadow page. |
| EXPECT_CALL(*mock_installed_scripts_manager_, |
| IsScriptInstalled(KURL(start_data_.script_url))) |
| .Times(testing::AtLeast(1)) |
| .WillRepeatedly(testing::Return(false)); |
| Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| testing::Mock::VerifyAndClearExpectations(mock_installed_scripts_manager_); |
| |
| // Load the script. |
| EXPECT_CALL(*mock_client_, WorkerScriptLoaded()).Times(1); |
| Platform::Current()->GetURLLoaderMockFactory()->ServeAsynchronousRequests(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| |
| // Resume after download. |
| // This is called on the worker thread. |
| EXPECT_CALL(*mock_installed_scripts_manager_, |
| IsScriptInstalled(KURL(start_data_.script_url))) |
| .Times(testing::AtLeast(1)) |
| .WillRepeatedly(testing::Return(false)); |
| worker_->ResumeAfterDownload(); |
| mock_client_->WaitUntilScriptEvaluated(); |
| testing::Mock::VerifyAndClearExpectations(mock_client_); |
| testing::Mock::VerifyAndClearExpectations(mock_installed_scripts_manager_); |
| |
| // Terminate the running worker thread. |
| EXPECT_CALL(*mock_client_, WorkerContextFailedToStart()).Times(0); |
| worker_->TerminateWorkerContext(); |
| mock_client_->WaitUntilThreadTermination(); |
| } |
| |
| } // namespace blink |