blob: c935a09d911d425f352621c11258593594b0c151 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/metrics_hashes.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/metrics/dwa/dwa_recorder.h"
#include "content/browser/fenced_frame/fenced_frame_config.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/shared_storage/shared_storage_browsertest_base.h"
#include "content/browser/shared_storage/shared_storage_document_service_impl.h"
#include "content/browser/shared_storage/shared_storage_event_params.h"
#include "content/browser/shared_storage/shared_storage_runtime_manager.h"
#include "content/browser/shared_storage/shared_storage_worklet_host.h"
#include "content/browser/shared_storage/test_shared_storage_observer.h"
#include "content/browser/shared_storage/test_shared_storage_runtime_manager.h"
#include "content/browser/shared_storage/test_shared_storage_worklet_host.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/document_user_data.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/network_service_util.h"
#include "content/public/common/page_type.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/public/test/fenced_frame_test_util.h"
#include "content/public/test/shared_storage_test_utils.h"
#include "content/public/test/test_devtools_protocol_client.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_select_url_fenced_frame_config_observer.h"
#include "content/public/test/test_shared_storage_header_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/fenced_frame_test_utils.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/base/schemeful_site.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h"
#include "third_party/blink/public/common/messaging/cloneable_message.h"
#include "third_party/blink/public/common/shared_storage/shared_storage_utils.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "third_party/blink/public/mojom/loader/code_cache.mojom.h"
#include "third_party/blink/public/mojom/shared_storage/shared_storage.mojom.h"
#include "ui/base/page_transition_types.h"
#include "url/url_constants.h"
namespace content {
using testing::ElementsAre;
using testing::Pair;
using testing::UnorderedElementsAre;
using SharedStorageReportingMap = base::flat_map<std::string, ::GURL>;
using SharedStorageUrlSpecWithMetadata =
SharedStorageEventParams::SharedStorageUrlSpecWithMetadata;
using AccessScope = blink::SharedStorageAccessScope;
using AccessMethod = TestSharedStorageObserver::AccessMethod;
namespace {
using WorkletHosts = SharedStorageRuntimeManager::WorkletHosts;
constexpr char kSharedStorageWorkletExpiredMessage[] =
"The sharedStorage worklet cannot execute further operations because the "
"previous operation did not include the option \'keepAlive: true\'.";
constexpr char kTitle1Path[] = "/title1.html";
constexpr char kTitle2Path[] = "/title2.html";
constexpr char kTitle3Path[] = "/title3.html";
constexpr char kTitle4Path[] = "/title4.html";
constexpr char kPngPath[] = "/shared_storage/pixel.png";
constexpr char kSharedStorageTrustedOriginsPath[] =
"/.well-known/shared-storage/trusted-origins";
constexpr char kDestroyedStatusHistogram[] =
"Storage.SharedStorage.Worklet.DestroyedStatus";
constexpr char kTimingKeepAliveDurationHistogram[] =
"Storage.SharedStorage.Worklet.Timing."
"KeepAliveEndedDueToOperationsFinished.KeepAliveDuration";
constexpr char kErrorTypeHistogram[] =
"Storage.SharedStorage.Worklet.Error.Type";
constexpr char kTimingUsefulResourceHistogram[] =
"Storage.SharedStorage.Worklet.Timing.UsefulResourceDuration";
constexpr char kTimingRunExecutedInWorkletHistogram[] =
"Storage.SharedStorage.Document.Timing.Run.ExecutedInWorklet";
constexpr char kEmptyAccessControlAllowOriginReplacement[] = "";
constexpr char kEmptySharedStorageCrossOriginAllowedReplacement[] = "";
std::string ReplacePortInString(std::string str, uint16_t port) {
const std::string kToReplace("{{port}}");
size_t index = str.find(kToReplace);
while (index != std::string::npos) {
str = str.replace(index, kToReplace.size(), base::NumberToString(port));
index = str.find(kToReplace);
}
return str;
}
auto describe_param = [](const auto& info) {
return base::StrCat({"ResolveSelectURLTo", info.param ? "Config" : "URN"});
};
FrameTreeNode* PrimaryFrameTreeNodeRootFromShell(Shell* shell) {
return static_cast<WebContentsImpl*>(shell->web_contents())
->GetPrimaryFrameTree()
.root();
}
bool IsLocalRoot(RenderFrameHost* rfh) {
CHECK(rfh);
return static_cast<RenderFrameHostImpl*>(rfh)->is_local_root();
}
std::string ValueToString(const base::Value* value) {
if (!value) {
return "[[NULL]]";
}
switch (value->type()) {
case base::Value::Type::STRING:
return value->GetString();
case base::Value::Type::NONE:
return "[[NONE]]";
case base::Value::Type::BOOLEAN:
return value->GetBool() ? "true" : "false";
case base::Value::Type::INTEGER:
return base::NumberToString(value->GetInt());
case base::Value::Type::DOUBLE:
return base::NumberToString(value->GetDouble());
case base::Value::Type::BINARY: {
const std::vector<uint8_t>& value_blob = value->GetBlob();
return std::string(value_blob.begin(), value_blob.end());
}
case base::Value::Type::LIST:
return base::WriteJson(value->GetList()).value_or("[[LIST]]");
case base::Value::Type::DICT:
return base::WriteJson(value->GetDict()).value_or("[[DICT]]");
}
NOTREACHED();
}
std::string SerializeVectorOfMapOfStrings(
const std::vector<std::map<std::string, std::string>>& input_vector) {
std::ostringstream oss;
oss << "[";
if (!input_vector.empty()) {
oss << "\n";
}
for (const auto& input_map : input_vector) {
oss << " {";
for (const auto& map_pair : input_map) {
oss << " {" << map_pair.first << ", " << map_pair.second << "} ";
}
oss << "}\n";
}
oss << "]";
return oss.str();
}
class TestSharedStorageDevToolsClient : public TestDevToolsProtocolClient {
public:
explicit TestSharedStorageDevToolsClient(RenderFrameHost* rfh) {
AttachToFrameTreeHost(rfh);
SendCommandSync("Storage.setSharedStorageTracking",
base::Value::Dict().Set("enable", true));
}
~TestSharedStorageDevToolsClient() override { DetachProtocolClient(); }
void set_expected_notification_method(
const std::string& expected_notification_method) {
expected_notification_method_ = expected_notification_method;
}
void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
base::span<const uint8_t> message) override {
std::string_view message_str(reinterpret_cast<const char*>(message.data()),
message.size());
base::Value parsed = *base::JSONReader::Read(message_str);
std::optional<int> id = parsed.GetDict().FindInt("id");
if (!id) {
const std::string* notification = parsed.GetDict().FindString("method");
ASSERT_TRUE(notification);
if (expected_notification_method_ == *notification) {
base::Value* params = parsed.GetDict().Find("params");
ASSERT_TRUE(params);
params_for_notifications_with_expected_method_.push_back(
std::move(*params).TakeDict());
}
}
TestDevToolsProtocolClient::DispatchProtocolMessage(agent_host,
std::move(message));
}
std::vector<std::map<std::string, std::string>>
GetSelectedParamsAsStringsForNotificationsWithExpectedMethod(
std::vector<std::string> selected_key_paths) const {
std::sort(selected_key_paths.begin(), selected_key_paths.end());
std::vector<std::map<std::string, std::string>>
selected_params_for_notifications_;
for (const base::Value::Dict& params :
params_for_notifications_with_expected_method_) {
selected_params_for_notifications_.push_back(
std::map<std::string, std::string>());
for (const std::string& path : selected_key_paths) {
const base::Value* param = params.FindByDottedPath(path);
if (param) {
selected_params_for_notifications_.back().emplace(
path, ValueToString(param));
}
}
}
return selected_params_for_notifications_;
}
private:
std::string expected_notification_method_;
std::vector<base::Value::Dict> params_for_notifications_with_expected_method_;
};
} // namespace
class SharedStorageTrustedOriginsResponse
: public net::test_server::BasicHttpResponse {
public:
SharedStorageTrustedOriginsResponse(
const base::Value* json_trusted_origins_list,
uint16_t port,
bool force_server_error)
: force_server_error_(force_server_error) {
if (json_trusted_origins_list) {
json_trusted_origins_list_str_ =
base::WriteJson(*json_trusted_origins_list);
if (json_trusted_origins_list_str_) {
json_trusted_origins_list_str_ =
ReplacePortInString(*json_trusted_origins_list_str_, port);
}
}
}
SharedStorageTrustedOriginsResponse(
const SharedStorageTrustedOriginsResponse&) = delete;
SharedStorageTrustedOriginsResponse& operator=(
const SharedStorageTrustedOriginsResponse&) = delete;
~SharedStorageTrustedOriginsResponse() override = default;
void SendResponse(
base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) override {
if (!json_trusted_origins_list_str_ || force_server_error_) {
set_code(net::HttpStatusCode::HTTP_INTERNAL_SERVER_ERROR);
set_content_type("text/plain");
set_content("");
} else {
set_code(net::HttpStatusCode::HTTP_OK);
set_content_type("application/json");
AddCustomHeader("Access-Control-Allow-Origin", "*");
set_content(*json_trusted_origins_list_str_);
}
delegate->SendResponseHeaders(code(), GetHttpReasonPhrase(code()),
BuildHeaders());
delegate->SendContents(content(), base::DoNothing());
}
private:
bool force_server_error_;
std::optional<std::string> json_trusted_origins_list_str_;
};
class SharedStorageBrowserTest : public SharedStorageBrowserTestBase,
public testing::WithParamInterface<bool> {
public:
SharedStorageBrowserTest() {
fenced_frame_api_change_feature_.InitWithFeatureState(
blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig());
custom_data_origin_feature_.InitAndEnableFeature(
blink::features::kSharedStorageCreateWorkletCustomDataOrigin);
dwa_feature_.InitAndEnableFeature(metrics::dwa::kDwaFeature);
}
bool ResolveSelectURLToConfig() override { return GetParam(); }
mojo_base::BigBuffer GetCodeCacheDataForUrl(RenderFrameHost* rfh,
const GURL& url) {
mojo::PendingRemote<blink::mojom::CodeCacheHost> pending_code_cache_host;
RenderFrameHostImpl::From(rfh)->CreateCodeCacheHost(
pending_code_cache_host.InitWithNewPipeAndPassReceiver());
mojo::Remote<blink::mojom::CodeCacheHost> inspecting_code_cache_host(
std::move(pending_code_cache_host));
base::test::TestFuture<base::Time, mojo_base::BigBuffer> code_cache_future;
inspecting_code_cache_host->FetchCachedCode(
blink::mojom::CodeCacheType::kJavascript, url,
code_cache_future.GetCallback());
auto [response_time, data] = code_cache_future.Take();
return std::move(data);
}
~SharedStorageBrowserTest() override = default;
void set_trusted_origins_list_index(size_t trusted_origins_list_index) {
trusted_origins_list_index_ = trusted_origins_list_index;
}
void set_force_server_error(bool force_server_error) {
force_server_error_ = force_server_error;
}
void RegisterCustomRequestHandlers() override {
RegisterSharedStorageTrustedOriginsRequestHandler();
}
void RegisterSharedStorageTrustedOriginsRequestHandler() {
https_server()->RegisterRequestHandler(base::BindRepeating(
&SharedStorageBrowserTest::HandleSharedStorageTrustedOriginsRequest,
base::Unretained(this), BuildWellKnownTrustedOriginsLists()));
}
// Virtual so that a derived class can build more a realistic vector of
// values. We use base::Value instead of base::Value::List, even though a
// correctly formatted entry would be a base::Value::List, so that a derived
// class can test the parse error that would happen if the JSON returned isn't
// a list.
//
// SharedStorageBrowserTest::BuildWellKnownTrustedOriginsLists builds a vector
// with a single list entry that simply allowlists all combinations of script
// and context origins.
virtual std::vector<base::Value> BuildWellKnownTrustedOriginsLists() {
std::vector<base::Value> trusted_origins_lists;
trusted_origins_lists.push_back(static_cast<base::Value>(
base::Value::List().Append(base::Value::Dict()
.Set("scriptOrigin", "*")
.Set("contextOrigin", "*"))));
return trusted_origins_lists;
}
std::unique_ptr<net::test_server::HttpResponse>
HandleSharedStorageTrustedOriginsRequest(
const std::vector<base::Value>& json_well_known_trusted_origin_lists,
const net::test_server::HttpRequest& request) {
const auto& path = request.GetURL().path();
if (path != kSharedStorageTrustedOriginsPath ||
json_well_known_trusted_origin_lists.empty()) {
return nullptr;
}
size_t index = trusted_origins_list_index_ %
json_well_known_trusted_origin_lists.size();
return std::make_unique<SharedStorageTrustedOriginsResponse>(
&json_well_known_trusted_origin_lists[index], port(),
force_server_error_);
}
private:
base::test::ScopedFeatureList fenced_frame_api_change_feature_;
base::test::ScopedFeatureList custom_data_origin_feature_;
base::test::ScopedFeatureList dwa_feature_;
size_t trusted_origins_list_index_ = 0;
bool force_server_error_ = false;
};
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, AddModule_Success) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// Navigate again to record histograms.
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
WaitForHistograms(
{kDestroyedStatusHistogram, kTimingUsefulResourceHistogram});
histogram_tester_.ExpectUniqueSample(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 1);
histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1);
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
url::Origin::Create(url).Serialize(),
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, AddModule_ScriptNotFound) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
std::string expected_error = base::StrCat(
{"a JavaScript error: \"OperationError: Failed to load ",
https_server()
->GetURL("a.test", "/shared_storage/nonexistent_module.js")
.spec(),
" HTTP status = 404 Not Found.\"\n"});
EXPECT_THAT(EvalJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/nonexistent_module.js');
)"),
EvalJsResult::ErrorIs(expected_error));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_EQ(0u, console_observer.messages().size());
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
url::Origin::Create(url).Serialize(),
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/nonexistent_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, AddModule_RedirectNotAllowed) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
std::string expected_error = base::StrCat(
{"a JavaScript error: \"OperationError: Unexpected redirect on ",
https_server()
->GetURL("a.test",
"/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_THAT(result, content::EvalJsResult::ErrorIs(expected_error));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_EQ(0u, console_observer.messages().size());
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
url::Origin::Create(url).Serialize(),
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL(
"a.test", "/server-redirect?shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
AddModule_ScriptExecutionFailure) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_THAT(EvalJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/erroneous_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"ReferenceError: undefinedVariable is not defined")));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Start executing erroneous_module.js",
base::UTF16ToUTF8(console_observer.messages()[0].message));
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
url::Origin::Create(url).Serialize(),
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/erroneous_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
AddModule_MultipleAddModuleFailure) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_THAT(EvalJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"addModule() can only be invoked once per worklet")));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// Navigate again to record histograms.
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
WaitForHistograms(
{kDestroyedStatusHistogram, kTimingUsefulResourceHistogram});
histogram_tester_.ExpectUniqueSample(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 1);
histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
AddModue_TheThirdTimeCompilesWithV8CodeCache) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
GURL module_url = https_server()->GetURL(
"a.test", "/shared_storage/large_cacheable_script.js");
EXPECT_TRUE(NavigateToURL(shell(), url));
// Initially, the code cache has no data.
mojo_base::BigBuffer code_cache_data0 = GetCodeCacheDataForUrl(
shell()->web_contents()->GetPrimaryMainFrame(), module_url);
EXPECT_EQ(code_cache_data0.size(), 0u);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule(
'shared_storage/large_cacheable_script.js');
)"));
mojo_base::BigBuffer code_cache_data1 = GetCodeCacheDataForUrl(
shell()->web_contents()->GetPrimaryMainFrame(), module_url);
EXPECT_GT(code_cache_data1.size(), 0u);
// After the first script loading, the code cache has some data.
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule(
'shared_storage/large_cacheable_script.js');
)"));
// After the second script loading, the code cache has more data. This implies
// that the code cache wasn't used for the second compilation. This is
// expected, as we won't store the cached code entirely for first seen URLs.
mojo_base::BigBuffer code_cache_data2 = GetCodeCacheDataForUrl(
shell()->web_contents()->GetPrimaryMainFrame(), module_url);
EXPECT_GT(code_cache_data2.size(), code_cache_data1.size());
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule(
'shared_storage/large_cacheable_script.js');
)"));
// After the third script loading, the code cache does not change. This
// implies that the code cache was used for the third compilation.
mojo_base::BigBuffer code_cache_data3 = GetCodeCacheDataForUrl(
shell()->web_contents()->GetPrimaryMainFrame(), module_url);
EXPECT_EQ(code_cache_data3.size(), code_cache_data2.size());
EXPECT_TRUE(std::equal(code_cache_data3.begin(), code_cache_data3.end(),
code_cache_data2.begin()));
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, RunOperation_Success) {
metrics::dwa::DwaRecorder::Get()->EnableRecording();
metrics::dwa::DwaRecorder::Get()->Purge();
ASSERT_THAT(metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting(),
testing::IsEmpty());
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
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));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
const auto& entries =
metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting();
EXPECT_EQ(entries.size(), 1u);
EXPECT_THAT(entries[0]->event_hash,
base::HashMetricName("SharedStorage.RunFinishedInWorklet"));
EXPECT_THAT(
entries[0]->content_hash,
base::HashMetricName(
net::registry_controlled_domains::GetDomainAndRegistry(
url,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)));
EXPECT_EQ(entries[0]->metrics.size(), 1u);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}});
ExpectOperationFinishedInfosObserved(
{{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0,
GetFirstWorkletHostDevToolsToken(), MainFrameId(), origin_str}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
RunOperation_Failure_RunOperationBeforeAddModule) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_THAT(
EvalJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'},
keepAlive: true});
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"sharedStorage.worklet.addModule() has to be called before run()")));
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
content::FetchHistogramsFromChildProcesses();
// Navigate to terminate the worklet.
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kRunWebVisible,
1);
histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
RunOperation_Failure_InvalidOptionsArgument) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_THAT(EvalJs(shell(), R"(
function testFunction() {}
sharedStorage.run(
'test-operation', {data: {'customKey': testFunction}});
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"function testFunction() {} could not be cloned")));
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 0);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
RunOperation_VerifyUndefinedData) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('test-operation', /*options=*/{});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("Start executing 'test-operation'",
base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("Finish executing 'test-operation'",
base::UTF16ToUTF8(console_observer.messages()[4].message));
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
RunOperation_Failure_BlobDataTypeNotSupportedInWorklet) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
const blob = new Blob(["abc"], {type: 'text/plain'});
sharedStorage.run('test-operation', /*options=*/{data: blob});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("Cannot deserialize data.",
base::UTF16ToUTF8(console_observer.messages()[2].message));
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
RunOperation_VerifyCryptoKeyData) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
const myPromise = new Promise((resolve, reject) => {
crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"]
).then((key) => {
sharedStorage.run('test-operation', /*options=*/{data: key})
.then(() => { resolve(); });
});
});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("Start executing 'test-operation'",
base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ(
"CryptoKey, algorithm: {\"length\":256,\"name\":\"AES-GCM\"} usages: "
"[\"encrypt\",\"decrypt\"] extractable: true",
base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("Finish executing 'test-operation'",
base::UTF16ToUTF8(console_observer.messages()[4].message));
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
RunOperation_Failure_ErrorInRunOperation) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule(
'shared_storage/erroneous_function_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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);
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
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_THAT(
base::UTF16ToUTF8(console_observer.messages()[3].message),
testing::HasSubstr("ReferenceError: undefinedVariable is not defined"));
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError,
console_observer.messages()[3].log_level);
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL(
"a.test", "/shared_storage/erroneous_function_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}});
ExpectOperationFinishedInfosObserved(
{{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0,
GetFirstWorkletHostDevToolsToken(), MainFrameId(), origin_str}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
RunOperation_SecondRunOperationAfterKeepAliveTrueRun_Success) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'},
keepAlive: true});
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
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));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(8u, console_observer.messages().size());
EXPECT_EQ("Start executing 'test-operation'",
base::UTF16ToUTF8(console_observer.messages()[5].message));
EXPECT_EQ("{\"customKey\":\"customValue\"}",
base::UTF16ToUTF8(console_observer.messages()[6].message));
EXPECT_EQ("Finish executing 'test-operation'",
base::UTF16ToUTF8(console_observer.messages()[7].message));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 2);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/1, /*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}});
ExpectOperationFinishedInfosObserved(
{{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0,
GetFirstWorkletHostDevToolsToken(), MainFrameId(), origin_str},
{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/1,
GetFirstWorkletHostDevToolsToken(), MainFrameId(), origin_str}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
RunOperation_SecondRunOperationAfterKeepAliveFalseRun_Failure) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'},
keepAlive: false});
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
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));
EXPECT_THAT(EvalJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)"),
EvalJsResult::ErrorIs(
testing::HasSubstr(kSharedStorageWorkletExpiredMessage)));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
RunOperation_SecondRunOperationAfterKeepAliveDefaultRun_Failure) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
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));
EXPECT_THAT(EvalJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)"),
EvalJsResult::ErrorIs(
testing::HasSubstr(kSharedStorageWorkletExpiredMessage)));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(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_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
WaitForHistograms(
{kDestroyedStatusHistogram, kTimingUsefulResourceHistogram});
histogram_tester_.ExpectUniqueSample(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 1);
histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1);
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
url::Origin::Create(url).Serialize(),
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(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_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kPageWithBlankIframePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
WebContentsConsoleObserver console_observer(shell()->web_contents());
RenderFrameHost* iframe =
PrimaryFrameTreeNodeRoot()->child_at(0)->current_frame_host();
EXPECT_TRUE(ExecJs(iframe, R"(
sharedStorage.worklet.addModule('shared_storage/simple_module2.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(2u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
NavigateIframeToURL(shell()->web_contents(), "test_iframe",
GURL(url::kAboutBlankURL));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// Navigate again to record histograms.
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
WaitForHistograms(
{kDestroyedStatusHistogram, kTimingUsefulResourceHistogram});
histogram_tester_.ExpectUniqueSample(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 2);
histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 2);
std::map<int, base::UnguessableToken>& cached_worklet_devtools_tokens =
GetCachedWorkletHostDevToolsTokens();
EXPECT_EQ(cached_worklet_devtools_tokens.size(), 2u);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module2.js"),
/*worklet_ordinal=*/0, cached_worklet_devtools_tokens[0])},
{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/1, cached_worklet_devtools_tokens[1])}});
}
IN_PROC_BROWSER_TEST_P(
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_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
test_runtime_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_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
test_runtime_manager().GetKeepAliveWorkletHost()->WaitForAddModule();
// Three pending messages are expected: two for console.log and one for
// `addModule()` response.
EXPECT_EQ(3u, test_runtime_manager()
.GetKeepAliveWorkletHost()
->pending_worklet_messages()
.size());
// Execute all the deferred messages. This will terminate the keep-alive.
test_runtime_manager()
.GetKeepAliveWorkletHost()
->ExecutePendingWorkletMessages();
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
// Expect no console logging, as messages logged during keep-alive are
// dropped.
EXPECT_EQ(0u, console_observer.messages().size());
WaitForHistograms({kDestroyedStatusHistogram, kTimingUsefulResourceHistogram,
kTimingKeepAliveDurationHistogram});
histogram_tester_.ExpectUniqueSample(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::
kKeepAliveEndedDueToOperationsFinished,
1);
histogram_tester_.ExpectTotalCount(kTimingKeepAliveDurationHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1);
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
url::Origin::Create(url).Serialize(),
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(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_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
test_runtime_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_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
test_runtime_manager().GetKeepAliveWorkletHost()->WaitForAddModule();
// Three pending messages are expected: two for console.log and one for
// `addModule()` response.
EXPECT_EQ(3u, test_runtime_manager()
.GetKeepAliveWorkletHost()
->pending_worklet_messages()
.size());
// Fire the keep-alive timer. This will terminate the keep-alive.
test_runtime_manager().GetKeepAliveWorkletHost()->FireKeepAliveTimerNow();
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
WaitForHistograms(
{kDestroyedStatusHistogram, kTimingUsefulResourceHistogram});
histogram_tester_.ExpectUniqueSample(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::kKeepAliveEndedDueToTimeout,
1);
histogram_tester_.ExpectTotalCount(kTimingKeepAliveDurationHistogram, 0);
histogram_tester_.ExpectUniqueSample(kTimingUsefulResourceHistogram, 100, 1);
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
url::Origin::Create(url).Serialize(),
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
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_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(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 `run()`
// response.
test_runtime_manager()
.GetAttachedWorkletHost()
->set_should_defer_worklet_messages(true);
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}})
)"));
// Navigate to trigger keep-alive
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
test_runtime_manager().GetKeepAliveWorkletHost()->WaitForWorkletResponses();
// Four pending messages are expected: three for console.log and one for
// `run()` response.
EXPECT_EQ(4u, test_runtime_manager()
.GetKeepAliveWorkletHost()
->pending_worklet_messages()
.size());
// Execute all the deferred messages. This will terminate the keep-alive.
test_runtime_manager()
.GetKeepAliveWorkletHost()
->ExecutePendingWorkletMessages();
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
// Expect no more console logging, as messages logged during keep-alive was
// dropped.
EXPECT_EQ(2u, console_observer.messages().size());
WaitForHistograms({kDestroyedStatusHistogram, kTimingUsefulResourceHistogram,
kTimingKeepAliveDurationHistogram,
kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectUniqueSample(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::
kKeepAliveEndedDueToOperationsFinished,
1);
histogram_tester_.ExpectTotalCount(kTimingKeepAliveDurationHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}});
ExpectOperationFinishedInfosObserved(
{{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0,
GetFirstWorkletHostDevToolsToken(),
/*main_frame_id=*/GlobalRenderFrameHostId(), origin_str}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
KeepAlive_StartBeforeSelectURLComplete_EndAfterSelectURLComplete) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
// Configure the worklet host to defer processing the subsequent `selectURL()`
// response.
test_runtime_manager()
.GetAttachedWorkletHost()
->set_should_defer_worklet_messages(true);
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{url: 'fenced_frames/title0.html'},
{url: 'fenced_frames/title1.html'}
],
{
data: {'mockResult': 1},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result.is_ok());
// Navigate to trigger keep-alive
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
test_runtime_manager().GetKeepAliveWorkletHost()->WaitForWorkletResponses();
// Four pending messages are expected: four for console.log and one for
// `selectURL()` response.
EXPECT_EQ(5u, test_runtime_manager()
.GetKeepAliveWorkletHost()
->pending_worklet_messages()
.size());
// Execute all the deferred messages. This will terminate the keep-alive.
test_runtime_manager()
.GetKeepAliveWorkletHost()
->ExecutePendingWorkletMessages();
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
// Expect no more console logging, as messages logged during keep-alive was
// dropped.
EXPECT_EQ(2u, console_observer.messages().size());
WaitForHistograms({kDestroyedStatusHistogram, kTimingUsefulResourceHistogram,
kTimingKeepAliveDurationHistogram,
kTimingSelectUrlExecutedInWorkletHistogram,
kErrorTypeHistogram});
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
// Since we have navigated away from the page that called `selectURL()`, we
// can't do anything with the resulting Fenced Frame config. So we log this
// outcome as a web-visible "error".
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kSelectURLWebVisible, 1);
histogram_tester_.ExpectUniqueSample(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::
kKeepAliveEndedDueToOperationsFinished,
1);
histogram_tester_.ExpectTotalCount(kTimingKeepAliveDurationHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{}},
{https_server()->GetURL("a.test",
"/fenced_frames/title1.html"),
{}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())}});
ExpectOperationFinishedInfosObserved(
{{base::TimeDelta(), AccessMethod::kSelectURL, /*operation_id=*/0,
GetFirstWorkletHostDevToolsToken(),
/*main_frame_id=*/GlobalRenderFrameHostId(), origin_str}});
}
IN_PROC_BROWSER_TEST_P(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_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kPageWithBlankIframePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
WebContentsConsoleObserver console_observer(shell()->web_contents());
// Configure the worklet host for the subframe to defer worklet responses.
test_runtime_manager()
.ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true);
RenderFrameHost* iframe =
PrimaryFrameTreeNodeRoot()->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_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
// Ensure that the response is deferred.
test_runtime_manager().GetKeepAliveWorkletHost()->WaitForAddModule();
// Three pending messages are expected: two for console.log and one for
// `addModule()` response.
EXPECT_EQ(3u, test_runtime_manager()
.GetKeepAliveWorkletHost()
->pending_worklet_messages()
.size());
// Configure the worklet host for the main frame to handle worklet responses
// directly.
test_runtime_manager()
.ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(false);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module2.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
// Execute all the deferred messages. This will terminate the keep-alive.
test_runtime_manager()
.GetKeepAliveWorkletHost()
->ExecutePendingWorkletMessages();
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_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));
// Navigate again to record histograms.
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
WaitForHistograms({kDestroyedStatusHistogram, kTimingUsefulResourceHistogram,
kTimingKeepAliveDurationHistogram});
histogram_tester_.ExpectBucketCount(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::
kKeepAliveEndedDueToOperationsFinished,
1);
histogram_tester_.ExpectBucketCount(
kDestroyedStatusHistogram,
blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 1);
histogram_tester_.ExpectTotalCount(kTimingKeepAliveDurationHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 2);
std::map<int, base::UnguessableToken>& cached_worklet_devtools_tokens =
GetCachedWorkletHostDevToolsTokens();
EXPECT_EQ(cached_worklet_devtools_tokens.size(), 2u);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, cached_worklet_devtools_tokens[0])},
{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module2.js"),
/*worklet_ordinal=*/1, cached_worklet_devtools_tokens[1])}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
RenderProcessHostDestroyedDuringWorkletKeepAlive_SameOrigin) {
// 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_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
test_runtime_manager()
.ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true);
EvalJsResult result = EvalJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)",
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES);
// Navigate to trigger keep-alive
RenderFrameDeletedObserver rfh_deleted_observer(
shell()->web_contents()->GetPrimaryMainFrame());
EXPECT_TRUE(NavigateToURL(shell(),
https_server()->GetURL("c.test", kSimplePagePath)));
rfh_deleted_observer.WaitUntilDeleted();
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(1u, test_runtime_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.
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
RenderProcessHostDestroyedDuringWorkletKeepAlive_CrossOrigin) {
// 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_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
test_runtime_manager()
.ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true);
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EvalJsResult result = EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1)", module_script_url.spec()),
EXECUTE_SCRIPT_NO_RESOLVE_PROMISES);
// Navigate to trigger keep-alive
RenderFrameDeletedObserver rfh_deleted_observer(
shell()->web_contents()->GetPrimaryMainFrame());
EXPECT_TRUE(NavigateToURL(shell(),
https_server()->GetURL("c.test", kSimplePagePath)));
rfh_deleted_observer.WaitUntilDeleted();
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(1u, test_runtime_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.
}
// Test that there's no need to charge budget if the input urls' size is 1.
// This specifically tests the operation success scenario.
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
SelectURL_BudgetMetadata_OperationSuccess_SingleInputURL) {
metrics::dwa::DwaRecorder::Get()->EnableRecording();
metrics::dwa::DwaRecorder::Get()->Purge();
ASSERT_THAT(metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting(),
testing::IsEmpty());
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html",
reportingMetadata: {
"click": "fenced_frames/report1.html",
"mouse interaction": "fenced_frames/report2.html"
}
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result.is_ok());
const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config =
config_observer.GetConfig();
EXPECT_TRUE(fenced_frame_config.has_value());
EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value());
SharedStorageBudgetMetadata* metadata =
GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
EXPECT_TRUE(metadata);
EXPECT_EQ(metadata->site,
net::SchemefulSite(https_server()->GetOrigin("a.test")));
EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0);
EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()),
UnorderedElementsAre(
Pair("click", https_server()->GetURL(
"a.test", "/fenced_frames/report1.html")),
Pair("mouse interaction",
https_server()->GetURL("a.test",
"/fenced_frames/report2.html"))));
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram,
kSelectUrlBudgetStatusHistogram});
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
histogram_tester_.ExpectUniqueSample(
kSelectUrlBudgetStatusHistogram,
blink::SharedStorageSelectUrlBudgetStatus::kSufficientBudget, 1);
const auto& entries =
metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting();
EXPECT_EQ(entries.size(), 1u);
EXPECT_THAT(entries[0]->event_hash,
base::HashMetricName("SharedStorage.SelectUrlFinishedInWorklet"));
EXPECT_THAT(
entries[0]->content_hash,
base::HashMetricName(
net::registry_controlled_domains::GetDomainAndRegistry(
main_url,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)));
EXPECT_EQ(entries[0]->metrics.size(), 1u);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{{"click",
https_server()
->GetURL("a.test", "/fenced_frames/report1.html")
.spec()},
{"mouse interaction",
https_server()
->GetURL("a.test", "/fenced_frames/report2.html")
.spec()}}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())}});
}
// Test that there's no need to charge budget if the input urls' size is 1.
// This specifically tests the operation failure scenario.
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
SelectURL_BudgetMetadata_OperationFailure_SingleInputURL) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html",
reportingMetadata: {
"click": "fenced_frames/report1.html"
}
}
],
{
data: {'mockResult': -1},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result.is_ok());
const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config =
config_observer.GetConfig();
EXPECT_TRUE(fenced_frame_config.has_value());
EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value());
SharedStorageBudgetMetadata* metadata =
GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
EXPECT_TRUE(metadata);
EXPECT_EQ(metadata->site,
net::SchemefulSite(https_server()->GetOrigin("a.test")));
EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0);
EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()),
UnorderedElementsAre(
Pair("click", https_server()->GetURL(
"a.test", "/fenced_frames/report1.html"))));
EXPECT_EQ(
"Promise resolved to a number outside the length of the input urls.",
base::UTF16ToUTF8(console_observer.messages().back().message));
WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{{"click",
https_server()
->GetURL("a.test", "/fenced_frames/report1.html")
.spec()}}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
SelectURL_BudgetMetadata_Origin) {
EXPECT_TRUE(NavigateToURL(
shell(), https_server()->GetURL("a.test", kPageWithBlankIframePath)));
GURL iframe_url = https_server()->GetURL("b.test", kSimplePagePath);
NavigateIframeToURL(shell()->web_contents(), "test_iframe", iframe_url);
RenderFrameHost* iframe =
PrimaryFrameTreeNodeRoot()->child_at(0)->current_frame_host();
EXPECT_TRUE(ExecJs(iframe, R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_TRUE(ExecJs(iframe, JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result = EvalJs(iframe, R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
},
{
url: "fenced_frames/title1.html",
reportingMetadata: {
"click": "fenced_frames/report1.html"
}
},
{
url: "fenced_frames/title2.html"
}
],
{
data: {'mockResult': 1},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result.is_ok());
const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config =
config_observer.GetConfig();
EXPECT_TRUE(fenced_frame_config.has_value());
EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value());
SharedStorageBudgetMetadata* metadata =
GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
EXPECT_TRUE(metadata);
EXPECT_EQ(metadata->site,
net::SchemefulSite(https_server()->GetOrigin("b.test")));
EXPECT_DOUBLE_EQ(metadata->budget_to_charge, std::log2(3));
SharedStorageReportingMap reporting_map =
GetSharedStorageReportingMap(observed_urn_uuid.value());
EXPECT_FALSE(reporting_map.empty());
EXPECT_EQ(1U, reporting_map.size());
EXPECT_EQ("click", reporting_map.begin()->first);
EXPECT_EQ(https_server()->GetURL("b.test", "/fenced_frames/report1.html"),
reporting_map.begin()->second);
WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(iframe_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("b.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("b.test",
"/fenced_frames/title0.html"),
{}},
{https_server()->GetURL("b.test",
"/fenced_frames/title1.html"),
{{"click",
https_server()
->GetURL("b.test", "/fenced_frames/report1.html")
.spec()}}},
{https_server()->GetURL("b.test",
"/fenced_frames/title2.html"),
{}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
SelectURL_SecondSelectURLAfterKeepAliveTrueSelectURL_Success) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer1(
GetStoragePartition());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result1 = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig,
keepAlive: true
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result1.is_ok());
const std::optional<GURL>& observed_urn_uuid1 = config_observer1.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid1.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid1.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result1.ExtractString(), observed_urn_uuid1->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer1.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config1 =
config_observer1.GetConfig();
EXPECT_TRUE(fenced_frame_config1.has_value());
EXPECT_EQ(fenced_frame_config1->urn_uuid(), observed_urn_uuid1.value());
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
TestSelectURLFencedFrameConfigObserver config_observer2(
GetStoragePartition());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result2 = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result2.is_ok());
const std::optional<GURL>& observed_urn_uuid2 = config_observer2.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid2.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid2.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result2.ExtractString(), observed_urn_uuid2->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer2.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config2 =
config_observer2.GetConfig();
EXPECT_TRUE(fenced_frame_config2.has_value());
EXPECT_EQ(fenced_frame_config2->urn_uuid(), observed_urn_uuid2.value());
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
2);
ASSERT_EQ(urn_uuids_observed().size(), 2u);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/1,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[1],
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
SelectURL_SecondSelectURLAfterKeepAliveFalseSelectURL_Failure) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer1(
GetStoragePartition());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result1 = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig,
keepAlive: false
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result1.is_ok());
const std::optional<GURL>& observed_urn_uuid1 = config_observer1.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid1.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid1.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result1.ExtractString(), observed_urn_uuid1->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer1.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config1 =
config_observer1.GetConfig();
EXPECT_TRUE(fenced_frame_config1.has_value());
EXPECT_EQ(fenced_frame_config1->urn_uuid(), observed_urn_uuid1.value());
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
TestSelectURLFencedFrameConfigObserver config_observer2(
GetStoragePartition());
EXPECT_THAT(EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)"),
EvalJsResult::ErrorIs(
testing::HasSubstr(kSharedStorageWorkletExpiredMessage)));
EXPECT_FALSE(config_observer2.ConfigObserved());
WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
SelectURL_SecondSelectURLAfterKeepAliveDefaultSelectURL_Failure) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer1(
GetStoragePartition());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
const char select_url_script[] = R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)";
EvalJsResult result1 = EvalJs(shell(), select_url_script);
EXPECT_TRUE(result1.is_ok());
const std::optional<GURL>& observed_urn_uuid1 = config_observer1.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid1.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid1.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result1.ExtractString(), observed_urn_uuid1->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer1.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config1 =
config_observer1.GetConfig();
EXPECT_TRUE(fenced_frame_config1.has_value());
EXPECT_EQ(fenced_frame_config1->urn_uuid(), observed_urn_uuid1.value());
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
TestSelectURLFencedFrameConfigObserver config_observer2(
GetStoragePartition());
EXPECT_THAT(EvalJs(shell(), select_url_script),
EvalJsResult::ErrorIs(
testing::HasSubstr(kSharedStorageWorkletExpiredMessage)));
EXPECT_FALSE(config_observer2.ConfigObserved());
WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
SelectURL_SelectURLAfterKeepAliveFalseRun_Failure) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'},
keepAlive: false});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
EXPECT_THAT(EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)"),
EvalJsResult::ErrorIs(
testing::HasSubstr(kSharedStorageWorkletExpiredMessage)));
EXPECT_FALSE(config_observer.ConfigObserved());
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_EQ(5u, 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("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));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
SelectURL_SelectURLAfterKeepAliveTrueRun_Success) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'},
keepAlive: true});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(5u, 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("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));
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result.is_ok());
const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config =
config_observer.GetConfig();
EXPECT_TRUE(fenced_frame_config.has_value());
EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value());
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
WaitForHistograms({kTimingRunExecutedInWorkletHistogram,
kTimingSelectUrlExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/1,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
SelectURL_SelectURLAfterKeepAliveDefaultRun_Failure) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
EXPECT_THAT(EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)"),
EvalJsResult::ErrorIs(
testing::HasSubstr(kSharedStorageWorkletExpiredMessage)));
EXPECT_FALSE(config_observer.ConfigObserved());
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_EQ(5u, 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("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));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
RunOperation_RunAfterKeepAliveTrueSelectURL_Success) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
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_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig,
keepAlive: true
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result.is_ok());
const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config =
config_observer.GetConfig();
EXPECT_TRUE(fenced_frame_config.has_value());
EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value());
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ("Finish executing 'test-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
WaitForHistograms({kTimingRunExecutedInWorkletHistogram,
kTimingSelectUrlExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/1, /*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
RunOperation_RunAfterKeepAliveFalseSelectURL_Failure) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
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_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result1 = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig,
keepAlive: false
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result1.is_ok());
const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result1.ExtractString(), observed_urn_uuid->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config =
config_observer.GetConfig();
EXPECT_TRUE(fenced_frame_config.has_value());
EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value());
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_THAT(EvalJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)"),
EvalJsResult::ErrorIs(
testing::HasSubstr(kSharedStorageWorkletExpiredMessage)));
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
RunOperation_RunAfterKeepAliveDefaultSelectURL_Failure) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
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_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result1 = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result1.is_ok());
const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result1.ExtractString(), observed_urn_uuid->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config =
config_observer.GetConfig();
EXPECT_TRUE(fenced_frame_config.has_value());
EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value());
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
EXPECT_THAT(EvalJs(shell(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)"),
EvalJsResult::ErrorIs(
testing::HasSubstr(kSharedStorageWorkletExpiredMessage)));
EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount());
EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount());
WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
SelectURL_ReportingMetadata_EmptyReportEvent) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
// There is 1 more "worklet operation": `selectURL()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EvalJsResult result = EvalJs(shell(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html",
reportingMetadata: {
"": "fenced_frames/report1.html"
}
}
],
{
data: {'mockResult': 0},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(result.is_ok());
const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
}
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
ASSERT_TRUE(config_observer.ConfigObserved());
const std::optional<FencedFrameConfig>& fenced_frame_config =
config_observer.GetConfig();
EXPECT_TRUE(fenced_frame_config.has_value());
EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value());
SharedStorageBudgetMetadata* metadata =
GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
EXPECT_TRUE(metadata);
EXPECT_EQ(metadata->site,
net::SchemefulSite(https_server()->GetOrigin("a.test")));
EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0);
EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()),
UnorderedElementsAre(
Pair("", https_server()->GetURL(
"a.test", "/fenced_frames/report1.html"))));
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages().back().message));
WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
1);
ASSERT_EQ(urn_uuids_observed().size(), 1u);
std::string origin_str = url::Origin::Create(main_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
https_server()->GetURL("a.test",
"/shared_storage/simple_module.js"),
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSelectURLForTesting(
"test-url-selection-operation", /*operation_id=*/0,
/*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(),
std::vector<SharedStorageUrlSpecWithMetadata>(
{{https_server()->GetURL("a.test",
"/fenced_frames/title0.html"),
{{"", https_server()
->GetURL("a.test", "/fenced_frames/report1.html")
.spec()}}}}),
ResolveSelectURLToConfig(),
/*saved_query=*/std::string(), urn_uuids_observed()[0],
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, SetAppendOperationInDocument) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.set('key0', 'value0');
sharedStorage.set('key1', 'value1');
sharedStorage.set('key1', 'value111');
sharedStorage.set('key2', 'value2');
sharedStorage.set('key2', 'value222', {ignoreIfPresent: true});
sharedStorage.set('key3', 'value3');
sharedStorage.append('key3', 'value333');
)"));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('key0'));
console.log(await sharedStorage.get('key1'));
console.log(await sharedStorage.get('key2'));
console.log(await sharedStorage.get('key3'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("value0",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("value111",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("value2",
base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("value3value333",
base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("4", base::UTF16ToUTF8(console_observer.messages()[4].message));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key0", "value0", false)},
{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key1", "value1", false)},
{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key1", "value111", false)},
{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key2", "value2", false)},
{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key2", "value222", true)},
{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key3", "value3", false)},
{AccessScope::kWindow, AccessMethod::kAppend, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForAppend("key3", "value333")},
{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key0", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key1", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key2", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key3", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, DeleteOperationInDocument) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.set('key0', 'value0');
sharedStorage.delete('key0');
)"));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.length());
console.log(await sharedStorage.get('key0'));
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo,
console_observer.messages()[0].log_level);
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo,
console_observer.messages()[1].log_level);
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key0", "value0", false)},
{AccessScope::kWindow, AccessMethod::kDelete, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForDelete("key0")},
{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key0", GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, ClearOperationInDocument) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.set('key0', 'value0');
sharedStorage.clear();
)"));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[0].message));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key0", "value0", false)},
{AccessScope::kWindow, AccessMethod::kClear, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForClear()},
{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, SetAppendOperationInWorklet) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
sharedStorage.set('key0', 'value0');
sharedStorage.set('key1', 'value1');
sharedStorage.set('key1', 'value111');
sharedStorage.set('key2', 'value2');
sharedStorage.set('key2', 'value222', {ignoreIfPresent: true});
sharedStorage.set('key3', 'value3');
sharedStorage.append('key3', 'value333');
console.log(await sharedStorage.get('key0'));
console.log(await sharedStorage.get('key1'));
console.log(await sharedStorage.get('key2'));
console.log(await sharedStorage.get('key3'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("value0",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("value111",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("value2",
base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("value3value333",
base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("4", base::UTF16ToUTF8(console_observer.messages()[4].message));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"key0", "value0", false, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"key1", "value1", false, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"key1", "value111", false, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"key2", "value2", false, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"key2", "value222", true, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"key3", "value3", false, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kAppend,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateForAppend(
"key3", "value333", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key0", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key1", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key2", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key3", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
AppendOperationFailedInWorklet) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
await sharedStorage.set('k', 'a'.repeat(2621439));
// This will fail due to the storage quota being reached.
await sharedStorage.append('k', 'a');
)",
&out_script_url);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages()[0].message),
testing::HasSubstr("sharedStorage.append() failed"));
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError,
console_observer.messages()[0].log_level);
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"k", std::string(2621439, 'a'), false,
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kAppend,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateForAppend(
"k", "a", GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, DeleteOperationInWorklet) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
sharedStorage.set('key0', 'value0');
console.log(await sharedStorage.length());
console.log(await sharedStorage.get('key0'));
sharedStorage.delete('key0');
console.log(await sharedStorage.length());
console.log(await sharedStorage.get('key0'));
)",
&out_script_url);
EXPECT_EQ(4u, console_observer.messages().size());
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("value0",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo,
console_observer.messages()[0].log_level);
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo,
console_observer.messages()[1].log_level);
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo,
console_observer.messages()[2].log_level);
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo,
console_observer.messages()[3].log_level);
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"key0", "value0", false, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key0", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kDelete,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateForDelete(
"key0", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key0", GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, ClearOperationInWorklet) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
sharedStorage.set('key0', 'value0');
console.log(await sharedStorage.length());
console.log(await sharedStorage.get('key0'));
sharedStorage.clear();
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("value0",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[2].message));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"key0", "value0", false, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key0", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kClear, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, ConsoleErrorInWorklet) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.error('error0');
)",
&out_script_url);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError,
console_observer.messages()[0].log_level);
EXPECT_EQ("error0",
base::UTF16ToUTF8(console_observer.messages()[0].message));
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, GetOperationInWorklet) {
base::SimpleTestClock clock;
base::RunLoop loop;
static_cast<StoragePartitionImpl*>(GetStoragePartition())
->GetSharedStorageManager()
->OverrideClockForTesting(&clock, loop.QuitClosure());
loop.Run();
clock.SetNow(base::Time::Now());
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.set('key0', 'value0');
)"));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL script_url =
https_server()->GetURL("a.test", "/shared_storage/getter_module.js");
EXPECT_TRUE(ExecJs(
shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('get-operation', {data: {'key': 'key0'},
keepAlive: true});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
// Advance clock so that key will expire.
clock.Advance(base::Days(kStalenessThresholdDays) + base::Seconds(1));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('get-operation', {data: {'key': 'key0'}});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(4u, console_observer.messages().size());
EXPECT_EQ("sharedStorage.length(): 1",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("sharedStorage.get('key0'): value0",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("sharedStorage.length(): 0",
base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("sharedStorage.get('key0'): undefined",
base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo,
console_observer.messages()[0].log_level);
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo,
console_observer.messages()[1].log_level);
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo,
console_observer.messages()[2].log_level);
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo,
console_observer.messages()[3].log_level);
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 2);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key0", "value0", false)},
{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"get-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key0", GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"get-operation", /*operation_id=*/1, /*keep_alive=*/false,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key0", GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
AccessStorageInSameOriginDocument) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
// Cache the main frame ID for comparison below, since it will change with
// navigation.
GlobalRenderFrameHostId cached_main_frame_id = MainFrameId();
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.set('key0', 'value0');
)"));
EXPECT_TRUE(
NavigateToURL(shell(), https_server()->GetURL("a.test", "/title1.html")));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[0].message));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kSet, cached_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForSet("key0", "value0", false)},
{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
AccessStorageInDifferentOriginDocument) {
GURL url1 = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url1));
// Cache the main frame ID for comparison below, since it will change with
// navigation.
GlobalRenderFrameHostId cached_main_frame_id = MainFrameId();
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.set('key0', 'value0');
)"));
GURL url2 = https_server()->GetURL("b.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), url2));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[0].message));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin2_str = url::Origin::Create(url2).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kSet, cached_main_frame_id,
url::Origin::Create(url1).Serialize(),
SharedStorageEventParams::CreateForSet("key0", "value0", false)},
{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin2_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin2_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin2_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, KeysAndEntriesOperation) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.set('key0', 'value0');
sharedStorage.set('key1', 'value1');
sharedStorage.set('key2', 'value2');
)"));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
for await (const key of sharedStorage.keys()) {
console.log(key);
}
for await (const [key, value] of sharedStorage.entries()) {
console.log(key + ';' + value);
}
)",
&out_script_url);
EXPECT_EQ(6u, console_observer.messages().size());
EXPECT_EQ("key0", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("key1", base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("key2", base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("key0;value0",
base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("key1;value1",
base::UTF16ToUTF8(console_observer.messages()[4].message));
EXPECT_EQ("key2;value2",
base::UTF16ToUTF8(console_observer.messages()[5].message));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key0", "value0", false)},
{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key1", "value1", false)},
{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key2", "value2", false)},
{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kKeys, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kEntries,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, ValuesOperation) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.set('key0', 'value0');
sharedStorage.set('key1', 'value1');
sharedStorage.set('key2', 'value2');
)"));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
for await (const value of sharedStorage.values()) {
console.log(value);
}
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("value0",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("value1",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("value2",
base::UTF16ToUTF8(console_observer.messages()[2].message));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key0", "value0", false)},
{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key1", "value1", false)},
{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet("key2", "value2", false)},
{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kValues,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
KeysAndEntriesOperation_MultipleBatches) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
EXPECT_TRUE(ExecJs(shell(), R"(
for (let i = 0; i < 150; ++i) {
sharedStorage.set('key' + i.toString().padStart(3, '0'),
'value' + i.toString().padStart(3, '0'));
}
)"));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
for await (const key of sharedStorage.keys()) {
console.log(key);
}
for await (const [key, value] of sharedStorage.entries()) {
console.log(key + ';' + value);
}
)",
&out_script_url);
EXPECT_EQ(300u, console_observer.messages().size());
std::string origin_str = url::Origin::Create(url).Serialize();
std::vector<TestSharedStorageObserver::Access> expected_accesses;
for (int i = 0; i < 150; ++i) {
std::string zero_padded_i = base::NumberToString(i);
zero_padded_i.insert(zero_padded_i.begin(), 3 - zero_padded_i.size(), '0');
std::string padded_key = base::StrCat({"key", zero_padded_i});
std::string padded_value = base::StrCat({"value", zero_padded_i});
EXPECT_EQ(padded_key,
base::UTF16ToUTF8(console_observer.messages()[i].message));
EXPECT_EQ(base::JoinString({padded_key, padded_value}, ";"),
base::UTF16ToUTF8(console_observer.messages()[i + 150].message));
expected_accesses.emplace_back(AccessScope::kWindow, AccessMethod::kSet,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateForSet(
padded_key, padded_value, false));
}
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1);
expected_accesses.emplace_back(AccessScope::kWindow, AccessMethod::kAddModule,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url, /*worklet_ordinal=*/0,
GetFirstWorkletHostDevToolsToken()));
expected_accesses.emplace_back(
AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken()));
expected_accesses.emplace_back(
AccessScope::kSharedStorageWorklet, AccessMethod::kKeys, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken()));
expected_accesses.emplace_back(
AccessScope::kSharedStorageWorklet, AccessMethod::kEntries, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
GetFirstWorkletHostDevToolsToken()));
ExpectAccessObserved(expected_accesses);
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, WebLocksUsageHistograms) {
// The test assumes pages gets deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
await sharedStorage.set('key0', 'value0');
await sharedStorage.set('key0', 'value0');
await navigator.locks.request("lock1", async (lock) => {
await sharedStorage.set('key0', 'value0', { withLock: 'lock2' });
});
await sharedStorage.batchUpdate([
new SharedStorageSetMethod('key0', 'value0'),
new SharedStorageAppendMethod('key1', 'value1')
], { withLock: 'lock1' });
)",
&out_script_url);
// Navigate again to record histograms.
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectBucketCount(
"Storage.SharedStorage.UpdateMethod.HasLockOption", true, 1);
histogram_tester_.ExpectBucketCount(
"Storage.SharedStorage.UpdateMethod.HasLockOption", false, 2);
histogram_tester_.ExpectUniqueSample(
"Storage.SharedStorage.BatchUpdateMethod.HasLockOption", true, 1);
histogram_tester_.ExpectUniqueSample(
"Storage.SharedStorage.Worklet.NavigatorLocksInvoked", true, 1);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForSet(
"key0", "value0",
/*ignore_if_present=*/false, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForSet(
"key0", "value0",
/*ignore_if_present=*/false, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForSet(
"key0", "value0", /*ignore_if_present=*/false,
GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/"lock2",
/*batch_update_id=*/std::nullopt)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kBatchUpdate,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForBatchUpdate(
GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/"lock1",
/*batch_update_id=*/0,
/*batch_size=*/2u)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForSet(
"key0", "value0",
/*ignore_if_present=*/false, GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/std::nullopt,
/*batch_update_id=*/0)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kAppend,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForAppend(
"key1", "value1", GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/std::nullopt,
/*batch_update_id=*/0)}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, MulipleBatchUpdates) {
// The test assumes pages gets deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
await sharedStorage.batchUpdate([
new SharedStorageSetMethod('key0', 'value0'),
new SharedStorageAppendMethod('key1', 'value1')
]);
await sharedStorage.batchUpdate([
new SharedStorageSetMethod('key2', 'value2'),
], { withLock: 'lock1' });
await sharedStorage.batchUpdate([
new SharedStorageSetMethod("key1", "value0", {ignoreIfPresent: true}),
new SharedStorageAppendMethod("key1", "value1"),
new SharedStorageDeleteMethod("key2"),
new SharedStorageClearMethod()
], { withLock: 'lock1' });
)",
&out_script_url);
// Navigate again to record histograms.
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectBucketCount(
"Storage.SharedStorage.BatchUpdateMethod.HasLockOption", false, 1);
histogram_tester_.ExpectBucketCount(
"Storage.SharedStorage.BatchUpdateMethod.HasLockOption", true, 2);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())},
{AccessScope::kSharedStorageWorklet, AccessMethod::kBatchUpdate,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForBatchUpdate(
GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/std::nullopt,
/*batch_update_id=*/0,
/*batch_size=*/2u)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForSet(
"key0", "value0", /*ignore_if_present=*/false,
GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/std::nullopt,
/*batch_update_id=*/0)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kAppend,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForAppend(
"key1", "value1", GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/std::nullopt,
/*batch_update_id=*/0)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kBatchUpdate,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForBatchUpdate(
GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/"lock1",
/*batch_update_id=*/1,
/*batch_size=*/1u)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForSet(
"key2", "value2", /*ignore_if_present=*/false,
GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/std::nullopt,
/*batch_update_id=*/1)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kBatchUpdate,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForBatchUpdate(
GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/"lock1",
/*batch_update_id=*/2,
/*batch_size=*/4u)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForSet(
"key1", "value0",
/*ignore_if_present=*/true, GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/std::nullopt,
/*batch_update_id=*/2)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kAppend,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForAppend(
"key1", "value1", GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/std::nullopt,
/*batch_update_id=*/2)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kDelete,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForDelete(
"key2", GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/std::nullopt,
/*batch_update_id=*/2)},
{AccessScope::kSharedStorageWorklet, AccessMethod::kClear,
initial_main_frame_id, origin_str,
SharedStorageEventParams::CreateForClear(
GetFirstWorkletHostDevToolsToken(),
/*with_lock=*/std::nullopt,
/*batch_update_id=*/2)}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, EmptyBatchUpdate) {
// The test assumes pages gets deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
shell()->web_contents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GlobalRenderFrameHostId initial_main_frame_id = MainFrameId();
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
await sharedStorage.batchUpdate([], { withLock: 'lock1' });
)",
&out_script_url);
// Navigate again to record histograms.
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(
"Storage.SharedStorage.BatchUpdateMethod.HasLockOption", true, 1);
// The empty batchUpdate does not receive an event notification.
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForAddModule(
out_script_url,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())},
{AccessScope::kWindow, AccessMethod::kRun, initial_main_frame_id,
origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
CreateWorklet_SameOrigin_Success) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"a.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
kEmptyAccessControlAllowOriginReplacement,
kEmptySharedStorageCrossOriginAllowedReplacement)));
EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.createWorklet($1)",
module_script_url.spec())));
TestSharedStorageWorkletHost* worklet_host =
test_runtime_manager().GetAttachedWorkletHost();
EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(),
worklet_host->GetProcessHost());
EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kCreateWorklet,
worklet_host->creation_method());
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
CreateWorklet_CrossOriginScript_DefaultDataOrigin_FailedCors) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
kEmptyAccessControlAllowOriginReplacement,
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_THAT(EvalJs(shell(), JsReplace("sharedStorage.createWorklet($1)",
module_script_url.spec())),
EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load")));
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
CreateWorklet_CrossOriginScript_ContextDataOrigin_FailedCors) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
kEmptyAccessControlAllowOriginReplacement,
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'context-origin'})",
module_script_url.spec())),
EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load")));
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
CreateWorklet_CrossOriginScript_ScriptDataOrigin_FailedCors) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
kEmptyAccessControlAllowOriginReplacement,
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})",
module_script_url.spec())),
EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load")));
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
CreateWorklet_CrossOriginScript_CustomDataOrigin_FailedCors) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
kEmptyAccessControlAllowOriginReplacement,
kEmptySharedStorageCrossOriginAllowedReplacement)));
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())),
EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load")));
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
CreateWorklet_CrossOriginScript_ScriptDataOrigin_FailedSharedStorageWorkletAllowedResponseHeaderCheck) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
kEmptySharedStorageCrossOriginAllowedReplacement)));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})",
module_script_url.spec())),
EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load")));
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
CreateWorklet_CrossOriginScript_ContextDataOrigin_NoSharedStorageWorkletAllowedResponseHeader_Success) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
kEmptySharedStorageCrossOriginAllowedReplacement)));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'context-origin'})",
module_script_url.spec())));
TestSharedStorageWorkletHost* worklet_host =
test_runtime_manager().GetAttachedWorkletHost();
// The worklet host should reuse the main frame's process because the context
// origin is being used as the data origin.
bool use_new_process =
(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess() !=
worklet_host->GetProcessHost());
EXPECT_FALSE(use_new_process);
EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kCreateWorklet,
worklet_host->creation_method());
std::string data_origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
data_origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
module_script_url, "context-origin",
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
CreateWorklet_CrossOriginScript_DefaultDataOrigin_NoSharedStorageWorkletAllowedResponseHeader_Success) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
kEmptySharedStorageCrossOriginAllowedReplacement)));
EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.createWorklet($1)",
module_script_url.spec())));
TestSharedStorageWorkletHost* worklet_host =
test_runtime_manager().GetAttachedWorkletHost();
// The worklet host should reuse the main frame's process because the context
// origin is being used as the data origin.
bool use_new_process =
(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess() !=
worklet_host->GetProcessHost());
EXPECT_FALSE(use_new_process);
EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kCreateWorklet,
worklet_host->creation_method());
// The default data origin is the context origin.
std::string data_origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
data_origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
module_script_url, "context-origin",
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
CreateWorklet_CrossOriginScript_ScriptDataOrigin_Success) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})",
module_script_url.spec())));
TestSharedStorageWorkletHost* worklet_host =
test_runtime_manager().GetAttachedWorkletHost();
// The worklet host should reuse the main frame's process on Android without
// strict site isolation; otherwise, it should use a new process.
bool expected_use_new_process = AreAllSitesIsolatedForTesting();
bool actual_use_new_process =
(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess() !=
worklet_host->GetProcessHost());
EXPECT_EQ(expected_use_new_process, actual_use_new_process);
EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kCreateWorklet,
worklet_host->creation_method());
std::string data_origin_str =
url::Origin::Create(module_script_url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
data_origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
module_script_url, "script-origin",
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
CreateWorklet_CrossOriginScript_CustomDataOrigin_Success) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
kEmptySharedStorageCrossOriginAllowedReplacement)));
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())));
TestSharedStorageWorkletHost* worklet_host =
test_runtime_manager().GetAttachedWorkletHost();
// The worklet host should reuse the main frame's process on Android without
// strict site isolation; otherwise, it should use a new process.
bool expected_use_new_process = AreAllSitesIsolatedForTesting();
bool actual_use_new_process =
(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess() !=
worklet_host->GetProcessHost());
EXPECT_EQ(expected_use_new_process, actual_use_new_process);
EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kCreateWorklet,
worklet_host->creation_method());
std::string custom_data_origin_str = custom_data_origin.Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
custom_data_origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
module_script_url, custom_data_origin_str,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
// Start a worklet under b.test using the script origin as the data
// originb(cross-origin to the main frame's origin), and then append a subframe
// under b.test. Assert that they share the same process.
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
CreateWorkletAndSubframe_CrossOrigin) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})",
module_script_url.spec())));
TestSharedStorageWorkletHost* worklet_host =
test_runtime_manager().GetAttachedWorkletHost();
GURL iframe_url = https_server()->GetURL("b.test", "/empty.thml");
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url);
EXPECT_EQ(worklet_host->GetProcessHost(),
iframe_node->current_frame_host()->GetProcess());
}
// Append a subframe under b.test (cross-origin to the main frame's origin), and
// then start a worklet under b.test using the script origin as the data origin.
// Assert that they share the same process.
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
CreateSubframeAndWorklet_CrossOrigin) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL iframe_url = https_server()->GetURL("b.test", "/empty.thml");
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url);
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})",
module_script_url.spec())));
TestSharedStorageWorkletHost* worklet_host =
test_runtime_manager().GetAttachedWorkletHost();
EXPECT_EQ(worklet_host->GetProcessHost(),
iframe_node->current_frame_host()->GetProcess());
}
// Start one worklet under b.test using the script origin as the data origin
// (cross-origin to the main frame's origin), and then start another worklet
// under b.test. Assert that they share the same process.
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
CreateTwoWorklets_CrossOrigin) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})",
module_script_url.spec())));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})",
module_script_url.spec())));
std::vector<TestSharedStorageWorkletHost*> worklet_hosts =
test_runtime_manager().GetAttachedWorkletHosts();
EXPECT_EQ(worklet_hosts.size(), 2u);
EXPECT_EQ(worklet_hosts[0]->GetProcessHost(),
worklet_hosts[1]->GetProcessHost());
}
// Start a worklet under b.test via createWorklet() using the script origin as
// the data origin, and then start a worklet under b.test's iframe. Assert that
// the data stored in the first worklet can be retrieved in the second worklet.
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
CrossOriginWorklet_VerifyDataOrigin) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(
new Promise((resolve, reject) => {
sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})
.then((worklet) => {
window.testWorklet = worklet;
resolve();
});
})
)",
module_script_url.spec())));
// Expect the run() operation.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
window.testWorklet.run('test-operation', {
data: {
'set-key': 'key0',
'set-value': 'value0'
},
keepAlive: true
});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
GURL iframe_url = https_server()->GetURL("b.test", "/empty.thml");
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node->current_frame_host(), R"(
console.log(await sharedStorage.get('key0'));
)",
&out_script_url, /*expected_total_host_count=*/2);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("value0",
base::UTF16ToUTF8(console_observer.messages()[0].message));
}
// Start a worklet with b.test script in a.test's context via createWorklet(),
// and then start a worklet with same-origin script in a.test's context. Assert
// that the data stored in the first worklet can be retrieved in the second
// worklet.
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
CrossOriginScript_ContextDataOrigin_VerifyDataOrigin) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(
new Promise((resolve, reject) => {
sharedStorage.createWorklet($1, {dataOrigin: 'context-origin'})
.then((worklet) => {
window.testWorklet = worklet;
resolve();
});
})
)",
module_script_url.spec())));
// Expect the run() operation.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
window.testWorklet.run('test-operation', {
data: {
'set-key': 'key0',
'set-value': 'value0'
},
keepAlive: true
});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
GURL iframe_url = https_server()->GetURL("a.test", "/empty.thml");
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node->current_frame_host(), R"(
console.log(await sharedStorage.get('key0'));
)",
&out_script_url, /*expected_total_host_count=*/2);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("value0",
base::UTF16ToUTF8(console_observer.messages()[0].message));
}
// Start a worklet with b.test script in a.test's context via createWorklet(),
// and then start a worklet with same-origin script in a.test's context. Assert
// that the data stored in the first worklet can be retrieved in the second
// worklet.
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
CrossOriginScript_DefaultDataOrigin_VerifyDataOrigin) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(
new Promise((resolve, reject) => {
sharedStorage.createWorklet($1)
.then((worklet) => {
window.testWorklet = worklet;
resolve();
});
})
)",
module_script_url.spec())));
// Expect the run() operation.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
window.testWorklet.run('test-operation', {
data: {
'set-key': 'key0',
'set-value': 'value0'
},
keepAlive: true
});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
GURL iframe_url = https_server()->GetURL("a.test", "/empty.thml");
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node->current_frame_host(), R"(
console.log(await sharedStorage.get('key0'));
)",
&out_script_url, /*expected_total_host_count=*/2);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("value0",
base::UTF16ToUTF8(console_observer.messages()[0].message));
}
// Start a worklet with b.test script in a.test's context with data origin
// c.test via createWorklet(), and then start a worklet with same-origin script
// in c.test's context. Assert that the data stored in the first worklet can be
// retrieved in the second worklet.
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
CrossOriginScript_CustomDataOrigin_VerifyDataOrigin) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
kEmptySharedStorageCrossOriginAllowedReplacement)));
GURL custom_data_origin_url =
https_server()->GetURL("c.test", kSimplePagePath);
url::Origin custom_data_origin = url::Origin::Create(custom_data_origin_url);
EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(
new Promise((resolve, reject) => {
sharedStorage.createWorklet($1, {dataOrigin: $2})
.then((worklet) => {
window.testWorklet = worklet;
resolve();
});
})
)",
module_script_url.spec(),
custom_data_origin.Serialize())));
// Expect the run() operation.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
window.testWorklet.run('test-operation', {
data: {
'set-key': 'key0',
'set-value': 'value0'
},
keepAlive: true
});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(), custom_data_origin_url);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node->current_frame_host(), R"(
console.log(await sharedStorage.get('key0'));
)",
&out_script_url, /*expected_total_host_count=*/2);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("value0",
base::UTF16ToUTF8(console_observer.messages()[0].message));
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
AddModule_CrossOriginScript_FailedCors) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
kEmptyAccessControlAllowOriginReplacement,
kEmptySharedStorageCrossOriginAllowedReplacement)));
EXPECT_THAT(EvalJs(shell(), JsReplace("sharedStorage.worklet.addModule($1)",
module_script_url.spec())),
EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load")));
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
AddModule_CrossOriginScript_Success) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
kEmptySharedStorageCrossOriginAllowedReplacement)));
EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.worklet.addModule($1)",
module_script_url.spec())));
}
// Start a worklet with b.test script (cross-origin to the main frame's origin),
// but a.test data and then append a subframe under b.test. Assert that they
// share the same process.
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
CreateWorkletAndSubframe_AddModule_CrossOriginScript) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
kEmptySharedStorageCrossOriginAllowedReplacement)));
EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.worklet.addModule($1)",
module_script_url.spec())));
TestSharedStorageWorkletHost* worklet_host =
test_runtime_manager().GetAttachedWorkletHost();
GURL iframe_url = https_server()->GetURL("a.test", "/empty.thml");
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url);
EXPECT_EQ(worklet_host->GetProcessHost(),
iframe_node->current_frame_host()->GetProcess());
EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kAddModule,
worklet_host->creation_method());
}
// Start a worklet with b.test script but a.test data, and then start a worklet
// under a.test's iframe. Assert that the data stored in the first worklet can
// be retrieved in the second worklet.
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
AddModule_CrossOriginScript_VerifyDataOrigin) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
kEmptySharedStorageCrossOriginAllowedReplacement)));
EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.worklet.addModule($1)",
module_script_url.spec())));
// Expect the run() operation.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('test-operation', {
data: {
'set-key': 'key0',
'set-value': 'value0'
},
keepAlive: true
});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
GURL iframe_url = https_server()->GetURL("a.test", "/empty.thml");
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node->current_frame_host(), R"(
console.log(await sharedStorage.get('key0'));
)",
&out_script_url, /*expected_total_host_count=*/2);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("value0",
base::UTF16ToUTF8(console_observer.messages()[0].message));
}
INSTANTIATE_TEST_SUITE_P(All,
SharedStorageBrowserTest,
testing::Bool(),
describe_param);
class SharedStorageAllowURNsInIframesBrowserTest
: public SharedStorageBrowserTestBase,
public testing::WithParamInterface<bool> {
public:
SharedStorageAllowURNsInIframesBrowserTest() {
allow_urns_in_frames_feature_.InitAndEnableFeature(
blink::features::kAllowURNsInIframes);
fenced_frame_api_change_feature_.InitWithFeatureState(
blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig());
}
bool ResolveSelectURLToConfig() override { return GetParam(); }
private:
base::test::ScopedFeatureList allow_urns_in_frames_feature_;
base::test::ScopedFeatureList fenced_frame_api_change_feature_;
};
IN_PROC_BROWSER_TEST_P(SharedStorageAllowURNsInIframesBrowserTest,
RenderSelectURLResultInIframe) {
GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url));
url::Origin shared_storage_origin =
url::Origin::Create(https_server()->GetURL("b.test", kSimplePagePath));
std::optional<GURL> urn_uuid =
SelectFrom8URLsInContext(shared_storage_origin);
ASSERT_TRUE(urn_uuid.has_value());
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(), *urn_uuid);
EXPECT_EQ(iframe_node->current_url(),
https_server()->GetURL("b.test", "/fenced_frames/title1.html"));
EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin), kBudgetAllowed);
GURL new_page_url = https_server()->GetURL("c.test", kSimplePagePath);
TestNavigationObserver top_navigation_observer(shell()->web_contents());
EXPECT_TRUE(
ExecJs(iframe_node, JsReplace("top.location = $1", new_page_url)));
top_navigation_observer.Wait();
// After the top navigation, log(8)=3 bits should have been withdrawn from the
// original shared storage origin.
EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin),
kBudgetAllowed - 3);
}
INSTANTIATE_TEST_SUITE_P(All,
SharedStorageAllowURNsInIframesBrowserTest,
testing::Bool(),
describe_param);
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
StringRoundTrip_SetThenGet_UnpairedSurrogatesArePreserved) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL script_url =
https_server()->GetURL("a.test", "/shared_storage/round_trip.js");
EXPECT_TRUE(ExecJs(
shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
std::vector<std::tuple<std::vector<uint16_t>, bool>> string_test_cases = {
{std::vector<uint16_t>({0x0068}), true}, // letter 'h'
{std::vector<uint16_t>({0xd800}), false}, // lone high surrogate
{std::vector<uint16_t>({0xdc00}), false}, // lone low surrogate
{std::vector<uint16_t>({0xd800, 0xdc00}), true}, // surrogate pair
};
for (size_t i = 0; i < string_test_cases.size(); ++i) {
const std::vector<uint16_t>& char_code_array =
std::get<0>(string_test_cases[i]);
std::u16string test_string =
std::u16string(char_code_array.begin(), char_code_array.end());
base::Value::List char_code_values;
for (uint16_t char_code : char_code_array) {
char_code_values.Append(static_cast<int>(char_code));
}
const bool expected_valid = std::get<1>(string_test_cases[i]);
// Check validity of UTF-16.
std::string base_output;
EXPECT_EQ(expected_valid,
base::UTF16ToUTF8(test_string.c_str(), test_string.size(),
&base_output));
// The dummy assignment is necessary, because otherwise under the hood,
// `ExecJs` makes a call that tries to evaluate the most recent script
// result as a `base::Value`, and `char_code_values` causes that to fail.
EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(window.charCodeArray = $1;
window.dummyAssignment = 0;)",
std::move(char_code_values))));
// We will wait for 1 "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('set-get-operation',
{data: {'key': 'asValue',
'valueCharCodeArray' : charCodeArray},
keepAlive: true});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(i + 1, console_observer.messages().size());
EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages().back().message),
testing::HasSubstr("was retrieved: true"));
}
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
StringRoundTrip_SetThenKeys_UnpairedSurrogatesArePreserved) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL script_url =
https_server()->GetURL("a.test", "/shared_storage/round_trip.js");
EXPECT_TRUE(ExecJs(
shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
std::vector<std::tuple<std::vector<uint16_t>, bool>> string_test_cases = {
{std::vector<uint16_t>({0x0068}), true}, // letter 'h'
{std::vector<uint16_t>({0xd800}), false}, // lone high surrogate
{std::vector<uint16_t>({0xdc00}), false}, // lone low surrogate
{std::vector<uint16_t>({0xd800, 0xdc00}), true}, // surrogate pair
};
for (size_t i = 0; i < string_test_cases.size(); ++i) {
const std::vector<uint16_t>& char_code_array =
std::get<0>(string_test_cases[i]);
std::u16string test_string =
std::u16string(char_code_array.begin(), char_code_array.end());
base::Value::List char_code_values;
for (uint16_t char_code : char_code_array) {
char_code_values.Append(static_cast<int>(char_code));
}
const bool expected_valid = std::get<1>(string_test_cases[i]);
// Check validity of UTF-16.
std::string base_output;
EXPECT_EQ(expected_valid,
base::UTF16ToUTF8(test_string.c_str(), test_string.size(),
&base_output));
// The dummy assignment is necessary, because otherwise under the hood,
// `ExecJs` makes a call that tries to evaluate the most recent script
// result as a `base::Value`, and `char_code_values` causes that to fail.
EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(window.charCodeArray = $1;
window.dummyAssignment = 0;)",
std::move(char_code_values))));
// We will wait for 1 "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('set-keys-operation',
{data: {'keyCharCodeArray' : charCodeArray,
'value': 'asKey'},
keepAlive: true});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(i + 1, console_observer.messages().size());
EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages().back().message),
testing::HasSubstr("was retrieved: true"));
}
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
StringRoundTrip_AppendThenDelete_UnpairedSurrogatesArePreserved) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL script_url =
https_server()->GetURL("a.test", "/shared_storage/round_trip.js");
EXPECT_TRUE(ExecJs(
shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
std::vector<std::tuple<std::vector<uint16_t>, bool>> string_test_cases = {
{std::vector<uint16_t>({0x0068}), true}, // letter 'h'
{std::vector<uint16_t>({0xd800}), false}, // lone high surrogate
{std::vector<uint16_t>({0xdc00}), false}, // lone low surrogate
{std::vector<uint16_t>({0xd800, 0xdc00}), true}, // surrogate pair
};
for (size_t i = 0; i < string_test_cases.size(); ++i) {
const std::vector<uint16_t>& char_code_array =
std::get<0>(string_test_cases[i]);
std::u16string test_string =
std::u16string(char_code_array.begin(), char_code_array.end());
base::Value::List char_code_values;
for (uint16_t char_code : char_code_array) {
char_code_values.Append(static_cast<int>(char_code));
}
const bool expected_valid = std::get<1>(string_test_cases[i]);
// Check validity of UTF-16.
std::string base_output;
EXPECT_EQ(expected_valid,
base::UTF16ToUTF8(test_string.c_str(), test_string.size(),
&base_output));
// The dummy assignment is necessary, because otherwise under the hood,
// `ExecJs` makes a call that tries to evaluate the most recent script
// result as a `base::Value`, and `char_code_values` causes that to fail.
EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(window.charCodeArray = $1;
window.dummyAssignment = 0;)",
std::move(char_code_values))));
// We will wait for 1 "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('append-delete-operation',
{data: {'keyCharCodeArray' : charCodeArray,
'value': 'asKey'},
keepAlive: true});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(i + 1, console_observer.messages().size());
EXPECT_EQ(u"delete success: true",
console_observer.messages().back().message);
}
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
StringRoundTrip_AppendThenEntries_UnpairedSurrogatesArePreserved) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL script_url =
https_server()->GetURL("a.test", "/shared_storage/round_trip.js");
EXPECT_TRUE(ExecJs(
shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
std::vector<std::tuple<std::vector<uint16_t>, bool>> string_test_cases = {
{std::vector<uint16_t>({0x0068}), true}, // letter 'h'
{std::vector<uint16_t>({0xd800}), false}, // lone high surrogate
{std::vector<uint16_t>({0xdc00}), false}, // lone low surrogate
{std::vector<uint16_t>({0xd800, 0xdc00}), true}, // surrogate pair
};
for (size_t i = 0; i < string_test_cases.size(); ++i) {
const std::vector<uint16_t>& char_code_array =
std::get<0>(string_test_cases[i]);
std::u16string test_string =
std::u16string(char_code_array.begin(), char_code_array.end());
base::Value::List char_code_values;
for (uint16_t char_code : char_code_array) {
char_code_values.Append(static_cast<int>(char_code));
}
const bool expected_valid = std::get<1>(string_test_cases[i]);
// Check validity of UTF-16.
std::string base_output;
EXPECT_EQ(expected_valid,
base::UTF16ToUTF8(test_string.c_str(), test_string.size(),
&base_output));
// The dummy assignment is necessary, because otherwise under the hood,
// `ExecJs` makes a call that tries to evaluate the most recent script
// result as a `base::Value`, and `char_code_values` causes that to fail.
EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(window.charCodeArray = $1;
window.dummyAssignment = 0;)",
std::move(char_code_values))));
// We will wait for 1 "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('append-entries-operation',
{data: {'key': 'asValue',
'valueCharCodeArray': charCodeArray},
keepAlive: true});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(i + 1, console_observer.messages().size());
EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages().back().message),
testing::HasSubstr("was retrieved: true"));
}
}
class SharedStorageHeaderObserverBrowserTest
: public SharedStorageBrowserTestBase {
public:
using OperationResult = storage::SharedStorageManager::OperationResult;
void FinishSetup() override {
https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
auto observer = std::make_unique<TestSharedStorageHeaderObserver>(
GetStoragePartition());
observer_ = observer->GetMutableWeakPtr();
static_cast<StoragePartitionImpl*>(GetStoragePartition())
->OverrideSharedStorageHeaderObserverForTesting(std::move(observer));
}
bool NavigateToURLWithResponse(
Shell* window,
const GURL& url,
net::test_server::ControllableHttpResponse& response,
net::HttpStatusCode http_status,
const std::string& content_type = std::string("text/html"),
const std::string& content = std::string(),
const std::vector<std::string>& cookies = {},
const std::vector<std::string>& extra_headers = {}) {
auto* web_contents = window->web_contents();
DCHECK(web_contents);
// Prepare for the navigation.
WaitForLoadStop(web_contents);
TestNavigationObserver same_tab_observer(
web_contents,
/*expected_number_of_navigations=*/1,
MessageLoopRunner::QuitMode::IMMEDIATE,
/*ignore_uncommitted_navigations=*/false);
if (!blink::IsRendererDebugURL(url)) {
same_tab_observer.set_expected_initial_url(url);
}
// This mimics behavior of Shell::LoadURL...
NavigationController::LoadURLParams params(url);
params.transition_type = ui::PageTransitionFromInt(
ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
web_contents->GetController().LoadURLWithParams(params);
web_contents->GetOutermostWebContents()->Focus();
response.WaitForRequest();
response.Send(http_status, content_type, content, cookies, extra_headers);
response.Done();
// Wait until the expected number of navigations finish.
same_tab_observer.Wait();
if (!IsLastCommittedEntryOfPageType(web_contents, PAGE_TYPE_NORMAL)) {
return false;
}
bool is_same_url = web_contents->GetLastCommittedURL() == url;
if (!is_same_url) {
DLOG(WARNING) << "Expected URL " << url << " but observed "
<< web_contents->GetLastCommittedURL();
}
return is_same_url;
}
std::string ReplacePortInString(std::string str) {
return ::content::ReplacePortInString(str, port());
}
void SetUpResponsesAndNavigateMainPage(
std::string main_hostname,
std::string subresource_or_subframe_hostname,
std::optional<std::string> shared_storage_permissions = std::nullopt,
bool is_image = false,
std::vector<std::string> redirect_hostnames = {}) {
subresource_or_subframe_content_type_ =
is_image ? "image/png" : "text/plain;charset=UTF-8";
const char* subresource_or_subframe_path =
is_image ? kPngPath : kTitle1Path;
subresource_or_subframe_response_ =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), subresource_or_subframe_path);
std::unique_ptr<net::test_server::ControllableHttpResponse> main_response;
if (shared_storage_permissions.has_value()) {
main_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), kSimplePagePath);
}
DCHECK_LT(redirect_hostnames.size(), 4u)
<< "You need to add more paths to "
"`SetUpResponsesAndNavigateMainPage()`. Currently there are enough "
"for up to 3 redirects.";
std::vector<std::string> paths({kTitle2Path, kTitle3Path, kTitle4Path});
for (size_t i = 0; i < redirect_hostnames.size(); i++) {
auto response =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), paths[i]);
redirected_responses_.push_back(std::move(response));
}
ASSERT_TRUE(https_server()->Start());
main_url_ =
https_server()->GetURL(std::move(main_hostname), kSimplePagePath);
subresource_or_subframe_url_ =
https_server()->GetURL(std::move(subresource_or_subframe_hostname),
subresource_or_subframe_path);
subresource_or_subframe_origin_ =
url::Origin::Create(subresource_or_subframe_url_);
for (size_t i = 0; i < redirect_hostnames.size(); i++) {
redirect_urls_.emplace_back(
https_server()->GetURL(redirect_hostnames[i], paths[i]));
redirect_origins_.emplace_back(
url::Origin::Create(redirect_urls_.back()));
}
if (shared_storage_permissions.has_value()) {
EXPECT_TRUE(NavigateToURLWithResponse(
shell(), main_url_, *main_response,
/*http_status=*/net::HTTP_OK,
/*content_type=*/"text/plain;charset=UTF-8",
/*content=*/{}, /*cookies=*/{}, /*extra_headers=*/
{"Permissions-Policy: shared-storage=" +
ReplacePortInString(
std::move(shared_storage_permissions.value()))}));
} else {
EXPECT_TRUE(NavigateToURL(shell(), main_url_));
}
}
void WaitForRequestAndSendResponse(
net::test_server::ControllableHttpResponse& response,
bool expect_writable_header,
net::HttpStatusCode http_status,
const std::string& content_type,
const std::vector<std::string>& extra_headers) {
response.WaitForRequest();
if (expect_writable_header) {
ASSERT_TRUE(base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Writable"));
} else {
EXPECT_FALSE(base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Writable"));
}
EXPECT_EQ(response.http_request()->content, "");
response.Send(http_status, content_type,
/*content=*/{}, /*cookies=*/{}, extra_headers);
response.Done();
}
void WaitForSubresourceOrSubframeRequestAndSendResponse(
bool expect_writable_header,
net::HttpStatusCode http_status,
const std::vector<std::string>& extra_headers) {
WaitForRequestAndSendResponse(
*subresource_or_subframe_response_, expect_writable_header, http_status,
subresource_or_subframe_content_type_, extra_headers);
}
void WaitForRedirectRequestAndSendResponse(
bool expect_writable_header,
net::HttpStatusCode http_status,
const std::vector<std::string>& extra_headers,
size_t redirect_index = 0) {
ASSERT_LT(redirect_index, redirected_responses_.size());
WaitForRequestAndSendResponse(
*redirected_responses_[redirect_index], expect_writable_header,
http_status, subresource_or_subframe_content_type_, extra_headers);
}
void FetchWithSharedStorageWritable(const ToRenderFrameHost& execution_target,
const GURL& url) {
EXPECT_TRUE(ExecJs(execution_target,
JsReplace(R"(
fetch($1, {sharedStorageWritable: true});
)",
url.spec()),
EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
}
void StartServerAndLoadMainURLWithSameOriginSubresourceOrSubframe(
std::string main_hostname,
std::string main_path) {
ASSERT_TRUE(https_server()->Start());
main_url_ =
https_server()->GetURL(std::move(main_hostname), std::move(main_path));
subresource_or_subframe_origin_ = url::Origin::Create(main_url_);
EXPECT_TRUE(NavigateToURL(shell(), main_url_));
}
void CreateSharedStorageWritableImage(
const ToRenderFrameHost& execution_target,
const GURL& url) {
EXPECT_TRUE(ExecJs(execution_target, JsReplace(R"(
let img = document.createElement('img');
img.src = $1;
img.sharedStorageWritable = true;
document.body.appendChild(img);
)",
url.spec())));
}
void CreateSharedStorageWritableIframe(
const ToRenderFrameHost& execution_target,
const GURL& url) {
EXPECT_TRUE(ExecJs(execution_target, JsReplace(R"(
let frame = document.createElement('iframe');
frame.sharedStorageWritable = true;
frame.src = $1;
document.body.appendChild(frame);
)",
url.spec())));
}
protected:
base::WeakPtr<TestSharedStorageHeaderObserver> observer_;
std::unique_ptr<net::test_server::ControllableHttpResponse>
subresource_or_subframe_response_;
std::vector<std::unique_ptr<net::test_server::ControllableHttpResponse>>
redirected_responses_;
GURL main_url_;
GURL subresource_or_subframe_url_;
std::vector<GURL> redirect_urls_;
url::Origin subresource_or_subframe_origin_;
std::vector<url::Origin> redirect_origins_;
std::string subresource_or_subframe_content_type_;
};
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_SameOrigin_PermissionsDefault_ClearSetAppend) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_SameOrigin_PermissionsNone) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/"()");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=wont;key=set"});
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_SameOrigin_PermissionsAll_ClearSetAppend) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/"*");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_SameOrigin_PermissionsSelf_ClearSetAppend) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/"self");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_CrossOrigin_PermissionsDefault_ClearSetAppend) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
// Create an iframe that's same-origin to the fetch URL.
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(),
https_server()->GetURL("b.test", kTitle2Path));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_CrossOrigin_PermissionsNone) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/"()");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=wont;key=set"});
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_CrossOrigin_PermissionsAll_ClearSetAppend) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/"*");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
// Create an iframe that's same-origin to the fetch URL.
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(),
https_server()->GetURL("b.test", kTitle2Path));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_CrossOrigin_PermissionsSelf) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/"self");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=wont;key=set"});
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Fetch_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteInitial) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options1;
methods_with_options1.push_back(MojomClearMethod());
methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options1.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options1)));
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *"});
// There won't be additional operations invoked.
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options2;
methods_with_options2.push_back(MojomClearMethod());
methods_with_options2.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options2.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options2)));
// Create an iframe that's same-origin to the original fetch URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node1, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Fetch_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteFinal) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *"});
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back());
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options)));
// Create an iframe that's same-origin to the original fetch URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node1, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
// Nothing was set in b.test's shared storage.
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[1].message));
// Create an iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url, /*expected_total_host_count=*/2u);
// The entry was set in c.test's shared storage.
EXPECT_EQ(4u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[3].message));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Fetch_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteBoth) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options1;
methods_with_options1.push_back(MojomClearMethod());
methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options1.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options1)));
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"});
// There will now have been a total of 2 operations (1 previous, 1 current).
ASSERT_TRUE(observer_);
observer_->WaitForOperations(2);
EXPECT_EQ(observer_->header_results().size(), 2u);
EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back());
std::vector<MethodWithOptionsPtr> methods_with_options2;
methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 2u);
EXPECT_EQ(observer_->operations()[1],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options2)));
// Create an iframe that's same-origin to the original fetch URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node1, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
// Only one entry was set in b.test's shared storage.
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message));
// Create an iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url, /*expected_total_host_count=*/2u);
// One entry was set in c.test's shared storage.
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_CrossOrigin_Redirect_InititalAllowed_FinalDenied) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options1;
methods_with_options1.push_back(MojomClearMethod());
methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options1.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options1)));
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=wont;key=set"});
// No new operations are invoked.
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options2;
methods_with_options2.push_back(MojomClearMethod());
methods_with_options2.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options2.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options2)));
// Create an iframe that's same-origin to the original fetch URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node1, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
// Create an iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
EXPECT_THAT(EvalJs(iframe_node2, R"(
sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"The \"shared-storage\" Permissions Policy "
"denied the method")));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Fetch_CrossOrigin_Redirect_InititalAllowed_IntermediateDenied_FinalAllowed_WriteInitialAndFinal) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://d.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test", "d.test"}));
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.front().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options1;
methods_with_options1.push_back(MojomClearMethod());
methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options1.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options1)));
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_TEMPORARY_REDIRECT,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: append;key=wont;value=set"},
/*redirect_index=*/0);
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"},
/*redirect_index=*/1);
// There will now have been a total of 2 operations (1 previous, 1 current).
ASSERT_TRUE(observer_);
observer_->WaitForOperations(2);
EXPECT_EQ(observer_->header_results().size(), 2u);
EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back());
std::vector<MethodWithOptionsPtr> methods_with_options2;
methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 2u);
EXPECT_EQ(observer_->operations()[1],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options2)));
// Create an iframe that's same-origin to the original fetch URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node1, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
// Only one entry was set in b.test's shared storage.
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message));
// Create an iframe that's same-origin to the first redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.front());
// c.test does not have permission to use shared storage.
EXPECT_THAT(EvalJs(iframe_node2, R"(
sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"The \"shared-storage\" Permissions Policy "
"denied the method")));
// Create an iframe that's same-origin to the second redirect URL.
FrameTreeNode* iframe_node3 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
ExecuteScriptInWorklet(iframe_node3, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url, /*expected_total_host_count=*/2u);
// One entry was set in d.test's shared storage.
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Fetch_CrossOrigin_Redirect_InitialDenied_FinalAllowed_WriteFinal) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://c.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
// No operations are invoked.
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"});
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(), redirect_origins_.back());
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options)));
WebContentsConsoleObserver console_observer(shell()->web_contents());
// Create an iframe that's same-origin to the original fetch URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
EXPECT_THAT(EvalJs(iframe_node1, R"(
sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"The \"shared-storage\" Permissions Policy "
"denied the method")));
// Create an iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
// one key was set in c.test's shared storage.
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_SameOrigin_PermissionsDefault_VerifyDelete) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test");
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(sharedStorage.set('hello', 'world');)"));
GURL script_url =
https_server()->GetURL("a.test", "/shared_storage/getter_module.js");
EXPECT_TRUE(ExecJs(
shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('get-operation', {data: {'key': 'hello'},
keepAlive: true});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("sharedStorage.length(): 1",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("sharedStorage.get('hello'): world",
base::UTF16ToUTF8(console_observer.messages()[1].message));
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: \"delete\";key=hello"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"hello"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('get-operation', {data: {'key': 'hello'}});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(4u, console_observer.messages().size());
EXPECT_EQ("sharedStorage.length(): 0",
base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("sharedStorage.get('hello'): undefined",
base::UTF16ToUTF8(console_observer.messages()[3].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_SameOrigin_PermissionsDefault_VerifyClear) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test");
WebContentsConsoleObserver console_observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), R"(sharedStorage.set('hello', 'world');)"));
GURL script_url =
https_server()->GetURL("a.test", "/shared_storage/getter_module.js");
EXPECT_TRUE(ExecJs(
shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('get-operation', {data: {'key': 'hello'},
keepAlive: true});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("sharedStorage.length(): 1",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("sharedStorage.get('hello'): world",
base::UTF16ToUTF8(console_observer.messages()[1].message));
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: \"clear\""});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
// There is 1 more "worklet operation": `run()`.
test_runtime_manager()
.GetAttachedWorkletHost()
->SetExpectedWorkletResponsesCount(1);
EXPECT_TRUE(ExecJs(shell(), R"(
sharedStorage.run('get-operation', {data: {'key': 'hello'}});
)"));
test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses();
EXPECT_EQ(4u, console_observer.messages().size());
EXPECT_EQ("sharedStorage.length(): 0",
base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("sharedStorage.get('hello'): undefined",
base::UTF16ToUTF8(console_observer.messages()[3].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Fetch_SameOrigin_PermissionsDefault_MultipleSet_Bytes) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: set;key=:aGVsbG8=:;value=:d29ybGQ=:, "
"set;value=:ZnJpZW5k:;key=:aGVsbG8=:;ignore_if_present=?0, "
"set;ignore_if_present;key=hello;value=there"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/false));
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"friend",
/*ignore_if_present=*/false));
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"there",
/*ignore_if_present=*/true));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("friend",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
NetworkServiceRestarts_HeaderObserverContinuesWorking) {
subresource_or_subframe_response_ =
std::make_unique<net::test_server::ControllableHttpResponse>(
https_server(), kTitle1Path);
ASSERT_TRUE(https_server()->Start());
if (IsInProcessNetworkService()) {
return;
}
main_url_ = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url_));
ASSERT_TRUE(observer_);
SimulateNetworkServiceCrash();
static_cast<StoragePartitionImpl*>(GetStoragePartition())
->FlushNetworkInterfaceForTesting();
// We should still have an `observer_`.
ASSERT_TRUE(observer_);
// We need to reinitialize and renavigate to `main_url_` after network service
// restart, if we want to prevent the shared storage operations below from
// being deferred then dropped due to switching to a new
// `NavigationOrDocumentHandle` for the main frame which hasn't yet seen a
// commit.
main_url_ = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), main_url_));
// We will still have an `observer_` after renavigating.
ASSERT_TRUE(observer_);
// We also need to reinitialize `subresource_or_subframe_url_` after network
// service restart. Fetching with `sharedStorageWritable` works as expected.
subresource_or_subframe_url_ = https_server()->GetURL("a.test", kTitle1Path);
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
subresource_or_subframe_origin_ =
url::Origin::Create(subresource_or_subframe_url_);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
InvalidHeader_NoOperationsInvoked) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: clear, invalid?item"});
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[0].message));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
ParsableUnrecognizedItemSkipped_RecognizedOperationsInvoked) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: clear, unrecognized;unknown_param=1,"
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
ExtraParametersIgnored) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: clear;unknown_param=1,"
"set;another_unknown=willIgnore;key=\"hello\";value=\"world\", "
"append;key=extra;key=hello;value=there;ignore_if_present;pi=3.14,"
"delete;value=ignored;key=toDelete;ignore_if_present=?0"});
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/false));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"toDelete"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
KeyOrValueLengthInvalid_ItemSkipped) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test");
FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{base::StrCat({"Shared-Storage-Write: clear, ",
"set;key=\"\";value=v,append;key=\"\";value=v,",
"delete;key=\"\",clear"})});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomClearMethod());
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[0].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Image_SameOrigin_PermissionsDefault) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/std::nullopt,
/*is_image=*/true);
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: set;key=a;value=b"});
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Image_SameOrigin_PermissionsNone) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/"()",
/*is_image=*/true);
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Image_SameOrigin_PermissionsAll) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/"*",
/*is_image=*/true);
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Image_SameOrigin_PermissionsSelf) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/"self",
/*is_image=*/true);
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Image_CrossOrigin_PermissionsDefault) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/std::nullopt,
/*is_image=*/true);
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: set;key=a;value=b"});
// Create iframe that's same-origin to the image URL.
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(),
https_server()->GetURL("b.test", kTitle2Path));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node, R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Image_CrossOrigin_PermissionsNone) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/"()",
/*is_image=*/true);
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Image_CrossOrigin_PermissionsAll) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/"*",
/*is_image=*/true);
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
// Create iframe that's same-origin to the image URL.
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(),
https_server()->GetURL("b.test", kTitle2Path));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node, R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Image_CrossOrigin_PermissionsSelf) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/"self",
/*is_image=*/true);
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Image_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteInitial) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")",
/*is_image=*/true,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *"});
// Create an iframe that's same-origin to the original image URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
// There won't be additional operations invoked from the redirect, just the
// original 3.
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Image_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteFinal) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")",
/*is_image=*/true,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *"});
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back());
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options)));
// Create an iframe that's same-origin to the original image URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node1, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
// Nothing was set in b.test's shared storage.
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[1].message));
// Create another iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url, /*expected_total_host_count=*/2u);
// The entry was set in c.test's shared storage.
EXPECT_EQ(4u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[3].message));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Image_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteBoth) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")",
/*is_image=*/true,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"});
// There will now have been a total of 2 operations (1 previous, 1 current).
ASSERT_TRUE(observer_);
observer_->WaitForOperations(2);
EXPECT_EQ(observer_->header_results().size(), 2u);
EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back());
EXPECT_EQ(observer_->operations().size(), 2u);
std::vector<MethodWithOptionsPtr> methods_with_options1;
methods_with_options1.push_back(MojomClearMethod());
methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options1.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options1)));
std::vector<MethodWithOptionsPtr> methods_with_options2;
methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations()[1],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options2)));
// Create an iframe that's same-origin to the original image URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node1, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
// Only one entry was set in b.test's shared storage.
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message));
// Create another iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url, /*expected_total_host_count=*/2u);
// One entry was set in c.test's shared storage.
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Image_CrossOrigin_Redirect_InititalAllowed_FinalDenied) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\")",
/*is_image=*/true,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=wont;key=set"});
// There won't be additional operations invoked from the redirect, just the
// original 3.
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
// Create an iframe that's same-origin to the original image URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node1, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
// Create another iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
EXPECT_THAT(EvalJs(iframe_node2, R"(
sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"The \"shared-storage\" Permissions Policy "
"denied the method")));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Image_CrossOrigin_Redirect_InititalAllowed_IntermediateDenied_FinalAllowed_WriteInitialAndFinal) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://d.test:{{port}}\")",
/*is_image=*/true,
/*redirect_hostnames=*/std::vector<std::string>({"c.test", "d.test"}));
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.front().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options1;
methods_with_options1.push_back(MojomClearMethod());
methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options1.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options1)));
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_TEMPORARY_REDIRECT,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: append;key=wont;value=set"},
/*redirect_index=*/0);
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"},
/*redirect_index=*/1);
// There will now have been a total of 2 operations (1 previous, 1 current).
ASSERT_TRUE(observer_);
observer_->WaitForOperations(2);
EXPECT_EQ(observer_->header_results().size(), 2u);
EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back());
std::vector<MethodWithOptionsPtr> methods_with_options2;
methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 2u);
EXPECT_EQ(observer_->operations()[1],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options2)));
// Create an iframe that's same-origin to the original image URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node1, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
// Only one entry was set in b.test's shared storage.
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message));
// Create an iframe that's same-origin to the first redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.front());
// c.test does not have permission to use shared storage.
EXPECT_THAT(EvalJs(iframe_node2, R"(
sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"The \"shared-storage\" Permissions Policy "
"denied the method")));
// Create an iframe that's same-origin to the second redirect URL.
FrameTreeNode* iframe_node3 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
ExecuteScriptInWorklet(iframe_node3, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url, /*expected_total_host_count=*/2u);
// One entry was set in d.test's shared storage.
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Image_CrossOrigin_Redirect_InitialDenied_FinalAllowed_WriteFinal) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://c.test:{{port}}\")",
/*is_image=*/true,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
// No operations are invoked.
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"});
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(), redirect_origins_.back());
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options)));
WebContentsConsoleObserver console_observer(shell()->web_contents());
// Create an iframe that's same-origin to the original image URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
EXPECT_THAT(EvalJs(iframe_node1, R"(
sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"The \"shared-storage\" Permissions Policy "
"denied the method")));
// Create an iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
// one key was set in c.test's shared storage.
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Image_ContentAttributeIncluded_Set_2ndImageCached_NotSet) {
WebContentsConsoleObserver console_observer(shell()->web_contents());
StartServerAndLoadMainURLWithSameOriginSubresourceOrSubframe(
/*main_hostname=*/"a.test",
/*main_path=*/
"/shared_storage/page-with-shared-storage-writable-image.html");
// Wait for the image onload to fire.
EXPECT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Image Loaded",
base::UTF16ToUTF8(console_observer.messages()[0].message));
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options1;
methods_with_options1.push_back(
MojomAppendMethod(/*key=*/u"a", /*value=*/u"b"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options1)));
EXPECT_EQ(
true,
EvalJs(
shell(),
JsReplace(
R"(
new Promise((resolve, reject) => {
let img = document.createElement('img');
img.src = $1;
img.onload = () => resolve(true);
img.sharedStorageWritable = true;
document.body.appendChild(img);
})
)",
https_server()
->GetURL("a.test",
"/shared_storage/shared-storage-writable-pixel.png")
.spec())));
// Create an iframe that's same-origin in order to run a second worklet.
FrameTreeNode* iframe_node =
CreateIFrame(PrimaryFrameTreeNodeRoot(), main_url_);
ExecuteScriptInWorklet(iframe_node, R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url, /*expected_total_host_count=*/2u);
// The value 'b' for the key 'a' is unchanged (nothing is appended to it).
EXPECT_EQ(6u, console_observer.messages().size());
EXPECT_EQ("Image Loaded",
base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[4].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[5].message));
// No new operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options2;
methods_with_options2.push_back(
MojomAppendMethod(/*key=*/u"a", /*value=*/u"b"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options2)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Image_ContentAttributeNotIncluded_NotSet) {
WebContentsConsoleObserver console_observer(shell()->web_contents());
StartServerAndLoadMainURLWithSameOriginSubresourceOrSubframe(
/*main_hostname=*/"a.test",
/*main_path=*/
"/shared_storage/page-with-non-shared-storage-writable-image.html");
// Wait for the image onload to fire.
EXPECT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Image Loaded",
base::UTF16ToUTF8(console_observer.messages()[0].message));
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[2].message));
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Iframe_SameOrigin_PermissionsDefault) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/std::nullopt,
/*is_image=*/false);
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: set;key=a;value=b"});
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Iframe_SameOrigin_PermissionsNone) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/"()",
/*is_image=*/false);
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Iframe_SameOrigin_PermissionsAll) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/"*",
/*is_image=*/false);
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Iframe_SameOrigin_PermissionsSelf) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"a.test",
/*shared_storage_permissions=*/"self",
/*is_image=*/false);
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Iframe_CrossOrigin_PermissionsDefault) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/std::nullopt,
/*is_image=*/false);
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Shared-Storage-Write: set;key=a;value=b"});
// Create another iframe that's same-origin to the first iframe URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(),
https_server()->GetURL("b.test", kTitle2Path));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Iframe_CrossOrigin_PermissionsNone) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/"()",
/*is_image=*/false);
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Iframe_CrossOrigin_PermissionsAll) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/"*",
/*is_image=*/false);
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
// Create another iframe that's same-origin to the first iframe URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(),
https_server()->GetURL("b.test", kTitle2Path));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Iframe_CrossOrigin_PermissionsSelf) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/"self",
/*is_image=*/false);
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: set;key=a;value=b"});
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Iframe_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteInitial) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *"});
// Create another iframe that's same-origin to the original iframe URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
// There won't be additional operations invoked from the redirect, just the
// original 3.
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Iframe_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteFinal) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *"});
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back());
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options)));
// Create another iframe that's same-origin to the original iframe URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
// Nothing was set in b.test's shared storage.
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[1].message));
// Create another iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node3 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
ExecuteScriptInWorklet(iframe_node3, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url, /*expected_total_host_count=*/2u);
// The entry was set in c.test's shared storage.
EXPECT_EQ(4u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[3].message));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Iframe_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteBoth) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"});
// There will now have been a total of 2 operations (1 previous, 1 current).
ASSERT_TRUE(observer_);
observer_->WaitForOperations(2);
EXPECT_EQ(observer_->header_results().size(), 2u);
EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back());
EXPECT_EQ(observer_->operations().size(), 2u);
std::vector<MethodWithOptionsPtr> methods_with_options1;
methods_with_options1.push_back(MojomClearMethod());
methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options1.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options1)));
std::vector<MethodWithOptionsPtr> methods_with_options2;
methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations()[1],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options2)));
// Create another iframe that's same-origin to the original iframe URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
// Only one entry was set in b.test's shared storage.
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message));
// Create another iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node3 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
ExecuteScriptInWorklet(iframe_node3, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url, /*expected_total_host_count=*/2u);
// One entry was set in c.test's shared storage.
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Iframe_CrossOrigin_Redirect_InititalAllowed_FinalDenied) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=wont;key=set"});
// There won't be additional operations invoked from the redirect, just the
// original 3.
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomClearMethod());
methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
// Create an iframe that's same-origin to the original iframe URL.
FrameTreeNode* iframe_node1 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node1, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
// Create another iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
EXPECT_THAT(EvalJs(iframe_node2, R"(
sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"The \"shared-storage\" Permissions Policy "
"denied the method")));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Iframe_CrossOrigin_Redirect_InititalAllowed_IntermediateDenied_FinalAllowed_WriteInitialAndFinal) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://b.test:{{port}}\" \"https://d.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test", "d.test"}));
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.front().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options1;
methods_with_options1.push_back(MojomClearMethod());
methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello",
/*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options1.push_back(
MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options1)));
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_TEMPORARY_REDIRECT,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: append;key=wont;value=set"},
/*redirect_index=*/0);
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"},
/*redirect_index=*/1);
// There will now have been a total of 2 operations (1 previous, 1 current).
ASSERT_TRUE(observer_);
observer_->WaitForOperations(2);
EXPECT_EQ(observer_->header_results().size(), 2u);
EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back());
std::vector<MethodWithOptionsPtr> methods_with_options2;
methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 2u);
EXPECT_EQ(observer_->operations()[1],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options2)));
// Create an iframe that's same-origin to the original iframe URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node2, R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[0].message));
// Only one entry was set in b.test's shared storage.
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message));
// Create an iframe that's same-origin to the first redirect URL.
FrameTreeNode* iframe_node3 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.front());
// c.test does not have permission to use shared storage.
EXPECT_THAT(EvalJs(iframe_node3, R"(
sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"The \"shared-storage\" Permissions Policy "
"denied the method")));
// Create an iframe that's same-origin to the second redirect URL.
FrameTreeNode* iframe_node4 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
ExecuteScriptInWorklet(iframe_node4, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url, /*expected_total_host_count=*/2u);
// One entry was set in d.test's shared storage.
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageHeaderObserverBrowserTest,
Iframe_CrossOrigin_Redirect_InitialDenied_FinalAllowed_WriteFinal) {
SetUpResponsesAndNavigateMainPage(
/*main_hostname=*/"a.test",
/*subresource_or_subframe_hostname=*/"b.test",
/*shared_storage_permissions=*/
"(self \"https://c.test:{{port}}\")",
/*is_image=*/false,
/*redirect_hostnames=*/std::vector<std::string>({"c.test"}));
CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_);
WaitForSubresourceOrSubframeRequestAndSendResponse(
/*expect_writable_header=*/false,
/*http_status=*/net::HTTP_FOUND,
/*extra_headers=*/
{base::StrCat({"Location: ", redirect_urls_.back().spec()}),
"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
// No operations are invoked.
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
WaitForRedirectRequestAndSendResponse(
/*expect_writable_header=*/true,
/*http_status=*/net::HTTP_OK,
/*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Write: delete;key=a, set;value=will;key=set"});
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(), redirect_origins_.back());
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a"));
methods_with_options.push_back(MojomSetMethod(/*key=*/u"set",
/*value=*/u"will",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(redirect_origins_.back(),
std::move(methods_with_options)));
WebContentsConsoleObserver console_observer(shell()->web_contents());
// Create an iframe that's same-origin to the original iframe URL.
FrameTreeNode* iframe_node2 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_);
EXPECT_THAT(EvalJs(iframe_node2, R"(
sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
)"),
EvalJsResult::ErrorIs(testing::HasSubstr(
"The \"shared-storage\" Permissions Policy "
"denied the method")));
// Create an iframe that's same-origin to the redirect URL.
FrameTreeNode* iframe_node3 =
CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back());
GURL out_script_url;
ExecuteScriptInWorklet(iframe_node3, R"(
console.log(await sharedStorage.get('set'));
console.log(await sharedStorage.length());
)",
&out_script_url);
// one key was set in c.test's shared storage.
EXPECT_EQ(2u, console_observer.messages().size());
EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Iframe_ContentAttributeIncluded_Set) {
WebContentsConsoleObserver console_observer(shell()->web_contents());
StartServerAndLoadMainURLWithSameOriginSubresourceOrSubframe(
/*main_hostname=*/"a.test",
/*main_path=*/
"/shared_storage/page-with-shared-storage-writable-iframe.html");
EXPECT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Iframe Loaded",
base::UTF16ToUTF8(console_observer.messages()[0].message));
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message));
ASSERT_TRUE(observer_);
observer_->WaitForOperations(1);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(),
subresource_or_subframe_origin_);
std::vector<MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b",
/*ignore_if_present=*/false));
EXPECT_EQ(observer_->operations().size(), 1u);
EXPECT_EQ(observer_->operations()[0],
HeaderOperationSuccess(subresource_or_subframe_origin_,
std::move(methods_with_options)));
}
IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest,
Iframe_ContentAttributeNotIncluded_NotSet) {
WebContentsConsoleObserver console_observer(shell()->web_contents());
StartServerAndLoadMainURLWithSameOriginSubresourceOrSubframe(
/*main_hostname=*/"a.test",
/*main_path=*/
"/shared_storage/page-with-non-shared-storage-writable-iframe.html");
EXPECT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Iframe Loaded",
base::UTF16ToUTF8(console_observer.messages()[0].message));
GURL out_script_url;
ExecuteScriptInWorklet(shell(), R"(
console.log(await sharedStorage.get('a'));
console.log(await sharedStorage.length());
)",
&out_script_url);
EXPECT_EQ(3u, console_observer.messages().size());
EXPECT_EQ("undefined",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[2].message));
// No operations are invoked.
ASSERT_TRUE(observer_);
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
}
class SharedStorageCreateWorkletCustomDataOriginBrowserTest
: public SharedStorageBrowserTest {
public:
~SharedStorageCreateWorkletCustomDataOriginBrowserTest() override = default;
std::vector<base::Value> BuildWellKnownTrustedOriginsLists() override {
std::vector<base::Value> trusted_origins_lists;
// We expect failure for script with origin "https://b.test:{{port}}" and
// context origin "https://a.test:{{port}}" when one of the following values
// is served.
trusted_origins_lists.push_back(static_cast<base::Value>(
base::Value::Dict()
.Set("scriptOrigin", "https://b.test:{{port}}")
.Set("contextOrigin", "https://a.test:{{port}}")));
trusted_origins_lists.push_back(
static_cast<base::Value>(base::Value::List()));
trusted_origins_lists.push_back(static_cast<base::Value>(
base::Value::List().Append(base::Value::List()
.Append("https://b.test:{{port}}")
.Append("https://a.test:{{port}}"))));
trusted_origins_lists.push_back(static_cast<base::Value>(
base::Value::List().Append(base::Value::Dict().Set(
"contextOrigin", "https://a.test:{{port}}"))));
trusted_origins_lists.push_back(
static_cast<base::Value>(base::Value::List().Append(
base::Value::Dict()
.Set("scriptOrigin", base::Value::List())
.Set("contextOrigin", "https://a.test:{{port}}"))));
trusted_origins_lists.push_back(static_cast<base::Value>(
base::Value::List().Append(base::Value::Dict().Set(
"scriptOrigin", "https://b.test:{{port}}"))));
trusted_origins_lists.push_back(
static_cast<base::Value>(base::Value::List().Append(
base::Value::Dict()
.Set("scriptOrigin", "https://b.test:{{port}}")
.Set("contextOrigin", base::Value::List()))));
trusted_origins_lists.push_back(static_cast<base::Value>(
base::Value::List()
.Append(base::Value::Dict()
.Set("scriptOrigin", "https://a.test:{{port}}")
.Set("contextOrigin", "*"))
.Append(base::Value::Dict()
.Set("scriptOrigin", "*")
.Set("contextOrigin", "https://b.test:{{port}}"))));
// We expect success for script with origin "https://b.test:{{port}}" and
// context origin "https://a.test:{{port}}" when one of the following values
// is served.
trusted_origins_lists.push_back(
static_cast<base::Value>(base::Value::List().Append(
base::Value::Dict()
.Set("scriptOrigin", "https://b.test:{{port}}")
.Set("contextOrigin", "https://a.test:{{port}}"))));
trusted_origins_lists.push_back(
static_cast<base::Value>(base::Value::List().Append(
base::Value::Dict()
.Set("scriptOrigin", "https://b.test:{{port}}")
.Set("contextOrigin", "*"))));
trusted_origins_lists.push_back(
static_cast<base::Value>(base::Value::List().Append(
base::Value::Dict()
.Set("scriptOrigin", "*")
.Set("contextOrigin", "https://a.test:{{port}}"))));
trusted_origins_lists.push_back(
static_cast<base::Value>(base::Value::List().Append(
base::Value::Dict()
.Set("scriptOrigin", base::Value::List()
.Append("https://x.test:{{port}}")
.Append("https://b.test:{{port}}"))
.Set("contextOrigin",
base::Value::List()
.Append("https://a.test:{{port}}")
.Append("https://y.test:{{port}}")))));
trusted_origins_lists.push_back(
static_cast<base::Value>(base::Value::List().Append(
base::Value::Dict()
.Set("scriptOrigin", base::Value::List()
.Append("https://b.test:{{port}}")
.Append("https://y.test:{{port}}")
.Append("https://x.test:{{port}}"))
.Set("contextOrigin",
base::Value::List()
.Append("https://y.test:{{port}}")
.Append("*")
.Append("https://z.test:{{port}}")))));
trusted_origins_lists.push_back(
static_cast<base::Value>(base::Value::List().Append(
base::Value::Dict()
.Set("scriptOrigin", base::Value::List()
.Append("https://x.test:{{port}}")
.Append("https://y.test:{{port}}")
.Append("*"))
.Set("contextOrigin",
base::Value::List()
.Append("https://y.test:{{port}}")
.Append("https://a.test:{{port}}")))));
return trusted_origins_lists;
}
};
INSTANTIATE_TEST_SUITE_P(All,
SharedStorageCreateWorkletCustomDataOriginBrowserTest,
testing::Bool(),
describe_param);
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Failure_ServerError) {
set_force_server_error(true);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())),
EvalJsResult::ErrorIs(testing::HasSubstr(
"no response, an invalid response, or an unexpected mime type")));
}
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Failure_NotAList) {
set_trusted_origins_list_index(0);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())),
EvalJsResult::ErrorIs(testing::HasSubstr(
"because there was no parse result or the result was not a list")));
}
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Failure_EmptyList) {
set_trusted_origins_list_index(1);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())),
EvalJsResult::ErrorIs(testing::HasSubstr("is an empty list")));
}
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Failure_NonDictItem) {
set_trusted_origins_list_index(2);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())),
EvalJsResult::ErrorIs(
testing::HasSubstr("non-dictionary item was encountered")));
}
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Failure_ScriptOriginKeyNotFound) {
set_trusted_origins_list_index(3);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())),
EvalJsResult::ErrorIs(testing::HasSubstr(
"dictionary item's `scriptOrigin` key was not found")));
}
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Failure_ScriptOriginValueEmptyList) {
set_trusted_origins_list_index(4);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())),
EvalJsResult::ErrorIs(testing::HasSubstr(
"`scriptOrigin` key was not found, or its value was an empty list")));
}
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Failure_ContextOriginKeyNotFound) {
set_trusted_origins_list_index(5);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())),
EvalJsResult::ErrorIs(testing::HasSubstr(
"dictionary item's `contextOrigin` key was not found")));
}
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Failure_ContextOriginValueEmptyList) {
set_trusted_origins_list_index(6);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())),
EvalJsResult::ErrorIs(testing::HasSubstr(
"`contextOrigin` key was not found, or its value was "
"an empty list")));
}
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Failure_NotAllowed) {
set_trusted_origins_list_index(7);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_THAT(
EvalJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())),
EvalJsResult::ErrorIs(testing::HasSubstr("has not been allowed")));
}
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Success_FullySpecified) {
set_trusted_origins_list_index(8);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())));
std::string custom_data_origin_str = custom_data_origin.Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
custom_data_origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
module_script_url, custom_data_origin_str,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Success_SpecifiedScriptOrigin_WildcardContextOrigin) {
set_trusted_origins_list_index(9);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())));
std::string custom_data_origin_str = custom_data_origin.Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
custom_data_origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
module_script_url, custom_data_origin_str,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Success_WildcardScriptOrigin_SpecifiedContextOrigin) {
set_trusted_origins_list_index(10);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())));
std::string custom_data_origin_str = custom_data_origin.Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
custom_data_origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
module_script_url, custom_data_origin_str,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Success_Lists_FullySpecified) {
set_trusted_origins_list_index(11);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())));
std::string custom_data_origin_str = custom_data_origin.Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
custom_data_origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
module_script_url, custom_data_origin_str,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Success_Lists_SpecifiedScriptOrigin_WildcardContextOrigin) {
set_trusted_origins_list_index(12);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())));
std::string custom_data_origin_str = custom_data_origin.Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
custom_data_origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
module_script_url, custom_data_origin_str,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageCreateWorkletCustomDataOriginBrowserTest,
CrossOriginScript_Success_Lists_WildcardScriptOrigin_SpecifiedContextOrigin) {
set_trusted_origins_list_index(13);
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
GURL module_script_url = https_server()->GetURL(
"b.test", "/shared_storage/module_with_cors_header.js");
url::Origin custom_data_origin =
url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
EXPECT_TRUE(ExecJs(
shell(),
JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})",
module_script_url.spec(), custom_data_origin.Serialize())));
std::string custom_data_origin_str = custom_data_origin.Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
custom_data_origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
module_script_url, custom_data_origin_str,
/*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}});
}
IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
TwoWorkletsInSameFrame_OrdinalIDsAreCorrect) {
GURL url = https_server()->GetURL("a.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContentsConsoleObserver console_observer(shell()->web_contents());
GURL out_script_url1;
ExecuteScriptInWorkletUsingCreateWorklet(shell(), R"(
sharedStorage.set('key0', 'value0');
sharedStorage.append('key0', 'value1');
console.log(await sharedStorage.get('key0'));
console.log(await sharedStorage.length());
)",
&out_script_url1);
GURL out_script_url2;
ExecuteScriptInWorkletUsingCreateWorklet(shell(), R"(
sharedStorage.set('key1', 'value2');
sharedStorage.append('key1', 'value3');
console.log(await sharedStorage.get('key1'));
console.log(await sharedStorage.length());
)",
&out_script_url2,
/*expected_total_host_count=*/2u);
EXPECT_EQ(4u, console_observer.messages().size());
EXPECT_EQ("value0value1",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("value2value3",
base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("2", base::UTF16ToUTF8(console_observer.messages()[3].message));
WaitForHistograms({kTimingRunExecutedInWorkletHistogram});
histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 2);
std::map<int, base::UnguessableToken>& cached_worklet_devtools_tokens =
GetCachedWorkletHostDevToolsTokens();
EXPECT_EQ(cached_worklet_devtools_tokens.size(), 2u);
std::string origin_str = url::Origin::Create(url).Serialize();
ExpectAccessObserved(
{{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
out_script_url1, "context-origin",
/*worklet_ordinal=*/0, cached_worklet_devtools_tokens[0])},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), cached_worklet_devtools_tokens[0])},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"key0", "value0", false, cached_worklet_devtools_tokens[0])},
{AccessScope::kSharedStorageWorklet, AccessMethod::kAppend,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateForAppend(
"key0", "value1", cached_worklet_devtools_tokens[0])},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key0", cached_worklet_devtools_tokens[0])},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
cached_worklet_devtools_tokens[0])},
{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForCreateWorklet(
out_script_url2, "context-origin",
/*worklet_ordinal=*/1, cached_worklet_devtools_tokens[1])},
{AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str,
SharedStorageEventParams::CreateForRunForTesting(
"test-operation", /*operation_id=*/0, /*keep_alive=*/true,
SharedStorageEventParams::PrivateAggregationConfigWrapper(),
blink::CloneableMessage(), cached_worklet_devtools_tokens[1])},
{AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForSet(
"key1", "value2", false, cached_worklet_devtools_tokens[1])},
{AccessScope::kSharedStorageWorklet, AccessMethod::kAppend,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateForAppend(
"key1", "value3", cached_worklet_devtools_tokens[1])},
{AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(),
origin_str,
SharedStorageEventParams::CreateForGet(
"key1", cached_worklet_devtools_tokens[1])},
{AccessScope::kSharedStorageWorklet, AccessMethod::kLength,
MainFrameId(), origin_str,
SharedStorageEventParams::CreateWithWorkletToken(
cached_worklet_devtools_tokens[1])}});
ExpectOperationFinishedInfosObserved(
{{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0,
cached_worklet_devtools_tokens[0], MainFrameId(), origin_str},
{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0,
cached_worklet_devtools_tokens[1], MainFrameId(), origin_str}});
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
TwoWindows_DevToolsAccessNotificationsFilteredByMainFrame) {
GURL url1 = https_server()->GetURL("a.test", kPageWithBlankIframePath);
GURL url2 = https_server()->GetURL("b.test", kPageWithBlankIframePath);
GURL iframe_url1 = https_server()->GetURL("c.test", kSimplePagePath);
GURL iframe_url2 = https_server()->GetURL("d.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url1));
RenderFrameHost* main_rfh1 = PrimaryFrameTreeNodeRoot()->current_frame_host();
TestSharedStorageDevToolsClient main_frame_devtools_client1(main_rfh1);
std::string expected_method = "Storage.sharedStorageAccessed";
main_frame_devtools_client1.set_expected_notification_method(expected_method);
EXPECT_TRUE(
NavigateIframeToURL(shell()->web_contents(), "test_iframe", iframe_url1));
RenderFrameHost* iframe1 =
PrimaryFrameTreeNodeRoot()->child_at(0)->current_frame_host();
TestSharedStorageDevToolsClient iframe_devtools_client1(iframe1);
iframe_devtools_client1.set_expected_notification_method(expected_method);
EXPECT_TRUE(ExecJs(iframe1, "sharedStorage.delete('key0')"));
Shell* shell2 = Shell::CreateNewWindow(
shell()->web_contents()->GetBrowserContext(), url2, nullptr, gfx::Size());
ASSERT_TRUE(WaitForLoadStop(shell2->web_contents()));
RenderFrameHost* main_rfh2 =
PrimaryFrameTreeNodeRootFromShell(shell2)->current_frame_host();
TestSharedStorageDevToolsClient main_frame_devtools_client2(main_rfh2);
main_frame_devtools_client2.set_expected_notification_method(expected_method);
EXPECT_TRUE(
NavigateIframeToURL(shell2->web_contents(), "test_iframe", iframe_url2));
RenderFrameHost* iframe2 = PrimaryFrameTreeNodeRootFromShell(shell2)
->child_at(0)
->current_frame_host();
TestSharedStorageDevToolsClient iframe_devtools_client2(iframe2);
iframe_devtools_client2.set_expected_notification_method(expected_method);
EXPECT_TRUE(ExecJs(iframe2, "sharedStorage.set('key2', 'value2')"));
GURL out_script_url1;
ExecuteScriptInWorklet(main_rfh1, R"(
sharedStorage.set('key0', 'value0');
sharedStorage.append('key0', 'value1');
)",
&out_script_url1);
GURL out_script_url2;
ExecuteScriptInWorkletUsingCreateWorklet(main_rfh2, R"(
sharedStorage.delete('key1');
sharedStorage.clear();
)",
&out_script_url2,
/*expected_total_host_count=*/2u);
std::vector<std::string> selected_key_paths(
{"method", "ownerSite", "params.operationId", "params.operationName",
"params.ignoreIfPresent", "params.key", "params.value",
"params.workletOrdinal", "scope"});
std::vector<std::map<std::string, std::string>>
selected_params_observed_main1 =
main_frame_devtools_client1
.GetSelectedParamsAsStringsForNotificationsWithExpectedMethod(
selected_key_paths);
// `main_frame_devtools_client1` receives the expected shared storage
// notifications for `shell()`, including any notifications from subframes of
// this main frame, but no further "Storage.sharedStorageAccessed"
// notifications. In particular, it does not receive those from `shell2`.
ASSERT_EQ(selected_params_observed_main1.size(), 5u)
<< SerializeVectorOfMapOfStrings(selected_params_observed_main1);
EXPECT_THAT(
selected_params_observed_main1[0],
ElementsAre(Pair("method", "delete"), Pair("ownerSite", "https://c.test"),
Pair("params.key", "key0"), Pair("scope", "window")));
EXPECT_THAT(
selected_params_observed_main1[1],
ElementsAre(Pair("method", "addModule"),
Pair("ownerSite", "https://a.test"),
Pair("params.workletOrdinal", "0"), Pair("scope", "window")));
EXPECT_THAT(
selected_params_observed_main1[2],
ElementsAre(Pair("method", "run"), Pair("ownerSite", "https://a.test"),
Pair("params.operationId", "0"),
Pair("params.operationName", "test-operation"),
Pair("scope", "window")));
EXPECT_THAT(
selected_params_observed_main1[3],
ElementsAre(Pair("method", "set"), Pair("ownerSite", "https://a.test"),
Pair("params.ignoreIfPresent", "false"),
Pair("params.key", "key0"), Pair("params.value", "value0"),
Pair("scope", "sharedStorageWorklet")));
EXPECT_THAT(
selected_params_observed_main1[4],
ElementsAre(Pair("method", "append"), Pair("ownerSite", "https://a.test"),
Pair("params.key", "key0"), Pair("params.value", "value1"),
Pair("scope", "sharedStorageWorklet")));
std::vector<std::map<std::string, std::string>>
selected_params_observed_main2 =
main_frame_devtools_client2
.GetSelectedParamsAsStringsForNotificationsWithExpectedMethod(
selected_key_paths);
// `main_frame_devtools_client2` receives the expected shared storage
// notifications for `shell2`, including any notifications from subframes of
// this main frame, but no further "Storage.sharedStorageAccessed"
// notifications. In particular, it does not receive those from `shell()`.
ASSERT_EQ(selected_params_observed_main2.size(), 5u)
<< SerializeVectorOfMapOfStrings(selected_params_observed_main2);
EXPECT_THAT(
selected_params_observed_main2[0],
ElementsAre(Pair("method", "set"), Pair("ownerSite", "https://d.test"),
Pair("params.ignoreIfPresent", "false"),
Pair("params.key", "key2"), Pair("params.value", "value2"),
Pair("scope", "window")));
EXPECT_THAT(
selected_params_observed_main2[1],
ElementsAre(Pair("method", "createWorklet"),
Pair("ownerSite", "https://b.test"),
Pair("params.workletOrdinal", "1"), Pair("scope", "window")));
EXPECT_THAT(
selected_params_observed_main2[2],
ElementsAre(Pair("method", "run"), Pair("ownerSite", "https://b.test"),
Pair("params.operationId", "0"),
Pair("params.operationName", "test-operation"),
Pair("scope", "window")));
EXPECT_THAT(
selected_params_observed_main2[3],
ElementsAre(Pair("method", "delete"), Pair("ownerSite", "https://b.test"),
Pair("params.key", "key1"),
Pair("scope", "sharedStorageWorklet")));
EXPECT_THAT(
selected_params_observed_main2[4],
ElementsAre(Pair("method", "clear"), Pair("ownerSite", "https://b.test"),
Pair("scope", "sharedStorageWorklet")));
ASSERT_EQ(IsLocalRoot(iframe1), IsLocalRoot(iframe2));
if (!IsLocalRoot(iframe1)) {
// In this case, `iframe_devtools_client1` and `iframe_devtools_client2` are
// actually attached to their respective main frame hosts. They will have
// received the same notifications as above.
return;
}
std::vector<std::map<std::string, std::string>>
selected_params_observed_iframe1 =
iframe_devtools_client1
.GetSelectedParamsAsStringsForNotificationsWithExpectedMethod(
selected_key_paths);
std::vector<std::map<std::string, std::string>>
selected_params_observed_iframe2 =
iframe_devtools_client2
.GetSelectedParamsAsStringsForNotificationsWithExpectedMethod(
std::move(selected_key_paths));
// Neither `iframe_devtools_client1` nor `iframe_devtools_client2` receives
// any shared storage notifications. The notifications for the subframes'
// events were received by their respective main frame clients above.
EXPECT_TRUE(selected_params_observed_iframe1.empty())
<< SerializeVectorOfMapOfStrings(selected_params_observed_iframe1);
EXPECT_TRUE(selected_params_observed_iframe2.empty())
<< SerializeVectorOfMapOfStrings(selected_params_observed_iframe2);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageBrowserTest,
TwoWindows_DevToolsOperationFinishedNotificationsFilteredByMainFrame) {
GURL url1 = https_server()->GetURL("a.test", kPageWithBlankIframePath);
GURL url2 = https_server()->GetURL("b.test", kPageWithBlankIframePath);
GURL iframe_url1 = https_server()->GetURL("c.test", kSimplePagePath);
GURL iframe_url2 = https_server()->GetURL("d.test", kSimplePagePath);
EXPECT_TRUE(NavigateToURL(shell(), url1));
RenderFrameHost* main_rfh1 = PrimaryFrameTreeNodeRoot()->current_frame_host();
TestSharedStorageDevToolsClient main_frame_devtools_client1(main_rfh1);
std::string expected_method =
"Storage.sharedStorageWorkletOperationExecutionFinished";
main_frame_devtools_client1.set_expected_notification_method(expected_method);
EXPECT_TRUE(
NavigateIframeToURL(shell()->web_contents(), "test_iframe", iframe_url1));
RenderFrameHost* iframe1 =
PrimaryFrameTreeNodeRoot()->child_at(0)->current_frame_host();
TestSharedStorageDevToolsClient iframe_devtools_client1(iframe1);
iframe_devtools_client1.set_expected_notification_method(expected_method);
EXPECT_TRUE(ExecJs(iframe1, R"(
sharedStorage.createWorklet('shared_storage/simple_module.js')
.then((worklet) => worklet.run("test-operation", {keepAlive: true}));
)"));
Shell* shell2 = Shell::CreateNewWindow(
shell()->web_contents()->GetBrowserContext(), url2, nullptr, gfx::Size());
ASSERT_TRUE(WaitForLoadStop(shell2->web_contents()));
RenderFrameHost* main_rfh2 =
PrimaryFrameTreeNodeRootFromShell(shell2)->current_frame_host();
TestSharedStorageDevToolsClient main_frame_devtools_client2(main_rfh2);
main_frame_devtools_client2.set_expected_notification_method(expected_method);
EXPECT_TRUE(
NavigateIframeToURL(shell2->web_contents(), "test_iframe", iframe_url2));
RenderFrameHost* iframe2 = PrimaryFrameTreeNodeRootFromShell(shell2)
->child_at(0)
->current_frame_host();
TestSharedStorageDevToolsClient iframe_devtools_client2(iframe2);
iframe_devtools_client2.set_expected_notification_method(expected_method);
EXPECT_TRUE(ExecJs(iframe2, R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js')
.then(() => sharedStorage.selectURL("test-url-selection-operation",
[{url: 'fenced_frames/title0.html'},
{url: 'fenced_frames/title1.html'}],
{keepAlive: true}));
)"));
EXPECT_TRUE(ExecJs(main_rfh1, R"(
sharedStorage.createWorklet('shared_storage/simple_module.js')
.then((worklet) => worklet.selectURL("test-url-selection-operation",
[{url: 'fenced_frames/title0.html'},
{url: 'fenced_frames/title1.html'}],
{keepAlive: true}));
)"));
GURL out_script_url;
ExecuteScriptInWorkletUsingCreateWorklet(main_rfh2, R"(
sharedStorage.clear();
)",
&out_script_url,
/*expected_total_host_count=*/4u);
// Ensure that execution of all 4 operations has finished.
WaitForHistogramsWithCounts(
{std::make_tuple(kTimingRunExecutedInWorkletHistogram, 2),
std::make_tuple(kTimingSelectUrlExecutedInWorkletHistogram, 2)});
std::vector<std::string> selected_key_paths(
{"method", "operationId", "ownerOrigin"});
std::vector<std::map<std::string, std::string>>
selected_params_observed_main1 =
main_frame_devtools_client1
.GetSelectedParamsAsStringsForNotificationsWithExpectedMethod(
selected_key_paths);
std::string main_origin_str1 = url::Origin::Create(url1).Serialize();
std::string main_origin_str2 = url::Origin::Create(url2).Serialize();
std::string iframe_origin_str1 = url::Origin::Create(iframe_url1).Serialize();
std::string iframe_origin_str2 = url::Origin::Create(iframe_url2).Serialize();
// `main_frame_devtools_client1` receives the expected shared storage
// notifications for `shell()`, including any notifications from subframes of
// this main frame, but no further "Storage.sharedStorageAccessed"
// notifications. In particular, it does not receive those from `shell2`.
ASSERT_EQ(selected_params_observed_main1.size(), 2u)
<< SerializeVectorOfMapOfStrings(selected_params_observed_main1);
if (selected_params_observed_main1[0]["ownerOrigin"] != iframe_origin_str1) {
// The order in which the operations finish executing is nondeterministic.
// If necessary, swap to put the iframe's results first.
std::swap(selected_params_observed_main1[0],
selected_params_observed_main1[1]);
}
EXPECT_THAT(selected_params_observed_main1[0],
ElementsAre(Pair("method", "run"), Pair("operationId", "0"),
Pair("ownerOrigin", iframe_origin_str1)));
EXPECT_THAT(selected_params_observed_main1[1],
ElementsAre(Pair("method", "selectURL"), Pair("operationId", "0"),
Pair("ownerOrigin", main_origin_str1)));
std::vector<std::map<std::string, std::string>>
selected_params_observed_main2 =
main_frame_devtools_client2
.GetSelectedParamsAsStringsForNotificationsWithExpectedMethod(
selected_key_paths);
// `main_frame_devtools_client2` receives the expected shared storage
// notifications for `shell2`, including any notifications from subframes of
// this main frame, but no further "Storage.sharedStorageAccessed"
// notifications. In particular, it does not receive those from `shell()`.
ASSERT_EQ(selected_params_observed_main2.size(), 2u)
<< SerializeVectorOfMapOfStrings(selected_params_observed_main2);
if (selected_params_observed_main2[0]["ownerOrigin"] != iframe_origin_str2) {
// The order in which the operations finish executing is nondeterministic.
// If necessary, swap to put the iframe's results first.
std::swap(selected_params_observed_main2[0],
selected_params_observed_main2[1]);
}
EXPECT_THAT(selected_params_observed_main2[0],
ElementsAre(Pair("method", "selectURL"), Pair("operationId", "0"),
Pair("ownerOrigin", iframe_origin_str2)));
EXPECT_THAT(selected_params_observed_main2[1],
ElementsAre(Pair("method", "run"), Pair("operationId", "0"),
Pair("ownerOrigin", main_origin_str2)));
ASSERT_EQ(IsLocalRoot(iframe1), IsLocalRoot(iframe2));
if (!IsLocalRoot(iframe1)) {
// In this case, `iframe_devtools_client1` and `iframe_devtools_client2` are
// actually attached to their respective main frame hosts. They will have
// received the same notifications as above.
return;
}
std::vector<std::map<std::string, std::string>>
selected_params_observed_iframe1 =
iframe_devtools_client1
.GetSelectedParamsAsStringsForNotificationsWithExpectedMethod(
selected_key_paths);
std::vector<std::map<std::string, std::string>>
selected_params_observed_iframe2 =
iframe_devtools_client2
.GetSelectedParamsAsStringsForNotificationsWithExpectedMethod(
std::move(selected_key_paths));
// Neither `iframe_devtools_client1` nor `iframe_devtools_client2` receives
// any shared storage notifications. The notifications for the subframes'
// events were received by their respective main frame clients above.
EXPECT_TRUE(selected_params_observed_iframe1.empty())
<< SerializeVectorOfMapOfStrings(selected_params_observed_iframe1);
EXPECT_TRUE(selected_params_observed_iframe2.empty())
<< SerializeVectorOfMapOfStrings(selected_params_observed_iframe2);
}
} // namespace content