| // Copyright 2021 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/memory/raw_ptr.h" |
| #include "base/strings/strcat.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "content/browser/shared_storage/shared_storage_worklet_driver.h" |
| #include "content/browser/shared_storage/shared_storage_worklet_host.h" |
| #include "content/browser/shared_storage/shared_storage_worklet_host_manager.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/back_forward_cache_util.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| const char kSimplePagePath[] = "/simple_page.html"; |
| |
| const char kPageWithBlankIframePath[] = "/page_with_blank_iframe.html"; |
| |
| } // namespace |
| |
| class TestSharedStorageWorkletHost : public SharedStorageWorkletHost { |
| public: |
| TestSharedStorageWorkletHost( |
| std::unique_ptr<SharedStorageWorkletDriver> driver, |
| SharedStorageDocumentServiceImpl& document_service, |
| bool should_defer_worklet_messages) |
| : SharedStorageWorkletHost(std::move(driver), document_service), |
| should_defer_worklet_messages_(should_defer_worklet_messages) {} |
| |
| ~TestSharedStorageWorkletHost() override = default; |
| |
| void WaitForWorkletResponsesCount(size_t count) { |
| if (worklet_responses_count_ >= count) |
| return; |
| |
| expected_worklet_responses_count_ = count; |
| worklet_responses_count_waiter_.Run(); |
| } |
| |
| void set_should_defer_worklet_messages(bool should_defer_worklet_messages) { |
| should_defer_worklet_messages_ = should_defer_worklet_messages; |
| } |
| |
| const std::vector<base::OnceClosure>& pending_worklet_messages() { |
| return pending_worklet_messages_; |
| } |
| |
| void ConsoleLog(const std::string& message) override { |
| ConsoleLogHelper(message, /*initial_message=*/true); |
| } |
| |
| void ConsoleLogHelper(const std::string& message, bool initial_message) { |
| if (should_defer_worklet_messages_ && initial_message) { |
| pending_worklet_messages_.push_back(base::BindOnce( |
| &TestSharedStorageWorkletHost::ConsoleLogHelper, |
| weak_ptr_factory_.GetWeakPtr(), message, /*initial_message=*/false)); |
| return; |
| } |
| |
| SharedStorageWorkletHost::ConsoleLog(message); |
| } |
| |
| void FireKeepAliveTimerNow() { |
| ASSERT_TRUE(GetKeepAliveTimerForTesting().IsRunning()); |
| GetKeepAliveTimerForTesting().FireNow(); |
| } |
| |
| void ExecutePendingWorkletMessages() { |
| for (auto& callback : pending_worklet_messages_) { |
| std::move(callback).Run(); |
| } |
| } |
| |
| private: |
| void OnAddModuleOnWorkletFinished( |
| blink::mojom::SharedStorageDocumentService::AddModuleOnWorkletCallback |
| callback, |
| bool success, |
| const std::string& error_message) override { |
| OnAddModuleOnWorkletFinishedHelper(std::move(callback), success, |
| error_message, |
| /*initial_message=*/true); |
| } |
| |
| void OnAddModuleOnWorkletFinishedHelper( |
| blink::mojom::SharedStorageDocumentService::AddModuleOnWorkletCallback |
| callback, |
| bool success, |
| const std::string& error_message, |
| bool initial_message) { |
| if (should_defer_worklet_messages_ && initial_message) { |
| pending_worklet_messages_.push_back(base::BindOnce( |
| &TestSharedStorageWorkletHost::OnAddModuleOnWorkletFinishedHelper, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), success, |
| error_message, /*initial_message=*/false)); |
| } else { |
| SharedStorageWorkletHost::OnAddModuleOnWorkletFinished( |
| std::move(callback), success, error_message); |
| } |
| |
| if (initial_message) |
| OnWorkletResponseReceived(); |
| } |
| |
| void OnRunOperationOnWorkletFinished( |
| bool success, |
| const std::string& error_message) override { |
| OnRunOperationOnWorkletFinishedHelper(success, error_message, |
| /*initial_message=*/true); |
| } |
| |
| void OnRunOperationOnWorkletFinishedHelper(bool success, |
| const std::string& error_message, |
| bool initial_message) { |
| if (should_defer_worklet_messages_ && initial_message) { |
| pending_worklet_messages_.push_back(base::BindOnce( |
| &TestSharedStorageWorkletHost::OnRunOperationOnWorkletFinishedHelper, |
| weak_ptr_factory_.GetWeakPtr(), success, error_message, |
| /*initial_message=*/false)); |
| } else { |
| SharedStorageWorkletHost::OnRunOperationOnWorkletFinished(success, |
| error_message); |
| } |
| |
| if (initial_message) |
| OnWorkletResponseReceived(); |
| } |
| |
| void OnWorkletResponseReceived() { |
| ++worklet_responses_count_; |
| |
| if (worklet_responses_count_waiter_.running() && |
| worklet_responses_count_ >= expected_worklet_responses_count_) { |
| worklet_responses_count_waiter_.Quit(); |
| } |
| } |
| |
| base::TimeDelta GetKeepAliveTimeout() const override { |
| // Configure a timeout large enough so that the scheduled task won't run |
| // automatically. Instead, we will manually call OneShotTimer::FireNow(). |
| return base::Seconds(30); |
| } |
| |
| // How many worklet operations have finished. This only include addModule and |
| // runOperation. |
| size_t worklet_responses_count_ = 0; |
| size_t expected_worklet_responses_count_ = 0; |
| base::RunLoop worklet_responses_count_waiter_; |
| |
| // Whether we should defer messages received from the worklet environment to |
| // handle them later. This includes request callbacks (e.g. for addModule() |
| // and runOperation()), as well as commands initiated from the worklet |
| // (e.g. console.log()). |
| bool should_defer_worklet_messages_; |
| std::vector<base::OnceClosure> pending_worklet_messages_; |
| |
| base::WeakPtrFactory<TestSharedStorageWorkletHost> weak_ptr_factory_{this}; |
| }; |
| |
| class TestSharedStorageWorkletHostManager |
| : public SharedStorageWorkletHostManager { |
| public: |
| using SharedStorageWorkletHostManager::SharedStorageWorkletHostManager; |
| |
| ~TestSharedStorageWorkletHostManager() override = default; |
| |
| std::unique_ptr<SharedStorageWorkletHost> CreateSharedStorageWorkletHost( |
| std::unique_ptr<SharedStorageWorkletDriver> driver, |
| SharedStorageDocumentServiceImpl& document_service) override { |
| return std::make_unique<TestSharedStorageWorkletHost>( |
| std::move(driver), document_service, should_defer_worklet_messages_); |
| } |
| |
| // Precondition: there's only one eligible worklet host. |
| TestSharedStorageWorkletHost* GetAttachedWorkletHost() { |
| DCHECK_EQ(1u, GetAttachedWorkletHostsCount()); |
| return static_cast<TestSharedStorageWorkletHost*>( |
| GetAttachedWorkletHostsForTesting().begin()->second.get()); |
| } |
| |
| // Precondition: there's only one eligible worklet host. |
| TestSharedStorageWorkletHost* GetKeepAliveWorkletHost() { |
| DCHECK_EQ(1u, GetKeepAliveWorkletHostsCount()); |
| return static_cast<TestSharedStorageWorkletHost*>( |
| GetKeepAliveWorkletHostsForTesting().begin()->second.get()); |
| } |
| |
| void ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation( |
| bool should_defer_worklet_messages) { |
| should_defer_worklet_messages_ = should_defer_worklet_messages; |
| } |
| |
| size_t GetAttachedWorkletHostsCount() { |
| return GetAttachedWorkletHostsForTesting().size(); |
| } |
| |
| size_t GetKeepAliveWorkletHostsCount() { |
| return GetKeepAliveWorkletHostsForTesting().size(); |
| } |
| |
| private: |
| bool should_defer_worklet_messages_ = false; |
| }; |
| |
| class SharedStorageBrowserTest : public ContentBrowserTest { |
| public: |
| SharedStorageBrowserTest() { |
| scoped_feature_list_.InitAndEnableFeature( |
| blink::features::kSharedStorageAPI); |
| } |
| |
| void SetUpOnMainThread() override { |
| auto test_worklet_host_manager = |
| std::make_unique<TestSharedStorageWorkletHostManager>(); |
| |
| test_worklet_host_manager_ = test_worklet_host_manager.get(); |
| |
| static_cast<StoragePartitionImpl*>(shell() |
| ->web_contents() |
| ->GetBrowserContext() |
| ->GetDefaultStoragePartition()) |
| ->OverrideSharedStorageWorkletHostManagerForTesting( |
| std::move(test_worklet_host_manager)); |
| |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| SetupCrossSiteRedirector(embedded_test_server()); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| TestSharedStorageWorkletHostManager& test_worklet_host_manager() { |
| DCHECK(test_worklet_host_manager_); |
| return *test_worklet_host_manager_; |
| } |
| |
| ~SharedStorageBrowserTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| raw_ptr<TestSharedStorageWorkletHostManager> test_worklet_host_manager_ = |
| nullptr; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, AddModule_Success) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, AddModule_ScriptNotFound) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| std::string expected_error = base::StrCat( |
| {"a JavaScript error:\nError: Failed to load ", |
| embedded_test_server() |
| ->GetURL("a.com", "/shared_storage/nonexistent_module.js") |
| .spec(), |
| " HTTP status = 404 Not Found.\n"}); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/nonexistent_module.js'); |
| )"); |
| |
| EXPECT_EQ(expected_error, result.error); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(0u, console_observer.messages().size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, AddModule_RedirectNotAllowed) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| std::string expected_error = base::StrCat( |
| {"a JavaScript error:\nError: Unexpected redirect on ", |
| embedded_test_server() |
| ->GetURL("a.com", "/server-redirect?shared_storage/simple_module.js") |
| .spec(), |
| ".\n"}); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule( |
| '/server-redirect?shared_storage/simple_module.js'); |
| )"); |
| |
| EXPECT_EQ(expected_error, result.error); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(0u, console_observer.messages().size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, |
| AddModule_ScriptExecutionFailure) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| std::string expected_error = base::StrCat( |
| {"a JavaScript error:\nError: ", |
| embedded_test_server() |
| ->GetURL("a.com", "/shared_storage/erroneous_module.js") |
| .spec(), |
| ":6 Uncaught ReferenceError: undefinedVariable is not defined.\n"}); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/erroneous_module.js'); |
| )"); |
| |
| EXPECT_EQ(expected_error, result.error); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing erroneous_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, |
| AddModule_MultipleAddModuleFailure) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| std::string expected_error = |
| "a JavaScript error:\nError: sharedStorage.worklet.addModule() can only " |
| "be invoked once per browsing context.\n"; |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )"); |
| EXPECT_EQ(expected_error, result.error); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, RunOperation_Success) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.runOperation( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )")); |
| |
| // There are 2 "worklet operations": addModule and runOperation. |
| test_worklet_host_manager() |
| .GetAttachedWorkletHost() |
| ->WaitForWorkletResponsesCount(2); |
| |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("{\"customKey\":\"customValue\"}", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, |
| RunOperation_Failure_RunOperationBeforeAddModule) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.runOperation( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )")); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // There are 2 "worklet operations": runOperation and addModule. |
| test_worklet_host_manager() |
| .GetAttachedWorkletHost() |
| ->WaitForWorkletResponsesCount(2); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ( |
| "sharedStorage.worklet.addModule() has to be called before " |
| "sharedStorage.runOperation().", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError, |
| console_observer.messages()[0].log_level); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, |
| RunOperation_Failure_InvalidOptionsArgument) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| function testFunction() {} |
| |
| sharedStorage.runOperation( |
| 'test-operation', {data: {'customKey': testFunction}}); |
| )"); |
| |
| EXPECT_EQ( |
| std::string( |
| "a JavaScript error:\nError: function testFunction() {} could not be " |
| "cloned.\n at eval (__const_std::string&_script__:4:21):\n " |
| " .then((result) => true ? result : Promise.reject(),\n " |
| " ^^^^^\n at eval (<anonymous>)\n at " |
| "EvalJs-runner.js:2:34\n"), |
| result.error); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, |
| RunOperation_Failure_ErrorInRunOperation) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule( |
| 'shared_storage/erroneous_function_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing erroneous_function_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[0].log_level); |
| EXPECT_EQ("Finish executing erroneous_function_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[0].log_level); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.runOperation( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )")); |
| |
| // There are 2 "worklet operations": addModule and runOperation. |
| test_worklet_host_manager() |
| .GetAttachedWorkletHost() |
| ->WaitForWorkletResponsesCount(2); |
| |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[2].log_level); |
| EXPECT_EQ("ReferenceError: undefinedVariable is not defined", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError, |
| console_observer.messages()[3].log_level); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, |
| RunOperation_Failure_UnimplementedSharedStorageMethod) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule( |
| 'shared_storage/shared_storage_keys_function_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing shared_storage_keys_function_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing shared_storage_keys_function_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.runOperation('test-operation'); |
| )")); |
| |
| // There are 2 "worklet operations": addModule and runOperation. |
| test_worklet_host_manager() |
| .GetAttachedWorkletHost() |
| ->WaitForWorkletResponsesCount(2); |
| |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[2].log_level); |
| EXPECT_EQ("sharedStorage.keys() is not implemented", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError, |
| console_observer.messages()[3].log_level); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, WorkletDestroyed) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_ASSUMES_NO_CACHING); |
| |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| EXPECT_EQ(0u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, TwoWorklets) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_ASSUMES_NO_CACHING); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL( |
| "a.com", kPageWithBlankIframePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| RenderFrameHost* iframe = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root() |
| ->child_at(0) |
| ->current_frame_host(); |
| |
| EXPECT_EQ(nullptr, EvalJs(iframe, R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module2.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(2u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| NavigateIframeToURL(shell()->web_contents(), "test_iframe", |
| GURL(url::kAboutBlankURL)); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("Executing simple_module2.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageBrowserTest, |
| KeepAlive_StartBeforeAddModuleComplete_EndAfterAddModuleComplete) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_ASSUMES_NO_CACHING); |
| |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| test_worklet_host_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )", |
| EXECUTE_SCRIPT_NO_RESOLVE_PROMISES); |
| |
| // Navigate to trigger keep-alive |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| EXPECT_EQ(0u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->WaitForWorkletResponsesCount(1); |
| |
| // Three pending messages are expected: two for console.log and one for |
| // addModule response. |
| EXPECT_EQ(3u, test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->pending_worklet_messages() |
| .size()); |
| |
| // Execute all the deferred messages. This will terminate the keep-alive. |
| test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->ExecutePendingWorkletMessages(); |
| |
| EXPECT_EQ(0u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Expect no console logging, as messages logged during keep-alive are |
| // dropped. |
| EXPECT_EQ(0u, console_observer.messages().size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, |
| KeepAlive_StartBeforeAddModuleComplete_EndAfterTimeout) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_ASSUMES_NO_CACHING); |
| |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| test_worklet_host_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )", |
| EXECUTE_SCRIPT_NO_RESOLVE_PROMISES); |
| |
| // Navigate to trigger keep-alive |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| EXPECT_EQ(0u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->WaitForWorkletResponsesCount(1); |
| |
| // Three pending messages are expected: two for console.log and one for |
| // addModule response. |
| EXPECT_EQ(3u, test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->pending_worklet_messages() |
| .size()); |
| |
| // Fire the keep-alive timer. This will terminate the keep-alive. |
| test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->FireKeepAliveTimerNow(); |
| |
| EXPECT_EQ(0u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageBrowserTest, |
| KeepAlive_StartBeforeRunOperationComplete_EndAfterRunOperationComplete) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_ASSUMES_NO_CACHING); |
| |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| |
| // Configure the worklet host to defer processing the subsequent runOperation |
| // response. |
| test_worklet_host_manager() |
| .GetAttachedWorkletHost() |
| ->set_should_defer_worklet_messages(true); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.runOperation( |
| 'test-operation', {data: {'customKey': 'customValue'}}) |
| )")); |
| |
| // Navigate to trigger keep-alive |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| EXPECT_EQ(0u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->WaitForWorkletResponsesCount(2); |
| |
| // Four pending messages are expected: three for console.log and one for |
| // runOperation response. |
| EXPECT_EQ(4u, test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->pending_worklet_messages() |
| .size()); |
| |
| // Execute all the deferred messages. This will terminate the keep-alive. |
| test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->ExecutePendingWorkletMessages(); |
| |
| EXPECT_EQ(0u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Expect no more console logging, as messages logged during keep-alive was |
| // dropped. |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, KeepAlive_SubframeWorklet) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_ASSUMES_NO_CACHING); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL( |
| "a.com", kPageWithBlankIframePath))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| // Configure the worklet host for the subframe to defer worklet responses. |
| test_worklet_host_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true); |
| |
| RenderFrameHost* iframe = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root() |
| ->child_at(0) |
| ->current_frame_host(); |
| |
| EvalJsResult result = EvalJs(iframe, R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )", |
| EXECUTE_SCRIPT_NO_RESOLVE_PROMISES); |
| |
| // Navigate away to let the subframe's worklet enter keep-alive. |
| NavigateIframeToURL(shell()->web_contents(), "test_iframe", |
| GURL(url::kAboutBlankURL)); |
| |
| EXPECT_EQ(0u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Ensure that the response is deferred. |
| test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->WaitForWorkletResponsesCount(1); |
| |
| // Three pending messages are expected: two for console.log and one for |
| // addModule response. |
| EXPECT_EQ(3u, test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->pending_worklet_messages() |
| .size()); |
| |
| // Configure the worklet host for the main frame to handle worklet responses |
| // directly. |
| test_worklet_host_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(false); |
| |
| EXPECT_EQ(nullptr, EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module2.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Execute all the deferred messages. This will terminate the keep-alive. |
| test_worklet_host_manager() |
| .GetKeepAliveWorkletHost() |
| ->ExecutePendingWorkletMessages(); |
| |
| EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Expect loggings only from executing top document's worklet. |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("Executing simple_module2.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, |
| RenderProcessHostDestroyedDuringWorkletKeepAlive) { |
| // The test assumes pages gets deleted after navigation, letting the worklet |
| // enter keep-alive phase. To ensure this, disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_ASSUMES_NO_CACHING); |
| |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", kSimplePagePath))); |
| |
| test_worklet_host_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )", |
| EXECUTE_SCRIPT_NO_RESOLVE_PROMISES); |
| |
| // Navigate to trigger keep-alive |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| EXPECT_EQ(0u, test_worklet_host_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // The BrowserContext will be destroyed right after this test body, which will |
| // cause the RenderProcessHost to be destroyed before the keep-alive |
| // SharedStorageWorkletHost. Expect no fatal error. |
| } |
| |
| } // namespace content |