| // Copyright 2016 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 "base/single_thread_task_runner.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_cache_options.h" |
| #include "third_party/blink/renderer/core/inspector/console_message_storage.h" |
| #include "third_party/blink/renderer/core/inspector/thread_debugger.h" |
| #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h" |
| #include "third_party/blink/renderer/core/script/script.h" |
| #include "third_party/blink/renderer/core/testing/dummy_page_holder.h" |
| #include "third_party/blink/renderer/core/workers/global_scope_creation_params.h" |
| #include "third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.h" |
| #include "third_party/blink/renderer/core/workers/threaded_worklet_object_proxy.h" |
| #include "third_party/blink/renderer/core/workers/worker_thread.h" |
| #include "third_party/blink/renderer/core/workers/worker_thread_test_helper.h" |
| #include "third_party/blink/renderer/core/workers/worklet_global_scope.h" |
| #include "third_party/blink/renderer/core/workers/worklet_module_responses_map.h" |
| #include "third_party/blink/renderer/core/workers/worklet_thread_holder.h" |
| #include "third_party/blink/renderer/platform/cross_thread_functional.h" |
| #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| |
| namespace blink { |
| |
| class ThreadedWorkletObjectProxyForTest final |
| : public ThreadedWorkletObjectProxy { |
| public: |
| ThreadedWorkletObjectProxyForTest( |
| ThreadedWorkletMessagingProxy* messaging_proxy, |
| ParentExecutionContextTaskRunners* parent_execution_context_task_runners) |
| : ThreadedWorkletObjectProxy(messaging_proxy, |
| parent_execution_context_task_runners), |
| reported_features_(static_cast<int>(WebFeature::kNumberOfFeatures)) {} |
| |
| protected: |
| void CountFeature(WebFeature feature) override { |
| // Any feature should be reported only one time. |
| EXPECT_FALSE(reported_features_.QuickGet(static_cast<int>(feature))); |
| reported_features_.QuickSet(static_cast<int>(feature)); |
| ThreadedWorkletObjectProxy::CountFeature(feature); |
| } |
| |
| void CountDeprecation(WebFeature feature) final { |
| // Any feature should be reported only one time. |
| EXPECT_FALSE(reported_features_.QuickGet(static_cast<int>(feature))); |
| reported_features_.QuickSet(static_cast<int>(feature)); |
| ThreadedWorkletObjectProxy::CountDeprecation(feature); |
| } |
| |
| private: |
| BitVector reported_features_; |
| }; |
| |
| class ThreadedWorkletThreadForTest : public WorkerThread { |
| public: |
| explicit ThreadedWorkletThreadForTest( |
| WorkerReportingProxy& worker_reporting_proxy) |
| : WorkerThread(worker_reporting_proxy) {} |
| ~ThreadedWorkletThreadForTest() override = default; |
| |
| WorkerBackingThread& GetWorkerBackingThread() override { |
| auto* worklet_thread_holder = |
| WorkletThreadHolder<ThreadedWorkletThreadForTest>::GetInstance(); |
| DCHECK(worklet_thread_holder); |
| return *worklet_thread_holder->GetThread(); |
| } |
| |
| void ClearWorkerBackingThread() override {} |
| |
| static void EnsureSharedBackingThread() { |
| DCHECK(IsMainThread()); |
| WorkletThreadHolder<ThreadedWorkletThreadForTest>::EnsureInstance( |
| ThreadCreationParams(WebThreadType::kTestThread) |
| .SetThreadNameForTest("ThreadedWorkletThreadForTest")); |
| } |
| |
| static void ClearSharedBackingThread() { |
| DCHECK(IsMainThread()); |
| WorkletThreadHolder<ThreadedWorkletThreadForTest>::ClearInstance(); |
| } |
| |
| void TestSecurityOrigin() { |
| WorkletGlobalScope* global_scope = To<WorkletGlobalScope>(GlobalScope()); |
| // The SecurityOrigin for a worklet should be a unique opaque origin, while |
| // the owner Document's SecurityOrigin shouldn't. |
| EXPECT_TRUE(global_scope->GetSecurityOrigin()->IsOpaque()); |
| EXPECT_FALSE(global_scope->DocumentSecurityOrigin()->IsOpaque()); |
| PostCrossThreadTask( |
| *GetParentExecutionContextTaskRunners()->Get(TaskType::kInternalTest), |
| FROM_HERE, CrossThreadBind(&test::ExitRunLoop)); |
| } |
| |
| void TestContentSecurityPolicy() { |
| EXPECT_TRUE(IsCurrentThread()); |
| ContentSecurityPolicy* csp = GlobalScope()->GetContentSecurityPolicy(); |
| |
| // The "script-src 'self'" directive allows this. |
| EXPECT_TRUE(csp->AllowScriptFromSource(GlobalScope()->Url(), String(), |
| IntegrityMetadataSet(), |
| kParserInserted)); |
| |
| // The "script-src https://allowed.example.com" should allow this. |
| EXPECT_TRUE(csp->AllowScriptFromSource(KURL("https://allowed.example.com"), |
| String(), IntegrityMetadataSet(), |
| kParserInserted)); |
| |
| EXPECT_FALSE(csp->AllowScriptFromSource( |
| KURL("https://disallowed.example.com"), String(), |
| IntegrityMetadataSet(), kParserInserted)); |
| |
| PostCrossThreadTask( |
| *GetParentExecutionContextTaskRunners()->Get(TaskType::kInternalTest), |
| FROM_HERE, CrossThreadBind(&test::ExitRunLoop)); |
| } |
| |
| // Test that having an invalid CSP does not result in an exception. |
| // See bugs: 844383,844317 |
| void TestInvalidContentSecurityPolicy() { |
| EXPECT_TRUE(IsCurrentThread()); |
| |
| // At this point check that the CSP that was set is indeed invalid. |
| ContentSecurityPolicy* csp = GlobalScope()->GetContentSecurityPolicy(); |
| EXPECT_EQ(1ul, csp->Headers().size()); |
| EXPECT_EQ("invalid-csp", csp->Headers().at(0).first); |
| EXPECT_EQ(kContentSecurityPolicyHeaderTypeEnforce, |
| csp->Headers().at(0).second); |
| |
| PostCrossThreadTask( |
| *GetParentExecutionContextTaskRunners()->Get(TaskType::kInternalTest), |
| FROM_HERE, CrossThreadBind(&test::ExitRunLoop)); |
| } |
| |
| // Emulates API use on threaded WorkletGlobalScope. |
| void CountFeature(WebFeature feature) { |
| EXPECT_TRUE(IsCurrentThread()); |
| GlobalScope()->CountFeature(feature); |
| PostCrossThreadTask( |
| *GetParentExecutionContextTaskRunners()->Get(TaskType::kInternalTest), |
| FROM_HERE, CrossThreadBind(&test::ExitRunLoop)); |
| } |
| |
| // Emulates deprecated API use on threaded WorkletGlobalScope. |
| void CountDeprecation(WebFeature feature) { |
| EXPECT_TRUE(IsCurrentThread()); |
| GlobalScope()->CountDeprecation(feature); |
| |
| // countDeprecation() should add a warning message. |
| EXPECT_EQ(1u, GetConsoleMessageStorage()->size()); |
| String console_message = GetConsoleMessageStorage()->at(0)->Message(); |
| EXPECT_TRUE(console_message.Contains("deprecated")); |
| |
| PostCrossThreadTask( |
| *GetParentExecutionContextTaskRunners()->Get(TaskType::kInternalTest), |
| FROM_HERE, CrossThreadBind(&test::ExitRunLoop)); |
| } |
| |
| void TestTaskRunner() { |
| EXPECT_TRUE(IsCurrentThread()); |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner = |
| GlobalScope()->GetTaskRunner(TaskType::kInternalTest); |
| EXPECT_TRUE(task_runner->RunsTasksInCurrentSequence()); |
| PostCrossThreadTask( |
| *GetParentExecutionContextTaskRunners()->Get(TaskType::kInternalTest), |
| FROM_HERE, CrossThreadBind(&test::ExitRunLoop)); |
| } |
| |
| private: |
| WorkerOrWorkletGlobalScope* CreateWorkerGlobalScope( |
| std::unique_ptr<GlobalScopeCreationParams> creation_params) final { |
| auto* global_scope = MakeGarbageCollected<WorkletGlobalScope>( |
| std::move(creation_params), GetWorkerReportingProxy(), this); |
| EXPECT_FALSE(global_scope->IsMainThreadWorkletGlobalScope()); |
| EXPECT_TRUE(global_scope->IsThreadedWorkletGlobalScope()); |
| return global_scope; |
| } |
| |
| bool IsOwningBackingThread() const final { return false; } |
| |
| WebThreadType GetThreadType() const override { |
| return WebThreadType::kUnspecifiedWorkerThread; |
| } |
| }; |
| |
| class ThreadedWorkletMessagingProxyForTest |
| : public ThreadedWorkletMessagingProxy { |
| public: |
| explicit ThreadedWorkletMessagingProxyForTest( |
| ExecutionContext* execution_context) |
| : ThreadedWorkletMessagingProxy(execution_context) { |
| worklet_object_proxy_ = std::make_unique<ThreadedWorkletObjectProxyForTest>( |
| this, GetParentExecutionContextTaskRunners()); |
| } |
| |
| ~ThreadedWorkletMessagingProxyForTest() override = default; |
| |
| void Start() { |
| Document* document = To<Document>(GetExecutionContext()); |
| std::unique_ptr<Vector<char>> cached_meta_data = nullptr; |
| WorkerClients* worker_clients = nullptr; |
| std::unique_ptr<WorkerSettings> worker_settings = nullptr; |
| InitializeWorkerThread( |
| std::make_unique<GlobalScopeCreationParams>( |
| document->Url(), mojom::ScriptType::kModule, document->UserAgent(), |
| nullptr /* web_worker_fetch_context */, |
| document->GetContentSecurityPolicy()->Headers(), |
| document->GetReferrerPolicy(), document->GetSecurityOrigin(), |
| document->IsSecureContext(), document->GetHttpsState(), |
| worker_clients, document->AddressSpace(), |
| OriginTrialContext::GetTokens(document).get(), |
| base::UnguessableToken::Create(), std::move(worker_settings), |
| kV8CacheOptionsDefault, new WorkletModuleResponsesMap), |
| base::nullopt); |
| } |
| |
| private: |
| friend class ThreadedWorkletTest; |
| |
| std::unique_ptr<WorkerThread> CreateWorkerThread() final { |
| return std::make_unique<ThreadedWorkletThreadForTest>(WorkletObjectProxy()); |
| } |
| }; |
| |
| class ThreadedWorkletTest : public testing::Test { |
| public: |
| void SetUp() override { |
| page_ = DummyPageHolder::Create(); |
| Document* document = page_->GetFrame().GetDocument(); |
| document->SetURL(KURL("https://example.com/")); |
| document->UpdateSecurityOrigin(SecurityOrigin::Create(document->Url())); |
| messaging_proxy_ = |
| MakeGarbageCollected<ThreadedWorkletMessagingProxyForTest>( |
| &page_->GetDocument()); |
| ThreadedWorkletThreadForTest::EnsureSharedBackingThread(); |
| } |
| |
| void TearDown() override { |
| GetWorkerThread()->Terminate(); |
| GetWorkerThread()->WaitForShutdownForTesting(); |
| test::RunPendingTasks(); |
| ThreadedWorkletThreadForTest::ClearSharedBackingThread(); |
| messaging_proxy_ = nullptr; |
| } |
| |
| ThreadedWorkletMessagingProxyForTest* MessagingProxy() { |
| return messaging_proxy_.Get(); |
| } |
| |
| ThreadedWorkletThreadForTest* GetWorkerThread() { |
| return static_cast<ThreadedWorkletThreadForTest*>( |
| messaging_proxy_->GetWorkerThread()); |
| } |
| |
| Document& GetDocument() { return page_->GetDocument(); } |
| |
| private: |
| std::unique_ptr<DummyPageHolder> page_; |
| Persistent<ThreadedWorkletMessagingProxyForTest> messaging_proxy_; |
| }; |
| |
| TEST_F(ThreadedWorkletTest, SecurityOrigin) { |
| MessagingProxy()->Start(); |
| |
| PostCrossThreadTask( |
| *GetWorkerThread()->GetTaskRunner(TaskType::kInternalTest), FROM_HERE, |
| CrossThreadBind(&ThreadedWorkletThreadForTest::TestSecurityOrigin, |
| CrossThreadUnretained(GetWorkerThread()))); |
| test::EnterRunLoop(); |
| } |
| |
| TEST_F(ThreadedWorkletTest, ContentSecurityPolicy) { |
| // Set up the CSP for Document before starting ThreadedWorklet because |
| // ThreadedWorklet inherits the owner Document's CSP. |
| ContentSecurityPolicy* csp = ContentSecurityPolicy::Create(); |
| csp->DidReceiveHeader("script-src 'self' https://allowed.example.com", |
| kContentSecurityPolicyHeaderTypeEnforce, |
| kContentSecurityPolicyHeaderSourceHTTP); |
| GetDocument().InitContentSecurityPolicy(csp); |
| |
| MessagingProxy()->Start(); |
| |
| PostCrossThreadTask( |
| *GetWorkerThread()->GetTaskRunner(TaskType::kInternalTest), FROM_HERE, |
| CrossThreadBind(&ThreadedWorkletThreadForTest::TestContentSecurityPolicy, |
| CrossThreadUnretained(GetWorkerThread()))); |
| test::EnterRunLoop(); |
| } |
| |
| TEST_F(ThreadedWorkletTest, InvalidContentSecurityPolicy) { |
| ContentSecurityPolicy* csp = ContentSecurityPolicy::Create(); |
| csp->DidReceiveHeader("invalid-csp", kContentSecurityPolicyHeaderTypeEnforce, |
| kContentSecurityPolicyHeaderSourceHTTP); |
| GetDocument().InitContentSecurityPolicy(csp); |
| |
| MessagingProxy()->Start(); |
| |
| PostCrossThreadTask( |
| *GetWorkerThread()->GetTaskRunner(TaskType::kInternalTest), FROM_HERE, |
| CrossThreadBind( |
| &ThreadedWorkletThreadForTest::TestInvalidContentSecurityPolicy, |
| CrossThreadUnretained(GetWorkerThread()))); |
| test::EnterRunLoop(); |
| } |
| |
| TEST_F(ThreadedWorkletTest, UseCounter) { |
| Page::InsertOrdinaryPageForTesting(GetDocument().GetPage()); |
| MessagingProxy()->Start(); |
| |
| // This feature is randomly selected. |
| const WebFeature kFeature1 = WebFeature::kRequestFileSystem; |
| |
| // API use on the threaded WorkletGlobalScope should be recorded in UseCounter |
| // on the Document. |
| EXPECT_FALSE(UseCounter::IsCounted(GetDocument(), kFeature1)); |
| PostCrossThreadTask( |
| *GetWorkerThread()->GetTaskRunner(TaskType::kInternalTest), FROM_HERE, |
| CrossThreadBind(&ThreadedWorkletThreadForTest::CountFeature, |
| CrossThreadUnretained(GetWorkerThread()), kFeature1)); |
| test::EnterRunLoop(); |
| EXPECT_TRUE(UseCounter::IsCounted(GetDocument(), kFeature1)); |
| |
| // API use should be reported to the Document only one time. See comments in |
| // ThreadedWorkletObjectProxyForTest::CountFeature. |
| PostCrossThreadTask( |
| *GetWorkerThread()->GetTaskRunner(TaskType::kInternalTest), FROM_HERE, |
| CrossThreadBind(&ThreadedWorkletThreadForTest::CountFeature, |
| CrossThreadUnretained(GetWorkerThread()), kFeature1)); |
| test::EnterRunLoop(); |
| |
| // This feature is randomly selected from Deprecation::deprecationMessage(). |
| const WebFeature kFeature2 = WebFeature::kPrefixedStorageInfo; |
| |
| // Deprecated API use on the threaded WorkletGlobalScope should be recorded in |
| // UseCounter on the Document. |
| EXPECT_FALSE(UseCounter::IsCounted(GetDocument(), kFeature2)); |
| PostCrossThreadTask( |
| *GetWorkerThread()->GetTaskRunner(TaskType::kInternalTest), FROM_HERE, |
| CrossThreadBind(&ThreadedWorkletThreadForTest::CountDeprecation, |
| CrossThreadUnretained(GetWorkerThread()), kFeature2)); |
| test::EnterRunLoop(); |
| EXPECT_TRUE(UseCounter::IsCounted(GetDocument(), kFeature2)); |
| |
| // API use should be reported to the Document only one time. See comments in |
| // ThreadedWorkletObjectProxyForTest::CountDeprecation. |
| PostCrossThreadTask( |
| *GetWorkerThread()->GetTaskRunner(TaskType::kInternalTest), FROM_HERE, |
| CrossThreadBind(&ThreadedWorkletThreadForTest::CountDeprecation, |
| CrossThreadUnretained(GetWorkerThread()), kFeature2)); |
| test::EnterRunLoop(); |
| } |
| |
| TEST_F(ThreadedWorkletTest, TaskRunner) { |
| MessagingProxy()->Start(); |
| |
| PostCrossThreadTask( |
| *GetWorkerThread()->GetTaskRunner(TaskType::kInternalTest), FROM_HERE, |
| CrossThreadBind(&ThreadedWorkletThreadForTest::TestTaskRunner, |
| CrossThreadUnretained(GetWorkerThread()))); |
| test::EnterRunLoop(); |
| } |
| |
| } // namespace blink |