// 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 #include "base/feature_list.h" #include "base/synchronization/waitable_event.h" #include "base/test/scoped_feature_list.h" #include "mojo/public/cpp/bindings/pending_associated_remote.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/remote.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/messaging/message_port_channel.h" #include "third_party/blink/public/mojom/service_worker/controller_service_worker_mode.mojom-blink.h" #include "third_party/blink/public/mojom/service_worker/service_worker.mojom-blink.h" #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom-blink.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_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/renderer/platform/loader/fetch/resource_error.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/scheduler/public/thread.h" #include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h" #include "third_party/blink/renderer/platform/testing/testing_platform_support.h" #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" #include "third_party/blink/renderer/platform/testing/url_test_helpers.h" namespace blink { namespace { const char* kNotFoundScriptURL = "https://www.example.com/sw-404.js"; // A fake WebURLLoader which is used for off-main-thread script fetch tests. class FakeWebURLLoader final : public WebURLLoader { public: FakeWebURLLoader() {} ~FakeWebURLLoader() override = default; void LoadSynchronously(const WebURLRequest&, WebURLLoaderClient*, WebURLResponse&, base::Optional&, WebData&, int64_t& encoded_data_length, int64_t& encoded_body_length, WebBlobInfo& downloaded_blob) override { NOTREACHED(); } void LoadAsynchronously(const WebURLRequest& request, WebURLLoaderClient* client) override { if (request.Url().GetString() == kNotFoundScriptURL) { WebURLResponse response; response.SetMimeType("text/javascript"); response.SetHttpStatusCode(404); client->DidReceiveResponse(response); client->DidFinishLoading(base::TimeTicks(), 0, 0, 0, false); return; } // Don't handle other requests intentionally to emulate ongoing load. } void SetDefersLoading(bool defers) override {} void DidChangePriority(WebURLRequest::Priority, int) override {} scoped_refptr GetTaskRunner() override { return base::MakeRefCounted(); } }; // A fake WebURLLoaderFactory which is used for off-main-thread script fetch // tests. class FakeWebURLLoaderFactory final : public WebURLLoaderFactory { public: std::unique_ptr CreateURLLoader( const WebURLRequest&, std::unique_ptr) override { return std::make_unique(); } }; // A fake WebWorkerFetchContext which is used for off-main-thread script fetch // tests. class FakeWebWorkerFetchContext final : public WebWorkerFetchContext { public: void SetTerminateSyncLoadEvent(base::WaitableEvent*) override {} void InitializeOnWorkerThread(AcceptLanguagesWatcher*) override {} WebURLLoaderFactory* GetURLLoaderFactory() override { return &fake_web_url_loader_factory_; } std::unique_ptr WrapURLLoaderFactory( mojo::ScopedMessagePipeHandle url_loader_factory_handle) override { return nullptr; } void WillSendRequest(WebURLRequest&) override {} mojom::ControllerServiceWorkerMode GetControllerServiceWorkerMode() const override { return mojom::ControllerServiceWorkerMode::kNoController; } WebURL SiteForCookies() const override { return WebURL(); } base::Optional TopFrameOrigin() const override { return base::Optional(); } WebString GetAcceptLanguages() const override { return WebString(); } mojo::ScopedMessagePipeHandle TakePendingWorkerTimingReceiver( int request_id) override { return {}; } private: FakeWebURLLoaderFactory fake_web_url_loader_factory_; }; class MockServiceWorkerContextClient final : public WebServiceWorkerContextClient { public: MockServiceWorkerContextClient() = default; ~MockServiceWorkerContextClient() override = default; MOCK_METHOD2(WorkerReadyForInspectionOnInitiatorThread, void(mojo::ScopedMessagePipeHandle, mojo::ScopedMessagePipeHandle)); void WorkerContextStarted(WebServiceWorkerContextProxy* proxy, scoped_refptr) override { mojo::PendingAssociatedRemote host_remote; auto host_receiver = host_remote.InitWithNewEndpointAndPassReceiver(); mojo::PendingAssociatedRemote< mojom::blink::ServiceWorkerRegistrationObjectHost> registration_object_host; auto registration_object_host_receiver = registration_object_host.InitWithNewEndpointAndPassReceiver(); mojo::PendingAssociatedRemote registration_object; mojo::PendingAssociatedRemote service_worker_object_host; auto service_worker_object_host_receiver = service_worker_object_host.InitWithNewEndpointAndPassReceiver(); mojo::PendingAssociatedRemote service_worker_object; // Simulates calling blink.mojom.ServiceWorker.InitializeGlobalScope() to // unblock the service worker script evaluation. mojo::Remote service_worker; proxy->BindServiceWorker( service_worker.BindNewPipeAndPassReceiver().PassPipe()); service_worker->InitializeGlobalScope( std::move(host_remote), mojom::blink::ServiceWorkerRegistrationObjectInfo::New( 2 /* registration_id */, KURL("https://example.com"), mojom::blink::ServiceWorkerUpdateViaCache::kImports, std::move(registration_object_host), registration_object.InitWithNewEndpointAndPassReceiver(), nullptr, nullptr, nullptr), mojom::blink::ServiceWorkerObjectInfo::New( 1 /* service_worker_version_id */, mojom::blink::ServiceWorkerState::kParsed, KURL("https://example.com"), std::move(service_worker_object_host), service_worker_object.InitWithNewEndpointAndPassReceiver()), mojom::blink::FetchHandlerExistence::EXISTS); // To make the other side callable. mojo::AssociateWithDisconnectedPipe(host_receiver.PassHandle()); mojo::AssociateWithDisconnectedPipe( registration_object_host_receiver.PassHandle()); mojo::AssociateWithDisconnectedPipe( service_worker_object_host_receiver.PassHandle()); } void FailedToFetchClassicScript() override { classic_script_load_failure_event_.Signal(); } void DidEvaluateScript(bool /* success */) override { script_evaluated_event_.Signal(); } scoped_refptr CreateWorkerFetchContextOnInitiatorThread() override { return base::MakeRefCounted(); } void WorkerContextDestroyed() override { termination_event_.Signal(); } // These methods must be called on the main thread. void WaitUntilScriptEvaluated() { script_evaluated_event_.Wait(); } void WaitUntilThreadTermination() { termination_event_.Wait(); } void WaitUntilFailedToLoadClassicScript() { classic_script_load_failure_event_.Wait(); } private: base::WaitableEvent script_evaluated_event_; base::WaitableEvent termination_event_; base::WaitableEvent classic_script_load_failure_event_; }; class WebEmbeddedWorkerImplTest : public testing::Test { protected: void SetUp() override { mock_client_ = std::make_unique(); worker_ = std::make_unique(mock_client_.get()); script_url_ = url_test_helpers::ToKURL("https://www.example.com/sw.js"); WebURLResponse response(script_url_); response.SetMimeType("text/javascript"); response.SetHttpStatusCode(200); url_test_helpers::RegisterMockedURLLoadWithCustomResponse(script_url_, "", response); } std::unique_ptr CreateStartData() { WebFetchClientSettingsObject outside_settings_object( network::mojom::ReferrerPolicy::kDefault, /*outgoing_referrer=*/WebURL(script_url_), blink::mojom::InsecureRequestsPolicy::kDoNotUpgrade); auto start_data = std::make_unique( std::move(outside_settings_object)); start_data->script_url = script_url_; start_data->user_agent = WebString("dummy user agent"); start_data->script_type = mojom::ScriptType::kClassic; start_data->wait_for_debugger_mode = WebEmbeddedWorkerStartData::kDontWaitForDebugger; return start_data; } void TearDown() override { // Drain queued tasks posted from the worker thread in order to avoid tasks // bound with unretained objects from running after tear down. Worker // termination may post such tasks (see https://crbug,com/1007616). // TODO(nhiroki): Stop using synchronous WaitableEvent, and instead use // QuitClosure to wait until all the tasks run before test completion. test::RunPendingTasks(); url_test_helpers::UnregisterAllURLsAndClearMemoryCache(); } WebURL script_url_; std::unique_ptr mock_client_; std::unique_ptr worker_; }; } // namespace TEST_F(WebEmbeddedWorkerImplTest, TerminateSoonAfterStart) { worker_->StartWorkerContext( CreateStartData(), /*installed_scripts_manager_params=*/nullptr, /*content_settings_proxy=*/mojo::ScopedMessagePipeHandle(), /*cache_storage_remote=*/mojo::ScopedMessagePipeHandle(), /*browser_interface_broker=*/mojo::ScopedMessagePipeHandle(), Thread::Current()->GetTaskRunner()); testing::Mock::VerifyAndClearExpectations(mock_client_.get()); // Terminate the worker immediately after start. worker_->TerminateWorkerContext(); worker_->WaitForShutdownForTesting(); } TEST_F(WebEmbeddedWorkerImplTest, TerminateWhileWaitingForDebugger) { std::unique_ptr start_data = CreateStartData(); start_data->wait_for_debugger_mode = WebEmbeddedWorkerStartData::kWaitForDebugger; worker_->StartWorkerContext( std::move(start_data), /*installed_scripts_manager_params=*/nullptr, /*content_settings_proxy=*/mojo::ScopedMessagePipeHandle(), /*cache_storage_remote=*/mojo::ScopedMessagePipeHandle(), /*browser_interface_broker=*/mojo::ScopedMessagePipeHandle(), Thread::Current()->GetTaskRunner()); testing::Mock::VerifyAndClearExpectations(mock_client_.get()); // Terminate the worker while waiting for the debugger. worker_->TerminateWorkerContext(); worker_->WaitForShutdownForTesting(); } TEST_F(WebEmbeddedWorkerImplTest, ScriptNotFound) { WebURL script_url = url_test_helpers::ToKURL(kNotFoundScriptURL); url_test_helpers::RegisterMockedErrorURLLoad(script_url); std::unique_ptr start_data = CreateStartData(); start_data->script_url = script_url; // Start worker and load the script. worker_->StartWorkerContext( std::move(start_data), /*installed_scripts_manager_params=*/nullptr, /*content_settings_proxy=*/mojo::ScopedMessagePipeHandle(), /*cache_storage_remote=*/mojo::ScopedMessagePipeHandle(), /*browser_interface_broker=*/mojo::ScopedMessagePipeHandle(), Thread::Current()->GetTaskRunner()); testing::Mock::VerifyAndClearExpectations(mock_client_.get()); mock_client_->WaitUntilFailedToLoadClassicScript(); // Terminate the worker for cleanup. worker_->TerminateWorkerContext(); worker_->WaitForShutdownForTesting(); } } // namespace blink