blob: 374dc0cf7fc15b5dbdeff62f20e4cfe612aa9f03 [file] [log] [blame]
// 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