blob: 2f177e609f2b168fa020ff0cd51ebd7564f8ef1f [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cmath>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <vector>
#include "base/containers/contains.h"
#include "base/json/json_reader.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_base.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/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/test/with_feature_override.h"
#include "base/values.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/platform_browser_test.h"
#include "components/content_settings/core/browser/content_settings_pref_provider.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_partition_key.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/prefs/pref_service.h"
#include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h"
#include "components/privacy_sandbox/privacy_sandbox_attestations/scoped_privacy_sandbox_attestations.h"
#include "components/privacy_sandbox/privacy_sandbox_features.h"
#include "components/privacy_sandbox/privacy_sandbox_prefs.h"
#include "components/privacy_sandbox/privacy_sandbox_test_util.h"
#include "components/services/storage/shared_storage/shared_storage_manager.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/shared_storage_test_utils.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 "extensions/buildflags/buildflags.h"
#include "net/base/schemeful_site.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "services/network/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/shared_storage/shared_storage_utils.h"
#include "url/url_constants.h"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#else
#include "chrome/browser/ui/browser.h"
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/extensions/extension_browsertest.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/test/test_extension_dir.h"
#endif
namespace storage {
namespace {
using OperationResult = SharedStorageManager::OperationResult;
constexpr char kMainHost[] = "a.test";
constexpr char kSimplePagePath[] = "/simple.html";
constexpr char kFencedFramePagePath[] = "/fenced_frames/title1.html";
constexpr char kTitle1Path[] = "/title1.html";
constexpr char kCrossOriginHost[] = "b.test";
constexpr char kThirdOriginHost[] = "c.test";
constexpr char kFourthOriginHost[] = "d.test";
constexpr char kRemainingBudgetPrefix[] = "remaining budget: ";
constexpr char kErrorTypeHistogram[] =
"Storage.SharedStorage.Worklet.Error.Type";
constexpr char kEntriesQueuedCountHistogram[] =
"Storage.SharedStorage.AsyncIterator.EntriesQueuedCount";
constexpr char kReceivedEntriesBenchmarksHistogram[] =
"Storage.SharedStorage.AsyncIterator.ReceivedEntriesBenchmarks";
constexpr char kIteratedEntriesBenchmarksHistogram[] =
"Storage.SharedStorage.AsyncIterator.IteratedEntriesBenchmarks";
constexpr char kTimingDocumentAddModuleHistogram[] =
"Storage.SharedStorage.Document.Timing.AddModule";
constexpr char kTimingDocumentRunHistogram[] =
"Storage.SharedStorage.Document.Timing.Run";
constexpr char kTimingDocumentSelectUrlHistogram[] =
"Storage.SharedStorage.Document.Timing.SelectURL";
constexpr char kTimingDocumentAppendHistogram[] =
"Storage.SharedStorage.Document.Timing.Append";
constexpr char kTimingDocumentSetHistogram[] =
"Storage.SharedStorage.Document.Timing.Set";
constexpr char kTimingDocumentGetHistogram[] =
"Storage.SharedStorage.Document.Timing.Get";
constexpr char kTimingDocumentDeleteHistogram[] =
"Storage.SharedStorage.Document.Timing.Delete";
constexpr char kTimingDocumentClearHistogram[] =
"Storage.SharedStorage.Document.Timing.Clear";
constexpr char kTimingWorkletAppendHistogram[] =
"Storage.SharedStorage.Worklet.Timing.Append";
constexpr char kTimingWorkletSetHistogram[] =
"Storage.SharedStorage.Worklet.Timing.Set";
constexpr char kTimingWorkletGetHistogram[] =
"Storage.SharedStorage.Worklet.Timing.Get";
constexpr char kTimingWorkletLengthHistogram[] =
"Storage.SharedStorage.Worklet.Timing.Length";
constexpr char kTimingWorkletDeleteHistogram[] =
"Storage.SharedStorage.Worklet.Timing.Delete";
constexpr char kTimingWorkletClearHistogram[] =
"Storage.SharedStorage.Worklet.Timing.Clear";
constexpr char kTimingWorkletKeysHistogram[] =
"Storage.SharedStorage.Worklet.Timing.Keys.Next";
constexpr char kTimingWorkletEntriesHistogram[] =
"Storage.SharedStorage.Worklet.Timing.Entries.Next";
constexpr char kWorkletNumPerPageHistogram[] =
"Storage.SharedStorage.Worklet.NumPerPage";
constexpr char kSelectUrlCallsPerPageHistogram[] =
"Storage.SharedStorage.Worklet.SelectURL.CallsPerPage";
constexpr char kTimingRemainingBudgetHistogram[] =
"Storage.SharedStorage.Worklet.Timing.RemainingBudget";
constexpr char kPrivateAggregationHostPipeResultHistogram[] =
"PrivacySandbox.PrivateAggregation.Host.PipeResult";
constexpr char
kPrivateAggregationHostTimeToGenerateReportRequestWithContextIdHistogram[] =
"PrivacySandbox.PrivateAggregation.Host."
"TimeToGenerateReportRequestWithContextId";
constexpr char kFencedStorageReadAttestationErrorPrefix[] =
"Attestation check for fenced storage read on";
constexpr char kFencedStorageReadDisabledBy3pcSettingError[] =
"Fenced storage read is disabled because all third-party cookies are "
"blocked.";
const double kBudgetAllowed = 5.0;
// In order to cut back on the total number of tests run, we deliberately only
// test three possibilities. In particular, the main host is unenrolled when the
// attestations are unenforced, leaving out the main host enrolled/attestations
// unenforced case. Since this enum is used as a parameter and combined with
// other parameters, using three instead of four cases is especially important
// on Android due to hardware limitations that constrain the total number of
// tests that can be run.
enum class EnforcementAndEnrollmentStatus {
kAttestationsUnenforced = 0,
kAttestationsEnforcedMainHostUnenrolled = 1,
kAttestationsEnforcedMainHostEnrolled = 2,
};
// With `WebContentsConsoleObserver`, we can only wait for the last message in a
// group.
base::RepeatingCallback<
bool(const content::WebContentsConsoleObserver::Message& message)>
MakeFilter(std::vector<std::string> possible_last_messages) {
return base::BindRepeating(
[](std::vector<std::string> possible_last_messages,
const content::WebContentsConsoleObserver::Message& message) {
for (const std::string& possible_message : possible_last_messages) {
if (base::StartsWith(base::UTF16ToUTF8(message.message),
possible_message)) {
return true;
}
}
return false;
},
std::move(possible_last_messages));
}
std::string GetFencedStorageReadDisabledMessage() {
return base::StrCat({"a JavaScript error: \"OperationError: ",
content::GetFencedStorageReadDisabledMessage()});
}
std::string GetFencedStorageReadWithoutRevokeNetworkMessage() {
return base::StrCat(
{"a JavaScript error: \"OperationError: ",
content::GetFencedStorageReadWithoutRevokeNetworkMessage()});
}
std::string GetSharedStorageDisabledErrorMessage() {
return base::StrCat({"a JavaScript error: \"OperationError: ",
content::GetSharedStorageDisabledMessage()});
}
std::string GetSharedStorageSelectURLDisabledErrorMessage() {
return base::StrCat({"a JavaScript error: \"OperationError: ",
content::GetSharedStorageSelectURLDisabledMessage()});
}
std::string GetSharedStorageAddModuleDisabledErrorMessage() {
return base::StrCat({"a JavaScript error: \"OperationError: ",
content::GetSharedStorageAddModuleDisabledMessage()});
}
void DelayBy(base::TimeDelta delta) {
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), delta);
run_loop.Run();
}
// TODO(cammie): Find a way to ensure that histograms are available at the
// necessary time without having to resort to sleeping/polling.
void WaitForHistograms(std::vector<std::string> histogram_names) {
while (true) {
content::FetchHistogramsFromChildProcesses();
metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
std::vector<std::string> still_waiting;
for (const auto& name : histogram_names) {
if (!base::StatisticsRecorder::FindHistogram(name))
still_waiting.push_back(name);
}
histogram_names = std::move(still_waiting);
if (histogram_names.empty())
break;
DelayBy(base::Seconds(1));
}
}
int GetSampleCountForHistogram(const std::string& histogram_name) {
auto* histogram = base::StatisticsRecorder::FindHistogram(histogram_name);
if (!histogram) {
return 0;
}
std::string json_output;
histogram->WriteJSON(
&json_output,
base::JSONVerbosityLevel::JSON_VERBOSITY_LEVEL_OMIT_BUCKETS);
std::optional<base::Value::Dict> json_dict =
base::JSONReader::ReadDict(json_output);
if (!json_dict) {
LOG(ERROR) << "Error parsing JSON of histogram data";
return 0;
}
std::optional<int> count = json_dict->FindInt("count");
if (!count) {
LOG(ERROR) << "Error: count missing from histogram data";
return 0;
}
return *count;
}
void WaitForHistogramsWithSampleCounts(
std::vector<std::tuple<std::string, int>> histogram_names_and_counts) {
while (true) {
content::FetchHistogramsFromChildProcesses();
metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
std::vector<std::tuple<std::string, int>> still_waiting;
for (const auto& [name, count] : histogram_names_and_counts) {
if (GetSampleCountForHistogram(name) < count) {
still_waiting.emplace_back(name, count);
}
}
histogram_names_and_counts = std::move(still_waiting);
if (histogram_names_and_counts.empty()) {
break;
}
DelayBy(base::Seconds(1));
}
}
// Return the active RenderFrameHost loaded in the last iframe in `parent_rfh`.
content::RenderFrameHost* LastChild(content::RenderFrameHost* parent_rfh) {
int child_end = 0;
while (ChildFrameAt(parent_rfh, child_end))
child_end++;
if (child_end == 0)
return nullptr;
return ChildFrameAt(parent_rfh, child_end - 1);
}
// Create an <iframe> inside `parent_rfh`, and navigate it toward `url`.
// This returns the new RenderFrameHost associated with new document created in
// the iframe.
content::RenderFrameHost* CreateIframe(content::RenderFrameHost* parent_rfh,
const GURL& url) {
EXPECT_EQ("iframe loaded",
content::EvalJs(parent_rfh, content::JsReplace(R"(
new Promise((resolve) => {
const iframe = document.createElement("iframe");
iframe.src = $1;
iframe.onload = _ => { resolve("iframe loaded"); };
document.body.appendChild(iframe);
}))",
url)));
return LastChild(parent_rfh);
}
privacy_sandbox::PrivacySandboxAttestationsMap
MakeSharedStoragePrivacySandboxAttestationsMap(
const std::vector<GURL>& enrollee_urls,
bool enroll_for_private_aggregation = false) {
privacy_sandbox::PrivacySandboxAttestationsMap attestations_map;
auto attestations_set =
privacy_sandbox::PrivacySandboxAttestationsGatedAPISet(
{privacy_sandbox::PrivacySandboxAttestationsGatedAPI::
kSharedStorage});
if (enroll_for_private_aggregation) {
attestations_set.Put(privacy_sandbox::PrivacySandboxAttestationsGatedAPI::
kPrivateAggregation);
}
for (const GURL& url : enrollee_urls) {
attestations_map[net::SchemefulSite(url)] = attestations_set;
}
return attestations_map;
}
class MockChromeContentBrowserClient : public ChromeContentBrowserClient {
public:
bool IsSharedStorageAllowed(
content::BrowserContext* browser_context,
content::RenderFrameHost* rfh,
const url::Origin& top_frame_origin,
const url::Origin& accessing_origin,
std::string* out_debug_message,
bool* out_block_is_site_setting_specific) override {
if (bypass_shared_storage_allowed_count_ > 0) {
bypass_shared_storage_allowed_count_--;
return true;
}
return ChromeContentBrowserClient::IsSharedStorageAllowed(
browser_context, rfh, top_frame_origin, accessing_origin,
out_debug_message, out_block_is_site_setting_specific);
}
bool IsSharedStorageSelectURLAllowed(
content::BrowserContext* browser_context,
const url::Origin& top_frame_origin,
const url::Origin& accessing_origin,
std::string* out_debug_message,
bool* out_block_is_site_setting_specific) override {
if (bypass_shared_storage_select_url_allowed_count_) {
bypass_shared_storage_select_url_allowed_count_--;
return true;
}
return ChromeContentBrowserClient::IsSharedStorageSelectURLAllowed(
browser_context, top_frame_origin, accessing_origin, out_debug_message,
out_block_is_site_setting_specific);
}
void set_bypass_shared_storage_allowed_count(int count) {
CHECK_EQ(bypass_shared_storage_allowed_count_, 0);
bypass_shared_storage_allowed_count_ = count;
}
void set_bypass_shared_storage_select_url_allowed_count(int count) {
CHECK_EQ(bypass_shared_storage_select_url_allowed_count_, 0);
bypass_shared_storage_select_url_allowed_count_ = count;
}
private:
int bypass_shared_storage_allowed_count_ = 0;
int bypass_shared_storage_select_url_allowed_count_ = 0;
};
} // namespace
class SharedStorageChromeBrowserTestBase : public PlatformBrowserTest {
public:
SharedStorageChromeBrowserTestBase() {
base::test::TaskEnvironment task_environment;
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{network::features::kSharedStorageAPI,
features::kPrivacySandboxAdsAPIsOverride,
privacy_sandbox::
kOverridePrivacySandboxSettingsLocalTesting},
/*disabled_features=*/{});
}
~SharedStorageChromeBrowserTestBase() override = default;
void SetUpOnMainThread() override {
// `PrivacySandboxAttestations` has a member of type
// `scoped_refptr<base::SequencedTaskRunner>`, its initialization must be
// done after a browser process is created.
PlatformBrowserTest::SetUpOnMainThread();
scoped_attestations_ =
std::make_unique<privacy_sandbox::ScopedPrivacySandboxAttestations>(
privacy_sandbox::PrivacySandboxAttestations::CreateForTesting());
host_resolver()->AddRule("*", "127.0.0.1");
https_server()->AddDefaultHandlers(GetChromeTestDataDir());
https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
content::SetupCrossSiteRedirector(https_server());
SetPrefs(EnablePrivacySandbox(), AllowThirdPartyCookies());
FinishSetUp();
mock_chrome_content_browser_client_ =
std::make_unique<MockChromeContentBrowserClient>();
old_chrome_content_browser_client_ = content::SetBrowserClientForTesting(
mock_chrome_content_browser_client_.get());
}
void TearDownOnMainThread() override {
content::SetBrowserClientForTesting(old_chrome_content_browser_client_);
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
content::WebContents* GetActiveWebContents() {
return chrome_test_utils::GetActiveWebContents(this);
}
content::StoragePartition* GetStoragePartition() {
return content::ToRenderFrameHost(GetActiveWebContents())
.render_frame_host()
->GetStoragePartition();
}
Profile* GetProfile() {
#if BUILDFLAG(IS_ANDROID)
return TabModelList::models()[0]->GetProfile();
#else
return browser()->profile();
#endif
}
privacy_sandbox::PrivacySandboxSettings* GetPrivacySandboxSettings() {
return PrivacySandboxSettingsFactory::GetForProfile(GetProfile());
}
// Virtual so derived classes can delay or perform additional set up before
// starting the server.
virtual void FinishSetUp() { CHECK(https_server()->Start()); }
void SetPrefs(bool enable_privacy_sandbox, bool allow_third_party_cookies) {
GetProfile()->GetPrefs()->SetInteger(
prefs::kCookieControlsMode,
static_cast<int>(
allow_third_party_cookies
? content_settings::CookieControlsMode::kOff
: content_settings::CookieControlsMode::kBlockThirdParty));
// We need to ensure the
// `PrivacySandboxDelegate::IsPrivacySandboxRestricted()` response returns
// the negation of `enable_privacy_sandbox`.
auto* privacy_sandbox_settings = GetPrivacySandboxSettings();
if (enable_privacy_sandbox) {
privacy_sandbox_settings->SetAllPrivacySandboxAllowedForTesting();
}
auto privacy_sandbox_delegate = std::make_unique<testing::NiceMock<
privacy_sandbox_test_util::MockPrivacySandboxSettingsDelegate>>();
privacy_sandbox_delegate->SetUpIsPrivacySandboxRestrictedResponse(
/*restricted=*/!enable_privacy_sandbox);
privacy_sandbox_delegate->SetUpIsIncognitoProfileResponse(
/*incognito=*/GetProfile()->IsIncognitoProfile());
privacy_sandbox_settings->SetDelegateForTesting(
std::move(privacy_sandbox_delegate));
}
void SetThirdPartyCookieSetting(const GURL& main_url) {
// We need to ensure the specific first-party URL `main_url` used by the
// test either has its third-party-cookie content setting set to
// `ContentSetting::CONTENT_SETTING_ALLOW` or
// `ContentSetting::CONTENT_SETTING_BLOCK`, according to
// `AllowThirdPartyCookies()`.
CookieSettingsFactory::GetForProfile(GetProfile())
->SetThirdPartyCookieSetting(
main_url, AllowThirdPartyCookies()
? ContentSetting::CONTENT_SETTING_ALLOW
: ContentSetting::CONTENT_SETTING_BLOCK);
}
void SetAttestationsMap(
const privacy_sandbox::PrivacySandboxAttestationsMap& attestations_map) {
privacy_sandbox::PrivacySandboxAttestations::GetInstance()
->SetAttestationsForTesting(attestations_map);
}
// Unless overridden to do otherwise, enrolls the main host to attest for
// Shared Storage exactly when `GetEnforcementAndEnrollmentStatus()` is
// `EnforcementAndEnrollmentStatus::kAttestationsEnforcedMainHostEnrolled`.
virtual void MaybeEnrollMainHost(const GURL& main_url) {
privacy_sandbox::PrivacySandboxAttestationsMap attestations_map =
(GetEnforcementAndEnrollmentStatus() ==
EnforcementAndEnrollmentStatus::kAttestationsEnforcedMainHostEnrolled)
? MakeSharedStoragePrivacySandboxAttestationsMap(
std::vector<GURL>({main_url}))
: privacy_sandbox::PrivacySandboxAttestationsMap();
SetAttestationsMap(attestations_map);
}
void
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage() {
main_url_ = https_server()->GetURL(kMainHost, kSimplePagePath);
SetThirdPartyCookieSetting(main_url_);
MaybeEnrollMainHost(main_url_);
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(), main_url_));
}
void
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
const std::vector<std::string>& additional_site_hosts) {
main_url_ = https_server()->GetURL(kMainHost, kSimplePagePath);
std::vector<GURL> urls({main_url_});
for (const auto& host : additional_site_hosts) {
urls.push_back(https_server()->GetURL(host, kSimplePagePath));
}
SetThirdPartyCookieSetting(main_url_);
SetAttestationsMap(MakeSharedStoragePrivacySandboxAttestationsMap(urls));
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(), main_url_));
}
void SetSiteException(const GURL& url, ContentSetting content_setting) {
auto* settings_map =
HostContentSettingsMapFactory::GetForProfile(GetProfile());
CHECK(settings_map);
auto* provider = settings_map->GetPrefProvider();
CHECK(provider);
provider->SetWebsiteSetting(
ContentSettingsPattern::FromURL(url),
ContentSettingsPattern::Wildcard(), ContentSettingsType::COOKIES,
base::Value(content_setting), /*constraints=*/{},
content_settings::PartitionKey::GetDefaultForTesting());
}
void AddSimpleModule(const content::ToRenderFrameHost& execution_target) {
content::WebContentsConsoleObserver add_module_console_observer(
GetActiveWebContents());
add_module_console_observer.SetFilter(
MakeFilter({"Finish executing simple_module.js"}));
std::string host =
execution_target.render_frame_host()->GetLastCommittedOrigin().host();
GURL module_script_url =
https_server()->GetURL(host, "/shared_storage/simple_module.js");
EXPECT_TRUE(content::ExecJs(
execution_target,
content::JsReplace("sharedStorage.worklet.addModule($1)",
module_script_url)));
ASSERT_TRUE(add_module_console_observer.Wait());
EXPECT_LE(1u,
content::GetAttachedSharedStorageWorkletHostsCount(
execution_target.render_frame_host()->GetStoragePartition()));
EXPECT_EQ(0u,
content::GetKeepAliveSharedStorageWorkletHostsCount(
execution_target.render_frame_host()->GetStoragePartition()));
EXPECT_EQ(1u, add_module_console_observer.messages().size());
EXPECT_EQ(
"Finish executing simple_module.js",
base::UTF16ToUTF8(add_module_console_observer.messages()[0].message));
}
bool ExecuteScriptInWorklet(
const content::ToRenderFrameHost& execution_target,
const std::string& script,
const std::string& last_script_message,
bool use_select_url = false) {
content::WebContentsConsoleObserver add_module_console_observer(
GetActiveWebContents());
add_module_console_observer.SetFilter(
MakeFilter({"Finish executing customizable_module.js"}));
base::StringPairs run_function_body_replacement;
run_function_body_replacement.emplace_back("{{RUN_FUNCTION_BODY}}", script);
std::string host =
execution_target.render_frame_host()->GetLastCommittedOrigin().host();
GURL module_script_url = https_server()->GetURL(
host, net::test_server::GetFilePathWithReplacements(
"/shared_storage/customizable_module.js",
run_function_body_replacement));
EXPECT_TRUE(content::ExecJs(
execution_target,
content::JsReplace("sharedStorage.worklet.addModule($1)",
module_script_url)));
EXPECT_TRUE(add_module_console_observer.Wait());
EXPECT_LE(1u,
content::GetAttachedSharedStorageWorkletHostsCount(
execution_target.render_frame_host()->GetStoragePartition()));
EXPECT_EQ(0u,
content::GetKeepAliveSharedStorageWorkletHostsCount(
execution_target.render_frame_host()->GetStoragePartition()));
EXPECT_EQ(1u, add_module_console_observer.messages().size());
EXPECT_EQ(
"Finish executing customizable_module.js",
base::UTF16ToUTF8(add_module_console_observer.messages()[0].message));
content::WebContentsConsoleObserver script_console_observer(
GetActiveWebContents());
script_console_observer.SetFilter(MakeFilter(
{last_script_message, ExpectedSharedStorageDisabledMessage()}));
if (!use_select_url) {
content::EvalJsResult result = content::EvalJs(execution_target, R"(
sharedStorage.run('test-operation');
)");
EXPECT_TRUE(script_console_observer.Wait());
EXPECT_EQ(1u, script_console_observer.messages().size());
EXPECT_EQ(
last_script_message,
base::UTF16ToUTF8(script_console_observer.messages()[0].message));
return result.is_ok();
}
EXPECT_TRUE(
ExecJs(execution_target,
content::JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
// Construct and add the `TestSelectURLFencedFrameConfigObserver` to shared
// storage worklet host manager.
content::StoragePartition* storage_partition =
content::ToRenderFrameHost(GetActiveWebContents())
.render_frame_host()
->GetStoragePartition();
content::TestSelectURLFencedFrameConfigObserver config_observer(
storage_partition);
content::EvalJsResult result = EvalJs(execution_target, R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-operation',
[{url: "fenced_frames/title0.html"},
{url: "fenced_frames/title1.html"},
],
{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(script_console_observer.Wait());
EXPECT_EQ(1u, script_console_observer.messages().size());
EXPECT_EQ(last_script_message,
base::UTF16ToUTF8(script_console_observer.messages()[0].message));
if (!result.is_ok()) {
return false;
}
std::optional<GURL> observed_urn_uuid = config_observer.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
GURL urn_uuid = observed_urn_uuid.value();
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
}
return true;
}
double RemainingBudget(const content::ToRenderFrameHost& execution_target,
bool should_add_module = false,
bool keep_alive_after_operation = true) {
if (should_add_module)
AddSimpleModule(execution_target);
content::WebContentsConsoleObserver budget_console_observer(
GetActiveWebContents());
const std::string kRemainingBudgetPrefixStr(kRemainingBudgetPrefix);
budget_console_observer.SetPattern(
base::StrCat({kRemainingBudgetPrefixStr, "*"}));
EXPECT_TRUE(ExecJs(execution_target,
content::JsReplace("window.keepWorklet = $1;",
keep_alive_after_operation)));
EXPECT_TRUE(ExecJs(execution_target, R"(
sharedStorage.run('remaining-budget-operation', {keepAlive: keepWorklet});
)"));
bool observed = budget_console_observer.Wait();
EXPECT_TRUE(observed);
if (!observed) {
return nan("");
}
EXPECT_EQ(1u, budget_console_observer.messages().size());
std::string console_message =
base::UTF16ToUTF8(budget_console_observer.messages()[0].message);
EXPECT_TRUE(base::StartsWith(console_message, kRemainingBudgetPrefixStr));
std::string result_string = console_message.substr(
kRemainingBudgetPrefixStr.size(),
console_message.size() - kRemainingBudgetPrefixStr.size());
double result = 0.0;
EXPECT_TRUE(base::StringToDouble(result_string, &result));
return result;
}
virtual bool ResolveSelectURLToConfig() const { return false; }
virtual bool EnablePrivacySandbox() const { return true; }
virtual bool AllowThirdPartyCookies() const { return true; }
virtual EnforcementAndEnrollmentStatus GetEnforcementAndEnrollmentStatus()
const {
return EnforcementAndEnrollmentStatus::kAttestationsUnenforced;
}
virtual bool EnableDebugMessages() const { return false; }
bool SuccessExpected() const {
return GetEnforcementAndEnrollmentStatus() !=
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostUnenrolled &&
EnablePrivacySandbox() && AllowThirdPartyCookies();
}
std::string ExpectedSharedStorageDisabledMessage() {
return "OperationError: " + content::GetSharedStorageDisabledMessage();
}
protected:
base::HistogramTester histogram_tester_;
std::unique_ptr<MockChromeContentBrowserClient>
mock_chrome_content_browser_client_;
GURL main_url_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
std::unique_ptr<privacy_sandbox::ScopedPrivacySandboxAttestations>
scoped_attestations_;
raw_ptr<content::ContentBrowserClient, AcrossTasksDanglingUntriaged>
old_chrome_content_browser_client_ = nullptr;
};
class SharedStorageChromeBrowserTest
: public SharedStorageChromeBrowserTestBase,
public testing::WithParamInterface<bool> {
public:
SharedStorageChromeBrowserTest() {
fenced_frame_api_change_feature_.InitWithFeatureState(
blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig());
fenced_frame_feature_.InitAndEnableFeature(blink::features::kFencedFrames);
attestation_feature_.InitWithFeatureState(
privacy_sandbox::kEnforcePrivacySandboxAttestations,
GetEnforcementAndEnrollmentStatus() !=
EnforcementAndEnrollmentStatus::kAttestationsUnenforced);
}
~SharedStorageChromeBrowserTest() override = default;
bool ResolveSelectURLToConfig() const override { return GetParam(); }
EnforcementAndEnrollmentStatus GetEnforcementAndEnrollmentStatus()
const override {
return EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled;
}
private:
base::test::ScopedFeatureList fenced_frame_api_change_feature_;
base::test::ScopedFeatureList fenced_frame_feature_;
base::test::ScopedFeatureList attestation_feature_;
};
// We skip testing the `enable_debug_messages` parameter on Android due to
// hardware limitations that constrain the total number of tests that can be
// run.
using SharedStorageChromeBrowserParams = std::tuple<
/*enable_privacy_sandbox=*/bool,
/*allow_third_party_cookies=*/bool,
#if BUILDFLAG(IS_ANDROID)
/*enforcement_and_enrollment_status=*/EnforcementAndEnrollmentStatus>;
#else
/*enforcement_and_enrollment_status=*/EnforcementAndEnrollmentStatus,
/*enable_debug_messages=*/bool>;
#endif
class SharedStoragePrefBrowserTest
: public SharedStorageChromeBrowserTestBase,
public testing::WithParamInterface<SharedStorageChromeBrowserParams> {
public:
SharedStoragePrefBrowserTest() {
base::FieldTrialParams params;
params["ExposeDebugMessageForSettingsStatus"] =
base::ToString(EnableDebugMessages());
shared_storage_feature_.InitAndEnableFeatureWithParameters(
network::features::kSharedStorageAPI, params);
fenced_frame_api_change_feature_.InitWithFeatureState(
blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig());
fenced_frame_feature_.InitAndEnableFeature(blink::features::kFencedFrames);
attestation_feature_.InitWithFeatureState(
privacy_sandbox::kEnforcePrivacySandboxAttestations,
GetEnforcementAndEnrollmentStatus() !=
EnforcementAndEnrollmentStatus::kAttestationsUnenforced);
}
bool ResolveSelectURLToConfig() const override { return true; }
bool EnablePrivacySandbox() const override { return std::get<0>(GetParam()); }
bool AllowThirdPartyCookies() const override {
return std::get<1>(GetParam());
}
EnforcementAndEnrollmentStatus GetEnforcementAndEnrollmentStatus()
const override {
return std::get<2>(GetParam());
}
bool EnableDebugMessages() const override {
#if BUILDFLAG(IS_ANDROID)
return false;
#else
return std::get<3>(GetParam());
#endif
}
virtual void VerifyDebugErrorMessage(const std::string& error_message) {
ASSERT_FALSE(SuccessExpected());
size_t found_pos = error_message.find("Debug");
if (!EnableDebugMessages()) {
EXPECT_EQ(found_pos, std::string::npos);
return;
}
EXPECT_NE(found_pos, std::string::npos);
int status = (GetEnforcementAndEnrollmentStatus() ==
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostUnenrolled)
? 6
: (EnablePrivacySandbox() ? 4 : 1);
if (status == 4) {
ASSERT_FALSE(AllowThirdPartyCookies());
}
found_pos = error_message.find("status " + base::NumberToString(status));
EXPECT_NE(found_pos, std::string::npos);
}
void AddSimpleModuleWithPermissionBypassed(
const content::ToRenderFrameHost& execution_target) {
content::WebContentsConsoleObserver add_module_console_observer(
GetActiveWebContents());
add_module_console_observer.SetFilter(
MakeFilter({"Finish executing simple_module.js"}));
// Bypass the following permissions to allow one `addModule()` call.
mock_chrome_content_browser_client_
->set_bypass_shared_storage_allowed_count(1);
mock_chrome_content_browser_client_
->set_bypass_shared_storage_select_url_allowed_count(1);
EXPECT_TRUE(content::ExecJs(execution_target, R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)"));
EXPECT_TRUE(add_module_console_observer.Wait());
// Shared Storage is enabled in order to `addModule()`.
EXPECT_EQ(1u, add_module_console_observer.messages().size());
EXPECT_EQ(
"Finish executing simple_module.js",
base::UTF16ToUTF8(add_module_console_observer.messages()[0].message));
}
bool ExecuteScriptInWorkletWithOuterPermissionsBypassed(
const content::ToRenderFrameHost& execution_target,
const std::string& script,
const std::string& last_script_message) {
content::WebContentsConsoleObserver add_module_console_observer(
GetActiveWebContents());
add_module_console_observer.SetFilter(
MakeFilter({"Finish executing customizable_module.js"}));
base::StringPairs run_function_body_replacement;
run_function_body_replacement.emplace_back("{{RUN_FUNCTION_BODY}}", script);
std::string host =
execution_target.render_frame_host()->GetLastCommittedOrigin().host();
GURL module_script_url = https_server()->GetURL(
host, net::test_server::GetFilePathWithReplacements(
"/shared_storage/customizable_module.js",
run_function_body_replacement));
// Bypass the following permissions to allow one call for `addModule()` and
// `run()` respectively. Any operations nested within the script run by
// `run()` will have preferences applied according to test parameters. When
// the latter disallow Shared Storage, it siumlates the situation where
// preferences are updated to block Shared Storage during the course of a
// previously allowed `run()` call.
mock_chrome_content_browser_client_
->set_bypass_shared_storage_allowed_count(2);
mock_chrome_content_browser_client_
->set_bypass_shared_storage_select_url_allowed_count(1);
EXPECT_TRUE(content::ExecJs(
execution_target,
content::JsReplace("sharedStorage.worklet.addModule($1)",
module_script_url)));
EXPECT_TRUE(add_module_console_observer.Wait());
EXPECT_EQ(1u,
content::GetAttachedSharedStorageWorkletHostsCount(
execution_target.render_frame_host()->GetStoragePartition()));
EXPECT_EQ(0u,
content::GetKeepAliveSharedStorageWorkletHostsCount(
execution_target.render_frame_host()->GetStoragePartition()));
EXPECT_EQ(1u, add_module_console_observer.messages().size());
EXPECT_EQ(
"Finish executing customizable_module.js",
base::UTF16ToUTF8(add_module_console_observer.messages()[0].message));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
content::WebContentsConsoleObserver script_console_observer(
GetActiveWebContents());
script_console_observer.SetFilter(MakeFilter(
{last_script_message, ExpectedSharedStorageDisabledMessage()}));
content::EvalJsResult result = content::EvalJs(execution_target, R"(
sharedStorage.run('test-operation');
)");
EXPECT_TRUE(script_console_observer.Wait());
EXPECT_EQ(1u, script_console_observer.messages().size());
if (SuccessExpected()) {
EXPECT_EQ(
last_script_message,
base::UTF16ToUTF8(script_console_observer.messages()[0].message));
} else {
EXPECT_TRUE(base::StartsWith(
base::UTF16ToUTF8(script_console_observer.messages()[0].message),
ExpectedSharedStorageDisabledMessage()));
}
WaitForHistograms({kTimingDocumentRunHistogram});
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 1);
return result.is_ok();
}
private:
base::test::ScopedFeatureList shared_storage_feature_;
base::test::ScopedFeatureList fenced_frame_api_change_feature_;
base::test::ScopedFeatureList fenced_frame_feature_;
base::test::ScopedFeatureList attestation_feature_;
};
namespace {
std::string DescribePrefBrowserTestParams(
const testing::TestParamInfo<SharedStoragePrefBrowserTest::ParamType>&
info) {
return base::StrCat(
{"PrivacySandbox", std::get<0>(info.param) ? "Enabled" : "Disabled",
"_3PCookies", std::get<1>(info.param) ? "Allowed" : "Blocked",
"_Attestations",
(std::get<2>(info.param) !=
EnforcementAndEnrollmentStatus::kAttestationsUnenforced)
? base::StrCat({"Enforced_MainHost",
(std::get<2>(info.param) ==
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled)
? "Enrolled"
: "Unenrolled"})
: "Unenforced"
#if !BUILDFLAG(IS_ANDROID)
,
"_Debug", std::get<3>(info.param) ? "Enabled" : "Disabled"
#endif
});
}
} // namespace
INSTANTIATE_TEST_SUITE_P(
All,
SharedStoragePrefBrowserTest,
testing::Combine(
testing::Bool(),
testing::Bool(),
testing::Values(EnforcementAndEnrollmentStatus::kAttestationsUnenforced,
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostUnenrolled,
#if BUILDFLAG(IS_ANDROID)
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled)),
#else
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled),
testing::Bool()),
#endif
DescribePrefBrowserTestParams);
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, AddModule) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(MakeFilter({"Finish executing simple_module.js"}));
content::WebContentsConsoleObserver attestations_console_observer(
GetActiveWebContents());
attestations_console_observer.SetPattern(
"Attestation check for Shared Storage on * failed.");
content::EvalJsResult result = content::EvalJs(GetActiveWebContents(), R"(
sharedStorage.worklet.addModule('shared_storage/simple_module.js');
)");
if (!SuccessExpected()) {
// Shared Storage will be disabled.
EXPECT_TRUE(
base::StartsWith(result.ExtractError(),
GetSharedStorageAddModuleDisabledErrorMessage()));
VerifyDebugErrorMessage(result.ExtractError());
EXPECT_EQ(0u, console_observer.messages().size());
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
if (GetEnforcementAndEnrollmentStatus() ==
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostUnenrolled) {
ASSERT_TRUE(attestations_console_observer.Wait());
EXPECT_FALSE(attestations_console_observer.messages().empty());
}
return;
}
ASSERT_TRUE(console_observer.Wait());
// Privacy Sandbox is enabled and 3P cookies are allowed, so Shared Storage
// should be allowed.
EXPECT_TRUE(result.is_ok());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Finish executing simple_module.js",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_TRUE(attestations_console_observer.messages().empty());
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, RunOperation) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
AddSimpleModuleWithPermissionBypassed(GetActiveWebContents());
content::WebContentsConsoleObserver run_op_console_observer(
GetActiveWebContents());
run_op_console_observer.SetFilter(
MakeFilter({"Finish executing \'test-operation\'"}));
content::EvalJsResult run_op_result =
content::EvalJs(GetActiveWebContents(), R"(
sharedStorage.run(
'test-operation', {data: {'customKey': 'customValue'}});
)");
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram});
EXPECT_GE(
histogram_tester_.GetBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess),
1);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
if (!SuccessExpected()) {
// Shared Storage will be disabled.
EXPECT_TRUE(base::StartsWith(run_op_result.ExtractError(),
GetSharedStorageDisabledErrorMessage()));
VerifyDebugErrorMessage(run_op_result.ExtractError());
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kRunWebVisible, 1);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
return;
}
ASSERT_TRUE(run_op_console_observer.Wait());
// Privacy Sandbox is enabled and 3P cookies are allowed, so Shared Storage
// should be allowed.
EXPECT_TRUE(run_op_result.is_ok());
EXPECT_EQ(1u, run_op_console_observer.messages().size());
EXPECT_EQ("Finish executing \'test-operation\'",
base::UTF16ToUTF8(run_op_console_observer.messages()[0].message));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kErrorTypeHistogram, 2),
std::make_tuple(kTimingDocumentRunHistogram, 1)});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 1);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, RunURLSelectionOperation) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
AddSimpleModuleWithPermissionBypassed(GetActiveWebContents());
content::WebContentsConsoleObserver run_url_op_console_observer(
GetActiveWebContents());
run_url_op_console_observer.SetFilter(
MakeFilter({"Finish executing \'test-url-selection-operation\'"}));
EXPECT_TRUE(ExecJs(GetActiveWebContents(),
content::JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
// Construct and add the `TestSelectURLFencedFrameConfigObserver` to shared
// storage worklet host manager.
content::StoragePartition* storage_partition = GetStoragePartition();
content::TestSelectURLFencedFrameConfigObserver config_observer(
storage_partition);
content::EvalJsResult run_url_op_result = EvalJs(GetActiveWebContents(), 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;
})()
)");
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram});
EXPECT_GE(
histogram_tester_.GetBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess),
1);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
if (!SuccessExpected()) {
// Shared Storage will be disabled.
EXPECT_TRUE(
base::StartsWith(run_url_op_result.ExtractError(),
GetSharedStorageSelectURLDisabledErrorMessage()));
VerifyDebugErrorMessage(run_url_op_result.ExtractError());
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kSelectURLWebVisible, 1);
// Navigate away to record `kWorkletNumPerPageHistogram`,
// `kSelectUrlCallsPerPageHistogram` histograms.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
return;
}
ASSERT_TRUE(run_url_op_console_observer.Wait());
// Privacy Sandbox is enabled and 3P cookies are allowed, so Shared Storage
// should be allowed.
EXPECT_TRUE(run_url_op_result.is_ok());
std::optional<GURL> observed_urn_uuid = config_observer.GetUrnUuid();
EXPECT_TRUE(observed_urn_uuid.has_value());
EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
GURL urn_uuid = observed_urn_uuid.value();
if (!ResolveSelectURLToConfig()) {
EXPECT_EQ(run_url_op_result.ExtractString(), observed_urn_uuid->spec());
}
EXPECT_EQ(1u, run_url_op_console_observer.messages().size());
EXPECT_EQ(
"Finish executing \'test-url-selection-operation\'",
base::UTF16ToUTF8(run_url_op_console_observer.messages()[0].message));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kErrorTypeHistogram, 2),
std::make_tuple(kTimingDocumentSelectUrlHistogram, 1)});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentSelectUrlHistogram, 1);
// Navigate away to record `kWorkletNumPerPageHistogram`,
// `kSelectUrlCallsPerPageHistogram` histograms.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, Set) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
content::EvalJsResult set_result = content::EvalJs(GetActiveWebContents(), R"(
sharedStorage.set('customKey', 'customValue');
)");
if (!SuccessExpected()) {
// Shared Storage will be disabled.
EXPECT_TRUE(base::StartsWith(set_result.ExtractError(),
GetSharedStorageDisabledErrorMessage()));
VerifyDebugErrorMessage(set_result.ExtractError());
return;
}
// Privacy Sandbox is enabled and 3P cookies are allowed, so Shared Storage
// should be allowed.
EXPECT_TRUE(set_result.is_ok());
WaitForHistograms({kTimingDocumentSetHistogram});
histogram_tester_.ExpectTotalCount(kTimingDocumentSetHistogram, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, Append) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
content::EvalJsResult append_result =
content::EvalJs(GetActiveWebContents(), R"(
sharedStorage.append('customKey', 'customValue');
)");
if (!SuccessExpected()) {
// Shared Storage will be disabled.
EXPECT_TRUE(base::StartsWith(append_result.ExtractError(),
GetSharedStorageDisabledErrorMessage()));
VerifyDebugErrorMessage(append_result.ExtractError());
return;
}
// Privacy Sandbox is enabled and 3P cookies are allowed, so Shared Storage
// should be allowed.
EXPECT_TRUE(append_result.is_ok());
WaitForHistograms({kTimingDocumentAppendHistogram});
histogram_tester_.ExpectTotalCount(kTimingDocumentAppendHistogram, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, Delete) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
content::EvalJsResult delete_result =
content::EvalJs(GetActiveWebContents(), R"(
sharedStorage.delete('customKey');
)");
if (!SuccessExpected()) {
// Shared Storage will be disabled.
EXPECT_TRUE(base::StartsWith(delete_result.ExtractError(),
GetSharedStorageDisabledErrorMessage()));
VerifyDebugErrorMessage(delete_result.ExtractError());
return;
}
// Privacy Sandbox is enabled and 3P cookies are allowed, so Shared Storage
// should be allowed.
EXPECT_TRUE(delete_result.is_ok());
WaitForHistograms({kTimingDocumentDeleteHistogram});
histogram_tester_.ExpectTotalCount(kTimingDocumentDeleteHistogram, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, Clear) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
content::EvalJsResult clear_result =
content::EvalJs(GetActiveWebContents(), R"(
sharedStorage.clear();
)");
if (!SuccessExpected()) {
// Shared Storage will be disabled.
EXPECT_TRUE(base::StartsWith(clear_result.ExtractError(),
GetSharedStorageDisabledErrorMessage()));
VerifyDebugErrorMessage(clear_result.ExtractError());
return;
}
// Privacy Sandbox is enabled and 3P cookies are allowed, so Shared Storage
// should be allowed.
EXPECT_TRUE(clear_result.is_ok());
WaitForHistograms({kTimingDocumentClearHistogram});
histogram_tester_.ExpectTotalCount(kTimingDocumentClearHistogram, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletSet) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// If `set()` fails due to Shared Storage being disabled, there will be a
// console message verified in the helper
// `ExecuteScriptInWorkletWithOuterPermissionsBypassed()` rather than an error
// message since it is wrapped in a `console.log()` call.
EXPECT_TRUE(ExecuteScriptInWorkletWithOuterPermissionsBypassed(
GetActiveWebContents(), R"(
console.log(await sharedStorage.set('key0', 'value0'));
console.log('Finished script');
)",
"Finished script"));
if (SuccessExpected()) {
WaitForHistograms({kTimingWorkletSetHistogram});
histogram_tester_.ExpectTotalCount(kTimingWorkletSetHistogram, 1);
}
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletAppend) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// If `append()` fails due to Shared Storage being disabled, there will be a
// console message verified in the helper
// `ExecuteScriptInWorkletWithOuterPermissionsBypassed()` rather than an error
// message since it is wrapped in a `console.log()` call.
EXPECT_TRUE(ExecuteScriptInWorkletWithOuterPermissionsBypassed(
GetActiveWebContents(), R"(
console.log(await sharedStorage.append('key0', 'value0'));
console.log('Finished script');
)",
"Finished script"));
if (SuccessExpected()) {
WaitForHistograms({kTimingWorkletAppendHistogram});
histogram_tester_.ExpectTotalCount(kTimingWorkletAppendHistogram, 1);
}
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletDelete) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// If `delete()` fails due to Shared Storage being disabled, there will be a
// console message verified in the helper
// `ExecuteScriptInWorkletWithOuterPermissionsBypassed()` rather than an error
// message since it is wrapped in a `console.log()` call.
EXPECT_TRUE(ExecuteScriptInWorkletWithOuterPermissionsBypassed(
GetActiveWebContents(), R"(
console.log(await sharedStorage.delete('key0'));
console.log('Finished script');
)",
"Finished script"));
if (SuccessExpected()) {
WaitForHistograms({kTimingWorkletDeleteHistogram});
histogram_tester_.ExpectTotalCount(kTimingWorkletDeleteHistogram, 1);
}
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletClear) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// If `clear()` fails due to Shared Storage being disabled, there will be a
// console message verified in the helper
// `ExecuteScriptInWorkletWithOuterPermissionsBypassed()` rather than an error
// message since it is wrapped in a `console.log()` call.
EXPECT_TRUE(ExecuteScriptInWorkletWithOuterPermissionsBypassed(
GetActiveWebContents(), R"(
console.log(await sharedStorage.clear());
console.log('Finished script');
)",
"Finished script"));
if (SuccessExpected()) {
WaitForHistograms({kTimingWorkletClearHistogram});
histogram_tester_.ExpectTotalCount(kTimingWorkletClearHistogram, 1);
}
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletGet) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// To prevent failure in the case where Shared Storage is enabled, we set a
// key before retrieving it; but in the case here we expect failure, we test
// only `get()` to isolate the behavior and determine if the promise is
// rejected solely from that call.
std::string script = SuccessExpected() ? R"(
console.log(await sharedStorage.set('key0', 'value0'));
console.log(await sharedStorage.get('key0'));
console.log('Finished script');
)"
: R"(
console.log(await sharedStorage.get('key0'));
console.log('Finished script');
)";
// If `get()` fails due to Shared Storage being disabled, there will be a
// console message verified in the helper
// `ExecuteScriptInWorkletWithOuterPermissionsBypassed()` rather than an error
// message since it is wrapped in a `console.log()` call.
EXPECT_TRUE(ExecuteScriptInWorkletWithOuterPermissionsBypassed(
GetActiveWebContents(), script, "Finished script"));
if (SuccessExpected()) {
WaitForHistograms({kTimingWorkletSetHistogram, kTimingWorkletGetHistogram});
histogram_tester_.ExpectTotalCount(kTimingWorkletSetHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingWorkletGetHistogram, 1);
}
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletKeys) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// If `keys()` fails due to Shared Storage being disabled, there will be a
// console message verified in the helper
// `ExecuteScriptInWorkletWithOuterPermissionsBypassed()` rather than an error
// message since it is wrapped in a `console.log()` call.
EXPECT_TRUE(ExecuteScriptInWorkletWithOuterPermissionsBypassed(
GetActiveWebContents(), R"(
for await (const key of sharedStorage.keys()) {
console.log(key);
}
console.log('Finished script');
)",
"Finished script"));
if (SuccessExpected()) {
WaitForHistograms({kTimingWorkletKeysHistogram});
histogram_tester_.ExpectTotalCount(kTimingWorkletKeysHistogram, 1);
}
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletEntries) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// If `entries()` fails due to Shared Storage being disabled, there will be a
// console message verified in the helper
// `ExecuteScriptInWorkletWithOuterPermissionsBypassed()` rather than an error
// message since it is wrapped in a `console.log()` call.
EXPECT_TRUE(ExecuteScriptInWorkletWithOuterPermissionsBypassed(
GetActiveWebContents(), R"(
for await (const [key, value] of sharedStorage.entries()) {
console.log(key + ';' + value);
}
console.log('Finished script');
)",
"Finished script"));
if (SuccessExpected()) {
WaitForHistograms({kTimingWorkletEntriesHistogram});
histogram_tester_.ExpectTotalCount(kTimingWorkletEntriesHistogram, 1);
}
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletLength) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// If `length()` fails due to Shared Storage being disabled, there will be a
// console message verified in the helper
// `ExecuteScriptInWorkletWithOuterPermissionsBypassed()` rather than an error
// message since it is wrapped in a `console.log()` call.
EXPECT_TRUE(ExecuteScriptInWorkletWithOuterPermissionsBypassed(
GetActiveWebContents(), R"(
console.log(await sharedStorage.length());
console.log('Finished script');
)",
"Finished script"));
if (SuccessExpected()) {
WaitForHistograms({kTimingWorkletLengthHistogram});
histogram_tester_.ExpectTotalCount(kTimingWorkletLengthHistogram, 1);
}
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, WorkletRemainingBudget) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// If `remainingBudget()` fails due to Shared Storage being disabled, there
// will be a console message verified in the helper
// `ExecuteScriptInWorkletWithOuterPermissionsBypassed()` rather than an error
// message since it is wrapped in a `console.log()` call.
EXPECT_TRUE(ExecuteScriptInWorkletWithOuterPermissionsBypassed(
GetActiveWebContents(), R"(
console.log(await sharedStorage.remainingBudget());
console.log('Finished script');
)",
"Finished script"));
if (SuccessExpected()) {
WaitForHistograms({kTimingRemainingBudgetHistogram});
histogram_tester_.ExpectTotalCount(kTimingRemainingBudgetHistogram, 1);
}
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
WorkletKeysEntries_AllIterated) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(ExecuteScriptInWorklet(GetActiveWebContents(), R"(
for (let i = 0; i < 150; ++i) {
sharedStorage.set('key' + i.toString().padStart(3, '0'),
'value' + i.toString().padStart(3, '0'));
}
for await (const key of sharedStorage.keys()) {
console.log(key);
}
for await (const [key, value] of sharedStorage.entries()) {
console.log(key + ';' + value);
}
console.log('Finished script');
)",
"Finished script"));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentRunHistogram, kTimingWorkletKeysHistogram,
kTimingWorkletEntriesHistogram,
kEntriesQueuedCountHistogram,
kReceivedEntriesBenchmarksHistogram,
kIteratedEntriesBenchmarksHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingWorkletKeysHistogram, 151);
histogram_tester_.ExpectTotalCount(kTimingWorkletEntriesHistogram, 151);
histogram_tester_.ExpectUniqueSample(kEntriesQueuedCountHistogram, 150, 2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 0,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 10,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 20,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 30,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 40,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 50,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 60,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 70,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 80,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 90,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 100,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 0,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 10,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 20,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 30,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 40,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 50,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 60,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 70,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 80,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 90,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 100,
2);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
// See crbug.com/1453981: A CL on V8 side (https://crrev.com/c/4582948) made
// each Api call slower in Android debug mode compared to what we had before
// because of additional DCHECKs. So we disable on Android debug builds where
// this test times out.
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
#if BUILDFLAG(IS_ANDROID) && !defined(NDEBUG)
DISABLED_WorkletKeys_PartiallyIterated
#else
WorkletKeys_PartiallyIterated
#endif // BUILDFLAG(IS_ANDROID) && !defined(NDEBUG)
) {
base::test::ScopedRunLoopTimeout timeout(FROM_HERE, base::Seconds(120));
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(ExecuteScriptInWorklet(GetActiveWebContents(), R"(
for (let i = 0; i < 300; ++i) {
sharedStorage.set('key' + i.toString().padStart(3, '0'),
'value' + i.toString().padStart(3, '0'));
}
var keys = sharedStorage.keys();
for (let i = 0; i < 150; ++i) {
let key_dict = await keys.next();
console.log(key_dict['value']);
}
var keys2 = sharedStorage.keys();
for (let i = 0; i < 243; ++i) {
let key_dict = await keys2.next();
console.log(key_dict['value']);
}
console.log('Finished script');
)",
"Finished script"));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentRunHistogram, kTimingWorkletKeysHistogram,
kEntriesQueuedCountHistogram,
kReceivedEntriesBenchmarksHistogram,
kIteratedEntriesBenchmarksHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingWorkletKeysHistogram, 150 + 243);
histogram_tester_.ExpectUniqueSample(kEntriesQueuedCountHistogram, 300, 2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 0,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 10,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 20,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 30,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 40,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 50,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 60,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 70,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 80,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 90,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 100,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 0,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 10,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 20,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 30,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 40,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 50,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 60,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 70,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 80,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 90,
0);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 100,
0);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
// See crbug.com/1453981: A CL on V8 side (https://crrev.com/c/4582948) made
// each Api call slower in Android debug mode compared to what we had before
// because of additional DCHECKs. So we disable on Android debug builds where
// this test times out.
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
#if BUILDFLAG(IS_ANDROID) && !defined(NDEBUG)
DISABLED_WorkletEntries_PartiallyIterated
#else
WorkletEntries_PartiallyIterated
#endif // BUILDFLAG(IS_ANDROID) && !defined(NDEBUG)
) {
base::test::ScopedRunLoopTimeout timeout(FROM_HERE, base::Seconds(120));
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(ExecuteScriptInWorklet(GetActiveWebContents(), R"(
for (let i = 0; i < 300; ++i) {
sharedStorage.set('key' + i.toString().padStart(3, '0'),
'value' + i.toString().padStart(3, '0'));
}
var entries = sharedStorage.entries();
for (let i = 0; i < 101; ++i) {
let entry_dict = await entries.next();
console.log(entry_dict['value']);
}
var entries = sharedStorage.entries();
for (let i = 0; i < 299; ++i) {
let entry_dict = await entries.next();
console.log(entry_dict['value']);
}
console.log('Finished script');
)",
"Finished script"));
WaitForHistograms(
{kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentRunHistogram, kTimingWorkletEntriesHistogram,
kEntriesQueuedCountHistogram, kReceivedEntriesBenchmarksHistogram,
kIteratedEntriesBenchmarksHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingWorkletEntriesHistogram, 101 + 299);
histogram_tester_.ExpectUniqueSample(kEntriesQueuedCountHistogram, 300, 2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 0,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 10,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 20,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 30,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 40,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 50,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 60,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 70,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 80,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 90,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 100,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 0,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 10,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 20,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 30,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 40,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 50,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 60,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 70,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 80,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 90,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 100,
0);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
WorkletKeysEntries_AllIteratedLessThanTenKeys) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(ExecuteScriptInWorklet(GetActiveWebContents(), R"(
for (let i = 0; i < 5; ++i) {
sharedStorage.set('key' + i.toString().padStart(3, '0'),
'value' + i.toString().padStart(3, '0'));
}
for await (const key of sharedStorage.keys()) {
console.log(key);
}
for await (const [key, value] of sharedStorage.entries()) {
console.log(key + ';' + value);
}
console.log('Finished script');
)",
"Finished script"));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentRunHistogram, kTimingWorkletKeysHistogram,
kTimingWorkletEntriesHistogram,
kEntriesQueuedCountHistogram,
kReceivedEntriesBenchmarksHistogram,
kIteratedEntriesBenchmarksHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingWorkletKeysHistogram, 6);
histogram_tester_.ExpectTotalCount(kTimingWorkletEntriesHistogram, 6);
histogram_tester_.ExpectUniqueSample(kEntriesQueuedCountHistogram, 5, 2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 0,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 10,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 20,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 30,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 40,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 50,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 60,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 70,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 80,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 90,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 100,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 0,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 10,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 20,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 30,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 40,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 50,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 60,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 70,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 80,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 90,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 100,
2);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
WorkletKeysEntries_PartiallyIteratedLessThanTenKeys) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(ExecuteScriptInWorklet(GetActiveWebContents(), R"(
for (let i = 0; i < 5; ++i) {
sharedStorage.set('key' + i.toString().padStart(3, '0'),
'value' + i.toString().padStart(3, '0'));
}
var keys = sharedStorage.keys();
for (let i = 0; i < 4; ++i) {
let key_dict = await keys.next();
console.log(key_dict['value']);
}
var entries = sharedStorage.entries();
for (let i = 0; i < 2; ++i) {
let entry_dict = await entries.next();
console.log(entry_dict['value']);
}
var keys2 = sharedStorage.keys();
for (let i = 0; i < 3; ++i) {
let key_dict = await keys2.next();
console.log(key_dict['value']);
}
var entries = sharedStorage.entries();
for (let i = 0; i < 1; ++i) {
let entry_dict = await entries.next();
console.log(entry_dict['value']);
}
console.log('Finished script');
)",
"Finished script"));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentRunHistogram, kTimingWorkletKeysHistogram,
kTimingWorkletEntriesHistogram,
kEntriesQueuedCountHistogram,
kReceivedEntriesBenchmarksHistogram,
kIteratedEntriesBenchmarksHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingWorkletKeysHistogram, 4 + 3);
histogram_tester_.ExpectTotalCount(kTimingWorkletEntriesHistogram, 2 + 1);
histogram_tester_.ExpectUniqueSample(kEntriesQueuedCountHistogram, 5, 4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 0,
4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 10,
4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 20,
4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 30,
4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 40,
4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 50,
4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 60,
4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 70,
4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 80,
4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 90,
4);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 100,
4);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 0,
4);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 10,
4);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 20,
4);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 30,
3);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 40,
3);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 50,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 60,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 70,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 80,
1);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 90,
0);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
WorkletKeysEntries_AllIteratedNoKeys) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(ExecuteScriptInWorklet(GetActiveWebContents(), R"(
sharedStorage.set('key', 'value');
sharedStorage.delete('key');
for await (const key of sharedStorage.keys()) {
console.log(key);
}
for await (const [key, value] of sharedStorage.entries()) {
console.log(key + ';' + value);
}
console.log('Finished script');
)",
"Finished script"));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentRunHistogram, kTimingWorkletKeysHistogram,
kTimingWorkletEntriesHistogram,
kEntriesQueuedCountHistogram,
kReceivedEntriesBenchmarksHistogram,
kIteratedEntriesBenchmarksHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingWorkletKeysHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingWorkletEntriesHistogram, 1);
histogram_tester_.ExpectUniqueSample(kEntriesQueuedCountHistogram, 0, 2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 0,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 10,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 20,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 30,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 40,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 50,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 60,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 70,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 80,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 90,
2);
histogram_tester_.ExpectBucketCount(kReceivedEntriesBenchmarksHistogram, 100,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 0,
2);
histogram_tester_.ExpectBucketCount(kIteratedEntriesBenchmarksHistogram, 10,
0);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
AddModule_InvalidScriptUrlError) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
std::string invalid_url = "http://#";
content::EvalJsResult result = content::EvalJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", invalid_url));
EXPECT_EQ(
base::StrCat({"a JavaScript error: \"DataError: The module script url is "
"invalid.\n",
" at __const_std::string&_script__:1:24):\n",
" {sharedStorage.worklet.addModule(\"", invalid_url,
"\")\n", " ^^^^^\n"}),
result.ExtractError());
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
AddModule_LoadFailureError) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url = https_server()->GetURL(
kMainHost, "/shared_storage/nonexistent_module.js");
content::EvalJsResult result = content::EvalJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url));
EXPECT_EQ(
base::StrCat({"a JavaScript error: \"OperationError: Failed to load ",
script_url.spec(), " HTTP status = 404 Not Found.\"\n"}),
result.ExtractError());
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
AddModule_UnexpectedRedirectError) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url = https_server()->GetURL(
kMainHost, "/server-redirect?shared_storage/simple_module.js");
content::EvalJsResult result = content::EvalJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url));
EXPECT_EQ(
base::StrCat(
{"a JavaScript error: \"OperationError: Unexpected redirect on ",
script_url.spec(), ".\"\n"}),
result.ExtractError());
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
AddModule_EmptyResultError) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/erroneous_module.js");
EXPECT_THAT(
content::EvalJs(GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)",
script_url)),
content::EvalJsResult::ErrorIs(testing::HasSubstr(
"ReferenceError: undefinedVariable is not defined")));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
AddModule_MultipleAddModuleError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/simple_module.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_THAT(
content::EvalJs(GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)",
script_url)),
content::EvalJsResult::ErrorIs(testing::HasSubstr(
"addModule() can only be invoked once per worklet")));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, Run_NotLoadedError) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_THAT(
content::EvalJs(GetActiveWebContents(), R"(
sharedStorage.run(
'test-operation', {data: {}});
)"),
content::EvalJsResult::ErrorIs(testing::HasSubstr(
"sharedStorage.worklet.addModule() has to be called before run()")));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kRunWebVisible,
1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, Run_NotRegisteredError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/simple_module.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
R"(
sharedStorage.run(
'test-operation-1', {data: {}});
)"));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kRunNonWebVisibleOperationNotFound,
1);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, Run_FunctionError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/erroneous_module2.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
R"(
sharedStorage.run(
'test-operation', {data: {}});
)"));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kRunNonWebVisibleOther, 1);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, Run_ScriptError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/erroneous_module4.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
R"(
sharedStorage.run(
'test-operation', {data: {}});
)"));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kRunNonWebVisibleOther, 1);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
Run_UnexpectedCustomDataError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/erroneous_module5.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
R"(
sharedStorage.run(
'test-operation', {data: {'customField': 'customValue123'}});
)"));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kRunNonWebVisibleOther, 1);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
SelectUrl_NotLoadedError) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(ExecJs(GetActiveWebContents(),
content::JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
EXPECT_THAT(EvalJs(GetActiveWebContents(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation-1',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)"),
content::EvalJsResult::ErrorIs(testing::HasSubstr(
"sharedStorage.worklet.addModule() has to be "
"called before selectURL()")));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kSelectURLWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
SelectUrl_NotRegisteredError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/simple_module.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_TRUE(ExecJs(GetActiveWebContents(),
content::JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation-1',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)"));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::
kSelectURLNonWebVisibleOperationNotFound,
1);
// Navigate away to record `kWorkletNumPerPageHistogram`,
// `kSelectUrlCallsPerPageHistogram` histograms.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
SelectUrl_FunctionError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/erroneous_module2.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_TRUE(ExecJs(GetActiveWebContents(),
content::JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)"));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kSelectURLNonWebVisibleOther, 1);
// Navigate away to record `kWorkletNumPerPageHistogram`,
// `kSelectUrlCallsPerPageHistogram` histograms.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, SelectUrl_ScriptError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/erroneous_module4.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_TRUE(ExecJs(GetActiveWebContents(),
content::JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)"));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kSelectURLNonWebVisibleOther, 1);
// Navigate away to record `kWorkletNumPerPageHistogram`,
// `kSelectUrlCallsPerPageHistogram` histograms.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
SelectUrl_UnexpectedCustomDataError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/erroneous_module5.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_TRUE(ExecJs(GetActiveWebContents(),
content::JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'customField': 'customValue123'},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)"));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kSelectURLNonWebVisibleOther, 1);
// Navigate away to record `kWorkletNumPerPageHistogram`,
// `kSelectUrlCallsPerPageHistogram` histograms.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
SelectUrl_OutOfRangeError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/erroneous_module6.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_TRUE(ExecJs(GetActiveWebContents(),
content::JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation-1',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)"));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::
kSelectURLNonWebVisibleReturnValueOutOfRange,
1);
// Navigate away to record `kWorkletNumPerPageHistogram`,
// `kSelectUrlCallsPerPageHistogram` histograms.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
SelectUrl_ReturnValueToIntError) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url =
https_server()->GetURL(kMainHost, "/shared_storage/erroneous_module6.js");
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
EXPECT_TRUE(ExecJs(GetActiveWebContents(),
content::JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
(async function() {
window.select_url_result = await sharedStorage.selectURL(
'test-url-selection-operation-2',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {},
resolveToConfig: resolveSelectURLToConfig
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)"));
WaitForHistogramsWithSampleCounts(
{std::make_tuple(kTimingDocumentAddModuleHistogram, 1),
std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::
kSelectURLNonWebVisibleReturnValueToInt,
1);
// Navigate away to record `kWorkletNumPerPageHistogram`,
// `kSelectUrlCallsPerPageHistogram` histograms.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletScriptDataOrigin_PrefsError_PrivacySandbox) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
// Disable Privacy Sandbox.
SetPrefs(/*enable_privacy_sandbox=*/false,
/*allow_third_party_cookies=*/true);
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
content::EvalJsResult result = content::EvalJs(
GetActiveWebContents(),
content::JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})",
script_url));
EXPECT_THAT(result, content::EvalJsResult::ErrorIs(testing::StartsWith(
GetSharedStorageAddModuleDisabledErrorMessage())));
EXPECT_EQ(0u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
// This test shows that the correct origin is used for the
// preferences/attestation check for cross-origin worklets.
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletScriptDataOrigin_PrefsError_SiteSettings) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
// Set a site exception blocking `script_url`.
SetSiteException(script_url, ContentSetting::CONTENT_SETTING_BLOCK);
// The prefs error for `createWorklet()` won't be revealed to the cross-origin
// caller. We verify the error indirectly using the histogram.
EXPECT_TRUE(
content::ExecJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
await sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'});
})()
)",
script_url)));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::
kAddModuleNonWebVisibleCrossOriginSharedStorageDisabled,
1);
}
// This test also shows that the correct origin is used for the
// preferences/attestation check for cross-origin worklets.
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletScriptDataOrigin_AttestationError) {
// Only the main frame site will be attested.
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
content::EvalJsResult result = content::EvalJs(
GetActiveWebContents(),
content::JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})",
script_url));
EXPECT_TRUE(base::StartsWith(
result.ExtractError(), GetSharedStorageAddModuleDisabledErrorMessage()));
EXPECT_EQ(0u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginWorklet_SelectUrl_PrefsError_PrivacySandbox) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(
content::ExecJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url)));
// Disable Privacy Sandbox.
SetPrefs(/*enable_privacy_sandbox=*/false,
/*allow_third_party_cookies=*/true);
content::EvalJsResult result = content::EvalJs(GetActiveWebContents(), R"(
window.testWorklet.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0}
}
)
)");
EXPECT_TRUE(base::StartsWith(
result.ExtractError(), GetSharedStorageSelectURLDisabledErrorMessage()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kSelectURLWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginWorklet_SelectUrl_PrefsError_SiteSettings) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(
content::ExecJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url)));
// Set a site exception blocking `script_url`.
SetSiteException(script_url, ContentSetting::CONTENT_SETTING_BLOCK);
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
// The prefs error for `selectURL()` won't be revealed to the cross-origin
// caller. But we can verify the error indirectly, by checking that no console
// messages are logged, which indicates that the operation did not execute.
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
window.testWorklet.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0}
}
)
)"));
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
EXPECT_EQ(0u, console_observer.messages().size());
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::
kSelectURLNonWebVisibleCrossOriginSharedStorageDisabled,
1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginWorklet_Run_PrefsError_PrivacySandbox) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(
content::ExecJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url)));
// Disable Privacy Sandbox.
SetPrefs(/*enable_privacy_sandbox=*/false,
/*allow_third_party_cookies=*/true);
content::EvalJsResult result = content::EvalJs(GetActiveWebContents(), R"(
window.testWorklet.run('test-operation')
)");
EXPECT_TRUE(base::StartsWith(result.ExtractError(),
GetSharedStorageDisabledErrorMessage()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kRunWebVisible,
1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginWorklet_Run_PrefsError_SiteSettings) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(
content::ExecJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url)));
// Set a site exception blocking `script_url`.
SetSiteException(script_url, ContentSetting::CONTENT_SETTING_BLOCK);
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
// The prefs error for `run()` won't be revealed to the cross-origin caller.
// But we can verify the error indirectly, by checking that no console
// messages are logged, which indicates that the operation did not execute.
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
window.testWorklet.run('test-operation')
)"));
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
EXPECT_EQ(0u, console_observer.messages().size());
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
histogram_tester_.ExpectBucketCount(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::
kRunNonWebVisibleCrossOriginSharedStorageDisabled,
1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletScriptDataOrigin_NetworkError_NoAllowOriginResponseHeader) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
// The module does not have the "Access-Control-Allow-Origin" response header.
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"", "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_THAT(
content::EvalJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url)),
content::EvalJsResult::ErrorIs(
testing::HasSubstr("Error: Failed to load")));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletScriptDataOrigin_NetworkError_NoCrossOriginWorkletResponseHeader) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
// The module does not have the "Shared-Storage-Cross-Origin-Worklet-Allowed"
// response header.
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
EXPECT_THAT(
content::EvalJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url)),
content::EvalJsResult::ErrorIs(
testing::HasSubstr("Error: Failed to load")));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletScriptDataOrigin_NetworkError_404) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
// nonexistent_module.js does not exist and should produce a 404 network
// error.
GURL script_url = https_server()->GetURL(
kCrossOriginHost, "/shared_storage/nonexistent_module.js");
EXPECT_THAT(
content::EvalJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url)),
content::EvalJsResult::ErrorIs(
testing::HasSubstr("Error: Failed to load")));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletScriptDataOrigin_Success) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(
MakeFilter({"Finish executing module_with_custom_header.js"}));
// The success for `createWorklet()` won't be revealed to the cross-origin
// caller definitively. But we can verify the success indirectly, by checking
// the console.
EXPECT_TRUE(
content::ExecJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url)));
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Finish executing module_with_custom_header.js",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletDefaultDataOrigin_PrefsError_PrivacySandbox) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// Disable Privacy Sandbox.
SetPrefs(/*enable_privacy_sandbox=*/false,
/*allow_third_party_cookies=*/true);
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
content::EvalJsResult result = content::EvalJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.createWorklet($1)", script_url));
EXPECT_TRUE(base::StartsWith(
result.ExtractError(), GetSharedStorageAddModuleDisabledErrorMessage()));
EXPECT_EQ(0u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
// This test shows that the correct origin is used for the
// preferences/attestation check for cross-origin worklets.
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletDefaultDataOrigin_PrefsError_SiteSettings) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
// Set a site exception blocking `script_url`.
SetSiteException(main_url_, ContentSetting::CONTENT_SETTING_BLOCK);
content::EvalJsResult result =
content::EvalJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
await sharedStorage.createWorklet($1);
})()
)",
script_url));
EXPECT_TRUE(base::StartsWith(
result.ExtractError(), GetSharedStorageAddModuleDisabledErrorMessage()));
EXPECT_EQ(0u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletDefaultDataOrigin_NoAllowOriginResponseHeader_Failure) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// The module does not have the "Access-Control-Allow-Origin" response header.
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"", "")));
EXPECT_THAT(
content::EvalJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
await sharedStorage.createWorklet($1);
})()
)",
script_url)),
content::EvalJsResult::ErrorIs(
testing::HasSubstr("Error: Failed to load")));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletDefaultDataOrigin_NoCrossOriginWorkletResponseHeader_Success) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// The module does not have the "Shared-Storage-Cross-Origin-Worklet-Allowed"
// response header, but the worklet won't need it because it will use the
// context origin as data origin.
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(
MakeFilter({"Finish executing module_with_custom_header.js"}));
EXPECT_TRUE(
content::ExecJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1);
})()
)",
script_url)));
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Finish executing module_with_custom_header.js",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletContextDataOrigin_PrefsError_PrivacySandbox) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// Disable Privacy Sandbox.
SetPrefs(/*enable_privacy_sandbox=*/false,
/*allow_third_party_cookies=*/true);
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
content::EvalJsResult result = content::EvalJs(
GetActiveWebContents(),
content::JsReplace(
"sharedStorage.createWorklet($1, {dataOrigin: 'context-origin'})",
script_url));
EXPECT_TRUE(base::StartsWith(
result.ExtractError(), GetSharedStorageAddModuleDisabledErrorMessage()));
EXPECT_EQ(0u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
// This test shows that the correct origin is used for the
// preferences/attestation check for cross-origin worklets.
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletContextDataOrigin_PrefsError_SiteSettings) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
// Set a site exception blocking `script_url`.
SetSiteException(main_url_, ContentSetting::CONTENT_SETTING_BLOCK);
content::EvalJsResult result =
content::EvalJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
await sharedStorage.createWorklet($1, {dataOrigin: 'context-origin'});
})()
)",
script_url));
EXPECT_TRUE(base::StartsWith(
result.ExtractError(), GetSharedStorageAddModuleDisabledErrorMessage()));
EXPECT_EQ(0u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletContextDataOrigin_NoAllowOriginResponseHeader_Failure) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// The module does not have the "Access-Control-Allow-Origin" response header.
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"", "")));
EXPECT_THAT(
content::EvalJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
await sharedStorage.createWorklet($1, {dataOrigin: 'context-origin'});
})()
)",
script_url)),
content::EvalJsResult::ErrorIs(
testing::HasSubstr("Error: Failed to load")));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(
SharedStorageChromeBrowserTest,
CrossOriginWorkletScript_CreateWorkletContextDataOrigin_NoCrossOriginWorkletResponseHeader_Success) {
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
// The module does not have the "Shared-Storage-Cross-Origin-Worklet-Allowed"
// response header, but the worklet won't need it because it will use the
// context origin as data origin.
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(
MakeFilter({"Finish executing module_with_custom_header.js"}));
EXPECT_TRUE(
content::ExecJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'context-origin'});
})()
)",
script_url)));
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Finish executing module_with_custom_header.js",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginScript_AddModule_PrefsError_PrivacySandbox) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
// Disable Privacy Sandbox.
SetPrefs(/*enable_privacy_sandbox=*/false,
/*allow_third_party_cookies=*/true);
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
content::EvalJsResult result = content::EvalJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url));
EXPECT_TRUE(base::StartsWith(
result.ExtractError(), GetSharedStorageAddModuleDisabledErrorMessage()));
EXPECT_EQ(0u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
// This test shows that the correct origin is used for the
// preferences/attestation check for cross-origin worklets.
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginScript_AddModule_PrefsError_SiteSettings) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kThirdOriginHost});
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
GURL iframe_url = https_server()->GetURL(kThirdOriginHost, kSimplePagePath);
content::RenderFrameHost* iframe =
CreateIframe(GetActiveWebContents()->GetPrimaryMainFrame(), iframe_url);
// Set a site exception blocking `iframe_url`.
SetSiteException(iframe_url, ContentSetting::CONTENT_SETTING_BLOCK);
content::EvalJsResult result = content::EvalJs(
iframe,
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url));
EXPECT_TRUE(base::StartsWith(
result.ExtractError(), GetSharedStorageAddModuleDisabledErrorMessage()));
EXPECT_EQ(0u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
// This test also shows that the correct origin is used for the
// preferences/attestation check for cross-origin worklets.
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginScript_AddModule_AttestationError) {
// Only the main frame site will be attested.
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
GURL iframe_url = https_server()->GetURL(kThirdOriginHost, kSimplePagePath);
content::RenderFrameHost* iframe =
CreateIframe(GetActiveWebContents()->GetPrimaryMainFrame(), iframe_url);
content::EvalJsResult result = content::EvalJs(
iframe,
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url));
EXPECT_TRUE(base::StartsWith(
result.ExtractError(), GetSharedStorageAddModuleDisabledErrorMessage()));
EXPECT_EQ(0u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginScript_AddModule_NetworkError_MissingHeader) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
// "Access-Control-Allow-Origin" header is missing.
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"", "")));
EXPECT_THAT(
content::EvalJs(GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)",
script_url)),
content::EvalJsResult::ErrorIs(
testing::HasSubstr("Error: Failed to load")));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginScript_AddModule_NetworkError_404) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
// nonexistent_module.js does not exist and should produce a 404 network
// error.
GURL script_url = https_server()->GetURL(
kCrossOriginHost, "/shared_storage/nonexistent_module.js");
EXPECT_THAT(
content::EvalJs(GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)",
script_url)),
content::EvalJsResult::ErrorIs(
testing::HasSubstr("Error: Failed to load")));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram,
blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginScript_AddModule_Success) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *", "")));
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(
MakeFilter({"Finish executing module_with_custom_header.js"}));
// The success for `addModule()` won't be revealed to the cross-origin
// caller definitively. But we can verify the success indirectly, by checking
// the console.
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Finish executing module_with_custom_header.js",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
WaitForHistograms({kErrorTypeHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginWorklet_SelectURL_Success) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(
content::ExecJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url)));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(
MakeFilter({"Finish executing 'test-url-selection-operation'"}));
// The success for `selectURL()` won't be revealed to the cross-origin
// caller definitively. But we can verify the success indirectly, by checking
// the console.
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
window.testWorklet.selectURL(
'test-url-selection-operation',
[
{
url: "fenced_frames/title0.html"
}
],
{
data: {'mockResult': 0}
}
)
)"));
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Finish executing 'test-url-selection-operation'",
base::UTF16ToUTF8(console_observer.messages()[0].message));
WaitForHistogramsWithSampleCounts({std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
CrossOriginWorklet_Run_Success) {
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(
kCrossOriginHost,
net::test_server::GetFilePathWithReplacements(
"/shared_storage/module_with_custom_header.js",
content::SharedStorageCrossOriginWorkletResponseHeaderReplacement(
"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1")));
EXPECT_TRUE(
content::ExecJs(GetActiveWebContents(), content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url)));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(MakeFilter({"Finish executing 'test-operation'"}));
// The success for `run()` won't be revealed to the cross-origin caller
// definitively. But we can verify the success indirectly, by checking the
// console.
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
window.testWorklet.run(
'test-operation',
{
data: {'customKey': 'customValue'}
}
)
)"));
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ("Finish executing 'test-operation'",
base::UTF16ToUTF8(console_observer.messages()[0].message));
WaitForHistogramsWithSampleCounts({std::make_tuple(kErrorTypeHistogram, 2)});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, DocumentTiming) {
base::test::ScopedRunLoopTimeout timeout(FROM_HERE, base::Seconds(60));
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
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');
sharedStorage.append('key2', 'value22');
sharedStorage.append('key4', 'value4');
sharedStorage.delete('key0');
sharedStorage.delete('key2');
sharedStorage.clear();
)"));
WaitForHistograms(
{kTimingDocumentSetHistogram, kTimingDocumentAppendHistogram,
kTimingDocumentDeleteHistogram, kTimingDocumentClearHistogram});
histogram_tester_.ExpectTotalCount(kTimingDocumentSetHistogram, 6);
histogram_tester_.ExpectTotalCount(kTimingDocumentAppendHistogram, 3);
histogram_tester_.ExpectTotalCount(kTimingDocumentDeleteHistogram, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentClearHistogram, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, WorkletTiming) {
base::test::ScopedRunLoopTimeout timeout(FROM_HERE, base::Seconds(60));
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(ExecuteScriptInWorklet(GetActiveWebContents(),
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');
sharedStorage.append('key2', 'value22');
sharedStorage.append('key4', 'value4');
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.get('key4'));
console.log(await sharedStorage.length());
sharedStorage.delete('key0');
sharedStorage.delete('key2');
// It's necessary to `await` this finally promise, since we are not
// using the option `keepAlive: true` in the `run()` call. The worklet
// will be closed by the browser once the worklet signals to the browser
// that the `run()` call has finished.
await sharedStorage.clear();
console.log('Finished script');
)",
"Finished script"));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentRunHistogram, kTimingWorkletSetHistogram,
kTimingWorkletAppendHistogram, kTimingWorkletGetHistogram,
kTimingWorkletLengthHistogram,
kTimingWorkletDeleteHistogram,
kTimingWorkletClearHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingWorkletSetHistogram, 6);
histogram_tester_.ExpectTotalCount(kTimingWorkletAppendHistogram, 3);
histogram_tester_.ExpectTotalCount(kTimingWorkletGetHistogram, 5);
histogram_tester_.ExpectTotalCount(kTimingWorkletLengthHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingWorkletDeleteHistogram, 2);
histogram_tester_.ExpectTotalCount(kTimingWorkletClearHistogram, 1);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
// TODO(crbug.com/382530217): Test is flaky on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_WorkletNumPerPage_Two DISABLED_WorkletNumPerPage_Two
#else
#define MAYBE_WorkletNumPerPage_Two WorkletNumPerPage_Two
#endif
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
MAYBE_WorkletNumPerPage_Two) {
base::test::ScopedRunLoopTimeout timeout(FROM_HERE, base::Seconds(60));
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
/*additional_site_hosts=*/std::vector<std::string>({kCrossOriginHost}));
content::RenderFrameHost* main_frame =
GetActiveWebContents()->GetPrimaryMainFrame();
EXPECT_TRUE(ExecuteScriptInWorklet(main_frame,
R"(
sharedStorage.set('key0', 'value0');
console.log('Finished script');
)",
"Finished script"));
content::RenderFrameHost* iframe = CreateIframe(
main_frame, https_server()->GetURL(kCrossOriginHost, kSimplePagePath));
EXPECT_TRUE(ExecuteScriptInWorklet(iframe,
R"(
sharedStorage.set('key0', 'value0');
console.log('Finished script');
)",
"Finished script"));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentRunHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 4);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 2);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 2, 1);
}
// See crbug.com/350110056. The test is flaky on multiple builders.
IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
DISABLED_WorkletNumPerPage_Three) {
base::test::ScopedRunLoopTimeout timeout(FROM_HERE, base::Seconds(60));
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3PCSettingAndAttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
/*additional_site_hosts=*/std::vector<std::string>(
{kCrossOriginHost, kThirdOriginHost}));
content::RenderFrameHost* main_frame =
GetActiveWebContents()->GetPrimaryMainFrame();
EXPECT_TRUE(ExecuteScriptInWorklet(main_frame,
R"(
sharedStorage.set('key0', 'value0');
console.log('Finished script');
)",
"Finished script",
/*use_select_url=*/true));
content::RenderFrameHost* iframe = CreateIframe(
main_frame, https_server()->GetURL(kCrossOriginHost, kSimplePagePath));
EXPECT_TRUE(ExecuteScriptInWorklet(iframe,
R"(
sharedStorage.set('key0', 'value0');
console.log('Finished script');
)",
"Finished script",
/*use_select_url=*/true));
content::RenderFrameHost* nested_iframe = CreateIframe(
iframe, https_server()->GetURL(kThirdOriginHost, kSimplePagePath));
EXPECT_TRUE(ExecuteScriptInWorklet(nested_iframe,
R"(
sharedStorage.set('key0', 'value0');
console.log('Finished script');
)",
"Finished script",
/*use_select_url=*/true));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentSelectUrlHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 6);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 3);
histogram_tester_.ExpectTotalCount(kTimingDocumentSelectUrlHistogram, 3);
// Navigate away to record `kWorkletNumPerPageHistogram`,
// `kSelectUrlCallsPerPageHistogram` histograms.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 3, 1);
histogram_tester_.ExpectUniqueSample(kSelectUrlCallsPerPageHistogram, 3, 1);
}
INSTANTIATE_TEST_SUITE_P(All,
SharedStorageChromeBrowserTest,
testing::Bool(),
[](const testing::TestParamInfo<
SharedStorageChromeBrowserTest::ParamType>& info) {
return base::StrCat({"ResolveSelectURLTo",
info.param ? "Config" : "URN"});
});
class SharedStorageFencedFrameChromeBrowserTest
: public SharedStorageChromeBrowserTestBase {
public:
SharedStorageFencedFrameChromeBrowserTest() {
base::test::TaskEnvironment task_environment;
shared_storage_feature_.InitWithFeaturesAndParameters(
/*enabled_features=*/
{{network::features::kSharedStorageAPI,
{{"SharedStorageBitBudget", base::NumberToString(kBudgetAllowed)}}}},
/*disabled_features=*/{});
fenced_frame_api_change_feature_.InitAndEnableFeature(
blink::features::kFencedFramesAPIChanges);
fenced_frame_feature_.InitAndEnableFeature(blink::features::kFencedFrames);
attestation_feature_.InitWithFeatureState(
privacy_sandbox::kEnforcePrivacySandboxAttestations,
GetEnforcementAndEnrollmentStatus() !=
EnforcementAndEnrollmentStatus::kAttestationsUnenforced);
}
~SharedStorageFencedFrameChromeBrowserTest() override = default;
bool ResolveSelectURLToConfig() const override { return true; }
EnforcementAndEnrollmentStatus GetEnforcementAndEnrollmentStatus()
const override {
return EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled;
}
void Set3rdPartyCookieAndAttestationSettingsThenNavigateToMainHostPage() {
main_url_ = https_server()->GetURL(kMainHost, kSimplePagePath);
iframe_url_ = https_server()->GetURL(kCrossOriginHost, kSimplePagePath);
new_page_url1_ = https_server()->GetURL(kThirdOriginHost, kSimplePagePath);
new_page_url2_ = https_server()->GetURL(kFourthOriginHost, kSimplePagePath);
SetThirdPartyCookieSetting(main_url_);
SetAttestationsMap(
MakeSharedStoragePrivacySandboxAttestationsMap(std::vector<GURL>(
{main_url_, iframe_url_, new_page_url1_, new_page_url2_})));
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(), main_url_));
}
content::RenderFrameHost* SelectURLAndCreateFencedFrame(
content::RenderFrameHost* render_frame_host,
bool should_add_module = true,
bool keep_alive_after_operation = true) {
if (should_add_module)
AddSimpleModule(render_frame_host);
content::WebContentsConsoleObserver run_url_op_console_observer(
GetActiveWebContents());
run_url_op_console_observer.SetFilter(
MakeFilter({"Finish executing \'test-url-selection-operation\'"}));
EXPECT_TRUE(
ExecJs(render_frame_host,
content::JsReplace("window.resolveSelectURLToConfig = $1;",
ResolveSelectURLToConfig())));
EXPECT_TRUE(ExecJs(render_frame_host,
content::JsReplace("window.keepWorklet = $1;",
keep_alive_after_operation)));
// Construct and add the `TestSelectURLFencedFrameConfigObserver` to shared
// storage worklet host manager.
content::StoragePartition* storage_partition = GetStoragePartition();
content::TestSelectURLFencedFrameConfigObserver config_observer(
storage_partition);
content::EvalJsResult run_url_op_result = EvalJs(render_frame_host, 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,
keepAlive: keepWorklet
}
);
if (resolveSelectURLToConfig &&
!(select_url_result instanceof FencedFrameConfig)) {
throw new Error('selectURL() did not return a FencedFrameConfig.');
}
return window.select_url_result;
})()
)");
EXPECT_TRUE(run_url_op_console_observer.Wait());
EXPECT_TRUE(run_url_op_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(run_url_op_result.ExtractString(), observed_urn_uuid->spec());
}
EXPECT_EQ(1u, run_url_op_console_observer.messages().size());
EXPECT_EQ(
"Finish executing \'test-url-selection-operation\'",
base::UTF16ToUTF8(run_url_op_console_observer.messages()[0].message));
return content::CreateFencedFrame(
render_frame_host,
ResolveSelectURLToConfig()
? content::FencedFrameNavigationTarget("select_url_result")
: content::FencedFrameNavigationTarget(observed_urn_uuid.value()));
}
protected:
GURL main_url_;
GURL iframe_url_;
GURL new_page_url1_;
GURL new_page_url2_;
private:
base::test::ScopedFeatureList shared_storage_feature_;
base::test::ScopedFeatureList fenced_frame_api_change_feature_;
base::test::ScopedFeatureList fenced_frame_feature_;
base::test::ScopedFeatureList attestation_feature_;
};
// TODO(https://crbug.com/396718068): Test is flaky on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_FencedFrameNavigateTop_BudgetWithdrawal DISABLED_FencedFrameNavigateTop_BudgetWithdrawal
#else
#define MAYBE_FencedFrameNavigateTop_BudgetWithdrawal FencedFrameNavigateTop_BudgetWithdrawal
#endif
IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameChromeBrowserTest,
MAYBE_FencedFrameNavigateTop_BudgetWithdrawal) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndAttestationSettingsThenNavigateToMainHostPage();
content::RenderFrameHost* iframe =
CreateIframe(GetActiveWebContents()->GetPrimaryMainFrame(), iframe_url_);
content::RenderFrameHost* fenced_frame_root_node =
SelectURLAndCreateFencedFrame(iframe);
EXPECT_DOUBLE_EQ(RemainingBudget(iframe), kBudgetAllowed);
content::TestNavigationObserver top_navigation_observer(
GetActiveWebContents());
EXPECT_TRUE(ExecJs(
fenced_frame_root_node,
content::JsReplace("window.open($1, '_unfencedTop')", new_page_url1_)));
top_navigation_observer.Wait();
content::RenderFrameHost* new_iframe =
CreateIframe(GetActiveWebContents()->GetPrimaryMainFrame(), iframe_url_);
// After the top navigation, log(3) bits should have been withdrawn from the
// original shared storage origin.
EXPECT_DOUBLE_EQ(RemainingBudget(new_iframe, /*should_add_module=*/true),
kBudgetAllowed - std::log2(3));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentSelectUrlHistogram,
kTimingDocumentRunHistogram,
kTimingRemainingBudgetHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 5);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentSelectUrlHistogram, 1);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 2);
histogram_tester_.ExpectTotalCount(kTimingRemainingBudgetHistogram, 2);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
// In the MPArch case, some additional pageloads with worklet count 0 are
// recorded, so we do not use `ExpectUniqueSample()` here.
histogram_tester_.ExpectBucketCount(kWorkletNumPerPageHistogram, 1, 2);
EXPECT_EQ(2, histogram_tester_.GetTotalSum(kWorkletNumPerPageHistogram));
}
// See crbug.com/377398196. The test is flaky on multiple builders.
IN_PROC_BROWSER_TEST_F(
SharedStorageFencedFrameChromeBrowserTest,
DISABLED_TwoFencedFrames_DifferentURNs_EachNavigateOnce_BudgetWithdrawalTwice) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
Set3rdPartyCookieAndAttestationSettingsThenNavigateToMainHostPage();
content::RenderFrameHost* iframe1 =
CreateIframe(GetActiveWebContents()->GetPrimaryMainFrame(), iframe_url_);
content::RenderFrameHost* fenced_frame_root_node1 =
SelectURLAndCreateFencedFrame(iframe1);
EXPECT_DOUBLE_EQ(RemainingBudget(iframe1), kBudgetAllowed);
content::TestNavigationObserver top_navigation_observer1(
GetActiveWebContents());
EXPECT_TRUE(ExecJs(
fenced_frame_root_node1,
content::JsReplace("window.open($1, '_unfencedTop')", new_page_url1_)));
top_navigation_observer1.Wait();
content::RenderFrameHost* iframe2 =
CreateIframe(GetActiveWebContents()->GetPrimaryMainFrame(), iframe_url_);
// After the top navigation, log(3) bits should have been withdrawn from the
// original shared storage origin.
EXPECT_DOUBLE_EQ(RemainingBudget(iframe2, /*should_add_module=*/true),
kBudgetAllowed - std::log2(3));
content::RenderFrameHost* fenced_frame_root_node2 =
SelectURLAndCreateFencedFrame(iframe2, /*should_add_module=*/false);
EXPECT_DOUBLE_EQ(RemainingBudget(iframe2), kBudgetAllowed - std::log2(3));
content::TestNavigationObserver top_navigation_observer2(
GetActiveWebContents());
EXPECT_TRUE(ExecJs(
fenced_frame_root_node2,
content::JsReplace("window.open($1, '_unfencedTop')", new_page_url2_)));
top_navigation_observer2.Wait();
content::RenderFrameHost* iframe3 =
CreateIframe(GetActiveWebContents()->GetPrimaryMainFrame(), iframe_url_);
// After the top navigation, another log(3) bits should have been withdrawn
// from the original shared storage origin.
EXPECT_DOUBLE_EQ(RemainingBudget(iframe3, /*should_add_module=*/true),
kBudgetAllowed - std::log2(3) - std::log2(3));
WaitForHistograms({kErrorTypeHistogram, kTimingDocumentAddModuleHistogram,
kTimingDocumentSelectUrlHistogram,
kTimingDocumentRunHistogram,
kTimingRemainingBudgetHistogram});
histogram_tester_.ExpectUniqueSample(
kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 9);
histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 3);
histogram_tester_.ExpectTotalCount(kTimingDocumentSelectUrlHistogram, 2);
histogram_tester_.ExpectTotalCount(kTimingDocumentRunHistogram, 4);
histogram_tester_.ExpectTotalCount(kTimingRemainingBudgetHistogram, 4);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
// In the MPArch case, some additional pageloads with worklet count 0 are
// recorded, so we do not use `ExpectUniqueSample()` here.
histogram_tester_.ExpectBucketCount(kWorkletNumPerPageHistogram, 1, 3);
EXPECT_EQ(3, histogram_tester_.GetTotalSum(kWorkletNumPerPageHistogram));
}
class SharedStoragePrivateAggregationChromeBrowserTest
: public SharedStorageChromeBrowserTestBase,
public testing::WithParamInterface<EnforcementAndEnrollmentStatus> {
public:
SharedStoragePrivateAggregationChromeBrowserTest() {
fenced_frame_api_change_feature_.InitWithFeatureState(
blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig());
fenced_frame_feature_.InitAndEnableFeature(blink::features::kFencedFrames);
attestation_feature_.InitWithFeatureState(
privacy_sandbox::kEnforcePrivacySandboxAttestations,
GetEnforcementAndEnrollmentStatus() !=
EnforcementAndEnrollmentStatus::kAttestationsUnenforced);
private_aggregation_feature_.InitAndEnableFeature(
blink::features::kPrivateAggregationApi);
}
~SharedStoragePrivateAggregationChromeBrowserTest() override = default;
bool ResolveSelectURLToConfig() const override { return true; }
EnforcementAndEnrollmentStatus GetEnforcementAndEnrollmentStatus()
const override {
return GetParam();
}
// This always enrolls the main host for Shared Storage, but only enrolls the
// main host for Private Aggregation exactly when
// `(GetEnforcementAndEnrollmentStatus() ==
// EnforcementAndEnrollmentStatus::kAttestationsEnforcedMainHostEnrolled)` is
// true.
void MaybeEnrollMainHost(const GURL& main_url) override {
privacy_sandbox::PrivacySandboxAttestationsMap attestations_map =
MakeSharedStoragePrivacySandboxAttestationsMap(
std::vector<GURL>({main_url}),
/*enroll_for_private_aggregation=*/(
GetEnforcementAndEnrollmentStatus() ==
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled));
SetAttestationsMap(attestations_map);
}
private:
base::test::ScopedFeatureList shared_storage_feature_;
base::test::ScopedFeatureList fenced_frame_api_change_feature_;
base::test::ScopedFeatureList fenced_frame_feature_;
base::test::ScopedFeatureList attestation_feature_;
base::test::ScopedFeatureList private_aggregation_feature_;
};
INSTANTIATE_TEST_SUITE_P(
All,
SharedStoragePrivateAggregationChromeBrowserTest,
testing::Values(
EnforcementAndEnrollmentStatus::kAttestationsUnenforced,
EnforcementAndEnrollmentStatus::kAttestationsEnforcedMainHostUnenrolled,
EnforcementAndEnrollmentStatus::kAttestationsEnforcedMainHostEnrolled),
[](const testing::TestParamInfo<
SharedStoragePrivateAggregationChromeBrowserTest::ParamType>& info) {
return base::StrCat(
{"Attestations",
(info.param !=
EnforcementAndEnrollmentStatus::kAttestationsUnenforced)
? base::StrCat(
{"Enforced_MainHost",
(info.param == EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled)
? "Enrolled"
: "Unenrolled"})
: "Unenforced"});
});
IN_PROC_BROWSER_TEST_P(SharedStoragePrivateAggregationChromeBrowserTest,
ContributeToHistogramViaRun) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
// This always enrolls the main host for Shared Storage, but only enrolls the
// main host for Private Aggregation exactly when `ShouldEnrollMainHost()` is
// true.
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(ExecuteScriptInWorklet(GetActiveWebContents(), R"(
privateAggregation.contributeToHistogram({bucket: 1n, value: 2});
console.log('Finished script');
)",
"Finished script"));
WaitForHistograms({kPrivateAggregationHostPipeResultHistogram});
histogram_tester_.ExpectUniqueSample(
kPrivateAggregationHostPipeResultHistogram,
SuccessExpected()
? content::GetPrivateAggregationHostPipeReportSuccessValue()
: content::GetPrivateAggregationHostPipeApiDisabledValue(),
1);
histogram_tester_.ExpectTotalCount(
kPrivateAggregationHostTimeToGenerateReportRequestWithContextIdHistogram,
0);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrivateAggregationChromeBrowserTest,
ContributeToHistogramViaSelectURL) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
// This always enrolls the main host for Shared Storage, but only enrolls the
// main host for Private Aggregation exactly when `ShouldEnrollMainHost()` is
// true.
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
EXPECT_TRUE(ExecuteScriptInWorklet(GetActiveWebContents(), R"(
privateAggregation.contributeToHistogram({bucket: 1n, value: 2});
console.log('Finished script');
return 1;
)",
"Finished script",
/*use_select_url=*/true));
WaitForHistograms({kPrivateAggregationHostPipeResultHistogram});
histogram_tester_.ExpectUniqueSample(
kPrivateAggregationHostPipeResultHistogram,
SuccessExpected()
? content::GetPrivateAggregationHostPipeReportSuccessValue()
: content::GetPrivateAggregationHostPipeApiDisabledValue(),
1);
histogram_tester_.ExpectTotalCount(
kPrivateAggregationHostTimeToGenerateReportRequestWithContextIdHistogram,
0);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
IN_PROC_BROWSER_TEST_P(SharedStoragePrivateAggregationChromeBrowserTest,
WithContextId_NoPrivateAggregationJS) {
// The test assumes pages get deleted after navigation. To ensure this,
// disable back/forward cache.
content::DisableBackForwardCacheForTesting(
GetActiveWebContents(),
content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
// This always enrolls the main host for Shared Storage, but only enrolls the
// main host for Private Aggregation exactly when `ShouldEnrollMainHost()` is
// true.
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
AddSimpleModule(GetActiveWebContents());
content::EvalJsResult result = content::EvalJs(GetActiveWebContents(), R"(
sharedStorage.run('test-operation',
{data: {},
privateAggregationConfig: {contextId:
'example_id'}});
)");
WaitForHistograms({kPrivateAggregationHostPipeResultHistogram});
histogram_tester_.ExpectUniqueSample(
kPrivateAggregationHostPipeResultHistogram,
SuccessExpected()
? content::GetPrivateAggregationHostPipeReportSuccessValue()
: content::GetPrivateAggregationHostPipeApiDisabledValue(),
1);
histogram_tester_.ExpectTotalCount(
kPrivateAggregationHostTimeToGenerateReportRequestWithContextIdHistogram,
SuccessExpected() ? 1 : 0);
// Navigate away to record `kWorkletNumPerPageHistogram` histogram.
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
GURL(url::kAboutBlankURL)));
histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
}
class FencedStorageReadBrowserTest : public SharedStoragePrefBrowserTest {
public:
FencedStorageReadBrowserTest() = default;
~FencedStorageReadBrowserTest() override = default;
void SetUpOnMainThread() override {
// Fenced frame test helper should enable the feature
// "FencedFramesLocalUnpartitionedDataAccess" in its constructor, which is
// required for the tests.
ASSERT_TRUE(base::FeatureList::IsEnabled(
blink::features::kFencedFramesLocalUnpartitionedDataAccess));
SharedStoragePrefBrowserTest::SetUpOnMainThread();
}
// This always enrolls the host for Shared Storage, but only enrolls the host
// for fenced storage read exactly when enforcement and enrollment status is
// `kAttestationsEnforcedMainHostEnrolled`.
void MaybeEnrollMainHost(const GURL& main_url) override {
auto attestations_set =
privacy_sandbox::PrivacySandboxAttestationsGatedAPISet(
{privacy_sandbox::PrivacySandboxAttestationsGatedAPI::
kSharedStorage});
if (GetEnforcementAndEnrollmentStatus() ==
EnforcementAndEnrollmentStatus::kAttestationsEnforcedMainHostEnrolled) {
attestations_set.Put(privacy_sandbox::PrivacySandboxAttestationsGatedAPI::
kFencedStorageRead);
}
privacy_sandbox::PrivacySandboxAttestationsMap attestations_map{
{net::SchemefulSite(main_url), attestations_set}};
SetAttestationsMap(attestations_map);
}
void VerifyDebugErrorMessage(const std::string& error_message) override {
ASSERT_FALSE(SuccessExpected());
size_t found_pos = error_message.find("Debug");
if (!EnableDebugMessages()) {
EXPECT_EQ(found_pos, std::string::npos);
return;
}
EXPECT_NE(found_pos, std::string::npos);
// The accessing site is always enrolled for Shared Storage. So the status
// only depends on Privacy Sandbox status.
int status = EnablePrivacySandbox() ? 4 : 1;
if (status == 4) {
ASSERT_FALSE(AllowThirdPartyCookies());
}
found_pos = error_message.find("status " + base::NumberToString(status));
EXPECT_NE(found_pos, std::string::npos);
}
content::RenderFrameHost*
CreateFencedFrameAndSet3rdPartyCookieAndFencedFrameHostAttestationSettings() {
EXPECT_TRUE(
NavigateToURL(GetActiveWebContents(),
https_server()->GetURL(kMainHost, kSimplePagePath)));
fenced_frame_url_ = https_server()->GetURL("a.test", kFencedFramePagePath);
content::RenderFrameHost* fenced_frame_rfh = content::CreateFencedFrame(
GetActiveWebContents()->GetPrimaryMainFrame(), fenced_frame_url_);
SetThirdPartyCookieSetting(fenced_frame_url_);
MaybeEnrollMainHost(fenced_frame_url_);
return fenced_frame_rfh;
}
bool SharedStorageSuccessExpected() const {
// This test suite always has the fenced frame URL enrolled for Shared
// Storage.
return EnablePrivacySandbox() && AllowThirdPartyCookies();
}
bool SuccessExpectedForFencedStorageReadWhenUntrustedNetworkAccessRevoked()
const {
return SuccessExpected();
}
GURL fenced_frame_url() const { return fenced_frame_url_; }
private:
// Enables the required features for fenced frame.
content::test::FencedFrameTestHelper fenced_frame_test_helper_;
GURL fenced_frame_url_;
};
IN_PROC_BROWSER_TEST_P(
FencedStorageReadBrowserTest,
FencedStorageReadAttestationWithoutUntrustedNetworkDisabled) {
// This always enrolls the fenced frame host for Shared Storage, but only
// enrolls the host for fenced frame fenced storage read exactly
// when `ShouldEnrollMainHost()` is true.
content::RenderFrameHost* fenced_frame_rfh =
CreateFencedFrameAndSet3rdPartyCookieAndFencedFrameHostAttestationSettings();
content::EvalJsResult set_result = content::EvalJs(fenced_frame_rfh, R"(
sharedStorage.set('customKey', 'customValue');
)");
if (!AllowThirdPartyCookies()) {
// Enable block of all third party cookies in the tracking protection
// setting.
GetProfile()->GetPrefs()->SetBoolean(prefs::kBlockAll3pcToggleEnabled,
true);
}
if (SharedStorageSuccessExpected()) {
EXPECT_TRUE(set_result.is_ok());
WaitForHistograms({kTimingDocumentSetHistogram});
histogram_tester_.ExpectTotalCount(kTimingDocumentSetHistogram, 1);
} else {
// Shared Storage will be disabled.
EXPECT_TRUE(base::StartsWith(set_result.ExtractError(),
GetSharedStorageDisabledErrorMessage()));
VerifyDebugErrorMessage(set_result.ExtractError());
}
// Set up console observer.
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(
MakeFilter({kFencedStorageReadAttestationErrorPrefix,
kFencedStorageReadDisabledBy3pcSettingError}));
// Attempt to access local unpartitioned data without revoking fenced frame
// untrusted network access.
content::EvalJsResult get_result = content::EvalJs(fenced_frame_rfh, R"(
sharedStorage.get('customKey');
)");
if (SuccessExpectedForFencedStorageReadWhenUntrustedNetworkAccessRevoked()) {
// Fenced storage read is disabled when untrusted network access is not
// revoked.
ASSERT_FALSE(get_result.is_ok());
EXPECT_TRUE(
base::StartsWith(get_result.ExtractError(),
GetFencedStorageReadWithoutRevokeNetworkMessage()));
EXPECT_TRUE(console_observer.messages().empty());
} else if (!AllowThirdPartyCookies()) {
// Fenced storage read is disabled. A JavaScript error is shown.
EXPECT_TRUE(base::StartsWith(get_result.ExtractError(),
GetFencedStorageReadDisabledMessage()));
// Fenced storage read is disabled when all third party cookies are blocked.
// Site cookie setting does not have effect.
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ(kFencedStorageReadDisabledBy3pcSettingError,
base::UTF16ToUTF8(console_observer.messages()[0].message));
} else if (GetEnforcementAndEnrollmentStatus() ==
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostUnenrolled) {
// Fenced storage read is disabled. A JavaScript error is shown.
EXPECT_TRUE(base::StartsWith(get_result.ExtractError(),
GetFencedStorageReadDisabledMessage()));
// Fenced storage read is disabled when the accessing site is not enrolled.
// A console message is shown.
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ(base::StrCat({kFencedStorageReadAttestationErrorPrefix, " ",
url::Origin::Create(fenced_frame_url()).Serialize(),
" failed."}),
base::UTF16ToUTF8(console_observer.messages()[0].message));
} else {
// Fenced storage read is disabled. A JavaScript error is shown.
ASSERT_FALSE(EnablePrivacySandbox());
EXPECT_TRUE(base::StartsWith(get_result.ExtractError(),
GetFencedStorageReadDisabledMessage()));
}
}
IN_PROC_BROWSER_TEST_P(
FencedStorageReadBrowserTest,
FencedStorageReadAttestationWithUntrustedNetworkDisabled) {
// This always enrolls the fenced frame host for Shared Storage, but only
// enrolls the host for fenced storage read exactly when
// `ShouldEnrollMainHost()` is true.
content::RenderFrameHost* fenced_frame_rfh =
CreateFencedFrameAndSet3rdPartyCookieAndFencedFrameHostAttestationSettings();
content::EvalJsResult set_result = content::EvalJs(fenced_frame_rfh, R"(
sharedStorage.set('customKey', 'customValue');
)");
if (!AllowThirdPartyCookies()) {
// Enable block of all third party cookies in the tracking protection
// setting.
GetProfile()->GetPrefs()->SetBoolean(prefs::kBlockAll3pcToggleEnabled,
true);
}
if (SharedStorageSuccessExpected()) {
EXPECT_TRUE(set_result.is_ok());
WaitForHistograms({kTimingDocumentSetHistogram});
histogram_tester_.ExpectTotalCount(kTimingDocumentSetHistogram, 1);
} else {
// Shared Storage will be disabled.
EXPECT_TRUE(base::StartsWith(set_result.ExtractError(),
GetSharedStorageDisabledErrorMessage()));
VerifyDebugErrorMessage(set_result.ExtractError());
}
// Set up console observer.
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(
MakeFilter({kFencedStorageReadAttestationErrorPrefix,
kFencedStorageReadDisabledBy3pcSettingError}));
// Disable untrusted network access.
EXPECT_TRUE(ExecJs(fenced_frame_rfh, R"(
window.fence.disableUntrustedNetwork();
)"));
// Access local unpartitioned data with fenced frame untrusted network access
// revoked.
content::EvalJsResult get_result = content::EvalJs(fenced_frame_rfh, R"(
sharedStorage.get('customKey');
)");
if (SuccessExpectedForFencedStorageReadWhenUntrustedNetworkAccessRevoked()) {
// Fenced storage read is allowed when untrusted network access is revoked.
ASSERT_TRUE(get_result.is_ok());
EXPECT_EQ(get_result.ExtractString(), "customValue");
EXPECT_TRUE(console_observer.messages().empty());
WaitForHistograms({kTimingDocumentGetHistogram});
histogram_tester_.ExpectTotalCount(kTimingDocumentGetHistogram, 1);
} else if (!AllowThirdPartyCookies()) {
// Fenced storage read is disabled. A JavaScript error is shown.
EXPECT_TRUE(base::StartsWith(get_result.ExtractError(),
GetFencedStorageReadDisabledMessage()));
// Fenced storage read is disabled when all third party cookies are blocked.
// Site cookie setting does not have effect.
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ(kFencedStorageReadDisabledBy3pcSettingError,
base::UTF16ToUTF8(console_observer.messages()[0].message));
} else if (GetEnforcementAndEnrollmentStatus() ==
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostUnenrolled) {
// Fenced storage read is disabled. A JavaScript error is shown.
EXPECT_TRUE(base::StartsWith(get_result.ExtractError(),
GetFencedStorageReadDisabledMessage()));
// Fenced storage read is disabled when the accessing site is not enrolled.
// A console message is shown.
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ(base::StrCat({kFencedStorageReadAttestationErrorPrefix, " ",
url::Origin::Create(fenced_frame_url()).Serialize(),
" failed."}),
base::UTF16ToUTF8(console_observer.messages()[0].message));
} else {
// Fenced storage read is disabled. A JavaScript
// error is shown.
ASSERT_FALSE(EnablePrivacySandbox());
EXPECT_TRUE(base::StartsWith(get_result.ExtractError(),
GetFencedStorageReadDisabledMessage()));
}
}
INSTANTIATE_TEST_SUITE_P(
All,
FencedStorageReadBrowserTest,
testing::Combine(
testing::Bool(),
testing::Bool(),
testing::Values(EnforcementAndEnrollmentStatus::kAttestationsUnenforced,
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostUnenrolled,
#if BUILDFLAG(IS_ANDROID)
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled)),
#else
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled),
testing::Bool()),
#endif
DescribePrefBrowserTestParams);
class SharedStorageHeaderPrefBrowserTest : public SharedStoragePrefBrowserTest {
public:
void FinishSetUp() override {
observer_ = content::CreateAndOverrideSharedStorageHeaderObserver(
GetStoragePartition());
}
protected:
base::WeakPtr<content::TestSharedStorageHeaderObserver> observer_;
};
INSTANTIATE_TEST_SUITE_P(
All,
SharedStorageHeaderPrefBrowserTest,
testing::Combine(
testing::Bool(),
testing::Bool(),
testing::Values(EnforcementAndEnrollmentStatus::kAttestationsUnenforced,
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostUnenrolled,
#if BUILDFLAG(IS_ANDROID)
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled)),
#else
EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled),
testing::Bool()),
#endif
DescribePrefBrowserTestParams);
IN_PROC_BROWSER_TEST_P(SharedStorageHeaderPrefBrowserTest, Basic) {
net::test_server::ControllableHttpResponse response(https_server(),
kTitle1Path);
ASSERT_TRUE(https_server()->Start());
Set3rdPartyCookieAndMainHostAttestationSettingsThenNavigateToMainHostPage();
GURL fetch_url = https_server()->GetURL(kMainHost, kTitle1Path);
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace(R"(
fetch($1, {sharedStorageWritable: true});
)",
fetch_url.spec()),
content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
response.WaitForRequest();
ASSERT_TRUE(base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Writable"));
EXPECT_EQ(response.http_request()->content, "");
response.Send(
/*http_status=*/net::HTTP_OK,
/*content_type=*/"text/plain;charset=UTF-8",
/*content=*/{}, /*cookies=*/{}, /*extra_headers=*/
{"Shared-Storage-Write: clear, "
"set;key=\"hello\";value=\"world\";ignore_if_present, "
"append;key=hello;value=there"});
ASSERT_TRUE(observer_);
if (!SuccessExpected()) {
// Shared Storage is disabled, so the `SharedStorageHeaderObserver` ignores
// the header and no operations are invoked.
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
response.Done();
return;
}
// Shared Storage is enabled.
observer_->WaitForOperations(1);
url::Origin fetch_origin = url::Origin::Create(fetch_url);
EXPECT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().front(), fetch_origin);
EXPECT_EQ(observer_->operations().size(), 1u);
std::vector<content::MethodWithOptionsPtr> methods_with_options;
methods_with_options.push_back(content::MojomClearMethod());
methods_with_options.push_back(
content::MojomSetMethod(/*key=*/u"hello", /*value=*/u"world",
/*ignore_if_present=*/true));
methods_with_options.push_back(
content::MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there"));
EXPECT_EQ(observer_->operations()[0],
content::HeaderOperationSuccess(fetch_origin,
std::move(methods_with_options)));
response.Done();
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
ExecuteScriptInWorklet(GetActiveWebContents(), R"(
console.log(await sharedStorage.get('hello'));
console.log(await sharedStorage.length());
console.log('Finished script');
)",
"Finished script");
EXPECT_EQ(5u, console_observer.messages().size());
EXPECT_EQ("Start executing customizable_module.js",
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ("Finish executing customizable_module.js",
base::UTF16ToUTF8(console_observer.messages()[1].message));
EXPECT_EQ("worldthere",
base::UTF16ToUTF8(console_observer.messages()[2].message));
EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[3].message));
EXPECT_EQ("Finished script",
base::UTF16ToUTF8(console_observer.messages()[4].message));
}
class SharedStorageChromeNoParamsBrowserTest
: public SharedStorageChromeBrowserTestBase {
public:
SharedStorageChromeNoParamsBrowserTest() {
fenced_frame_api_change_feature_.InitWithFeatureState(
blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig());
fenced_frame_feature_.InitAndEnableFeature(blink::features::kFencedFrames);
attestation_feature_.InitWithFeatureState(
privacy_sandbox::kEnforcePrivacySandboxAttestations,
GetEnforcementAndEnrollmentStatus() !=
EnforcementAndEnrollmentStatus::kAttestationsUnenforced);
}
~SharedStorageChromeNoParamsBrowserTest() override = default;
bool ResolveSelectURLToConfig() const override { return true; }
EnforcementAndEnrollmentStatus GetEnforcementAndEnrollmentStatus()
const override {
return EnforcementAndEnrollmentStatus::
kAttestationsEnforcedMainHostEnrolled;
}
private:
base::test::ScopedFeatureList fenced_frame_api_change_feature_;
base::test::ScopedFeatureList fenced_frame_feature_;
base::test::ScopedFeatureList attestation_feature_;
};
#if BUILDFLAG(ENABLE_EXTENSIONS)
namespace {
constexpr char kBackgroundJSTemplate[] = R"(
chrome.runtime.onInstalled.addListener(() => {
chrome.declarativeNetRequest.updateDynamicRules({
addRules: $1,
removeRuleIds: [1]
});
});
)";
constexpr char kModulePath[] = "/module.js";
constexpr char kModuleMessage[] = "finished adding module";
constexpr char kModuleTemplate[] = R"(
class Operation {
async run(urls, data) {
return 1;
}
}
register("operation", Operation);
console.log($1);
)";
constexpr char kLoadFailedMessagePrefix[] =
"Uncaught (in promise) OperationError: Failed to load";
} // namespace
class SharedStorageExtensionBrowserTest
: public extensions::ExtensionBrowserTest {
public:
SharedStorageExtensionBrowserTest() {
base::test::TaskEnvironment task_environment;
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/
{network::features::kSharedStorageAPI,
features::kPrivacySandboxAdsAPIsOverride,
privacy_sandbox::kOverridePrivacySandboxSettingsLocalTesting,
blink::features::kFencedFrames,
blink::features::kFencedFramesAPIChanges,
privacy_sandbox::kEnforcePrivacySandboxAttestations},
/*disabled_features=*/{});
}
~SharedStorageExtensionBrowserTest() override = default;
void SetUpOnMainThread() override {
// `PrivacySandboxAttestations` has a member of type
// `scoped_refptr<base::SequencedTaskRunner>`, its initialization must be
// done after a browser process is created.
extensions::ExtensionBrowserTest::SetUpOnMainThread();
scoped_attestations_ =
std::make_unique<privacy_sandbox::ScopedPrivacySandboxAttestations>(
privacy_sandbox::PrivacySandboxAttestations::CreateForTesting());
host_resolver()->AddRule("*", "127.0.0.1");
https_server()->AddDefaultHandlers(GetChromeTestDataDir());
https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
content::SetupCrossSiteRedirector(https_server());
// Allow 3rd-party cookies.
GetProfile()->GetPrefs()->SetInteger(
prefs::kCookieControlsMode,
static_cast<int>(content_settings::CookieControlsMode::kOff));
// Enable privacy sandbox.
auto* privacy_sandbox_settings =
PrivacySandboxSettingsFactory::GetForProfile(GetProfile());
privacy_sandbox_settings->SetAllPrivacySandboxAllowedForTesting();
auto privacy_sandbox_delegate = std::make_unique<testing::NiceMock<
privacy_sandbox_test_util::MockPrivacySandboxSettingsDelegate>>();
privacy_sandbox_delegate->SetUpIsPrivacySandboxRestrictedResponse(
/*restricted=*/false);
privacy_sandbox_delegate->SetUpIsIncognitoProfileResponse(
/*incognito=*/GetProfile()->IsIncognitoProfile());
privacy_sandbox_settings->SetDelegateForTesting(
std::move(privacy_sandbox_delegate));
}
void TearDownOnMainThread() override {
if (extension_) {
// Prevent dangling pointer.
extensions::ExtensionId id = extension_->id();
extension_ = nullptr;
UninstallExtension(id);
}
extensions::ExtensionBrowserTest::TearDownOnMainThread();
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
content::WebContents* GetActiveWebContents() {
return chrome_test_utils::GetActiveWebContents(this);
}
Profile* GetProfile() { return browser()->profile(); }
const extensions::Extension* InstallExtension(
std::optional<std::string> value_to_set) {
extensions::TestExtensionDir test_dir;
auto manifest =
base::Value::Dict()
.Set("name", "Shared Storage Header Modifier")
.Set("version", "1")
.Set("manifest_version", 3)
.Set("permissions",
base::Value::List()
.Append("declarativeNetRequest")
.Append("declarativeNetRequestWithHostAccess"))
.Set("host_permissions", base::Value::List().Append("<all_urls>"))
.Set("background",
base::Value::Dict().Set("service_worker", "background.js"));
test_dir.WriteManifest(manifest);
std::string operation = value_to_set ? "set" : "remove";
auto request_header_dict =
base::Value::Dict()
.Set("header", "Sec-Shared-Storage-Data-Origin")
.Set("operation", operation);
if (value_to_set) {
request_header_dict.Set("value", *value_to_set);
}
auto rules = base::Value::List().Append(
base::Value::Dict()
.Set("id", 1)
.Set("priority", 1)
.Set("condition",
base::Value::Dict().Set(
"resourceTypes",
base::Value::List().Append("script").Append("other")))
.Set("action", base::Value::Dict()
.Set("type", "modifyHeaders")
.Set("requestHeaders",
base::Value::List().Append(
std::move(request_header_dict)))));
std::string background_js =
content::JsReplace(kBackgroundJSTemplate, std::move(rules));
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), background_js);
extension_ = LoadExtension(test_dir.UnpackedPath(),
{.wait_for_registration_stored = true});
return extension_;
}
void AttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
const std::vector<std::string>& additional_site_hosts) {
main_url_ = https_server()->GetURL(kMainHost, kSimplePagePath);
std::vector<GURL> urls({main_url_});
for (const auto& host : additional_site_hosts) {
urls.push_back(https_server()->GetURL(host, kSimplePagePath));
}
CookieSettingsFactory::GetForProfile(GetProfile())
->SetThirdPartyCookieSetting(main_url_,
ContentSetting::CONTENT_SETTING_ALLOW);
privacy_sandbox::PrivacySandboxAttestations::GetInstance()
->SetAttestationsForTesting(
MakeSharedStoragePrivacySandboxAttestationsMap(urls));
EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(), main_url_));
}
protected:
GURL main_url_;
raw_ptr<const extensions::Extension> extension_ = nullptr;
private:
base::test::ScopedFeatureList scoped_feature_list_;
net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
std::unique_ptr<privacy_sandbox::ScopedPrivacySandboxAttestations>
scoped_attestations_;
};
IN_PROC_BROWSER_TEST_F(
SharedStorageExtensionBrowserTest,
CrossOrigin_ExtensionRemovesRequestHeader_CORSRetainsRequestHeader_NoResponseHeader_Failure) {
net::test_server::ControllableHttpResponse response(https_server(),
kModulePath);
ASSERT_TRUE(https_server()->Start());
// Install extension that will remove the "Sec-Shared-Storage-Data-Origin"
// request header.
ASSERT_TRUE(InstallExtension(/*value_to_set=*/std::nullopt));
ASSERT_TRUE(extension_registrar()->IsExtensionEnabled(extension_->id()));
AttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(kCrossOriginHost, kModulePath);
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(MakeFilter({kLoadFailedMessagePrefix}));
// Load worklet with cross-origin data origin.
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url),
content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
response.WaitForRequest();
if (base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin")) {
// There is a race condition that still sometimes prevents the extension
// from operating on the request before it is sent. Since the effect of the
// extension is needed to test the shared storage code path, we bail out
// with an early return in this case. TODO: Determine the cause and fix.
return;
}
// "Sec-Shared-Storage-Data-Origin" request header was removed by the
// extension before the request was sent to the server.
ASSERT_FALSE(base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin"));
// The "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1" response header is
// missing, while the CorsURLLoader still sees the original
// "Sec-Shared-Storage-Data-Origin" request header.
response.Send(
/*http_status=*/net::HTTP_OK,
/*content_type=*/"application/javascript;charset=UTF-8",
/*content=*/content::JsReplace(kModuleTemplate, kModuleMessage),
/*cookies=*/{}, /*extra_headers=*/
{"Access-Control-Allow-Origin: *"});
response.Done();
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages()[0].message),
testing::HasSubstr(kLoadFailedMessagePrefix));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageExtensionBrowserTest,
CrossOrigin_ExtensionRemovesRequestHeader_CORSRetainsRequestHeader_ResponseHeader_Success) {
net::test_server::ControllableHttpResponse response(https_server(),
kModulePath);
ASSERT_TRUE(https_server()->Start());
// Install extension that will remove the "Sec-Shared-Storage-Data-Origin"
// request header.
ASSERT_TRUE(InstallExtension(/*value_to_set=*/std::nullopt));
ASSERT_TRUE(extension_registrar()->IsExtensionEnabled(extension_->id()));
AttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(kCrossOriginHost, kModulePath);
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(MakeFilter({kModuleMessage}));
// Load worklet with cross-origin data origin.
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url),
content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
response.WaitForRequest();
if (base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin")) {
// There is a race condition that still sometimes prevents the extension
// from operating on the request before it is sent. Since the effect of the
// extension is needed to test the shared storage code path, we bail out
// with an early return in this case. TODO: Determine the cause and fix.
return;
}
// "Sec-Shared-Storage-Data-Origin" request header was removed by the
// extension before the request was sent to the server.
ASSERT_FALSE(base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin"));
response.Send(
/*http_status=*/net::HTTP_OK,
/*content_type=*/"application/javascript;charset=UTF-8",
/*content=*/content::JsReplace(kModuleTemplate, kModuleMessage),
/*cookies=*/{}, /*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"});
response.Done();
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ(kModuleMessage,
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageExtensionBrowserTest,
CrossOrigin_ExtensionModifiesRequestHeader_CORSRetainsRequestHeader_NoResponseHeader_Failure) {
net::test_server::ControllableHttpResponse response(https_server(),
kModulePath);
ASSERT_TRUE(https_server()->Start());
// Install extension that will modify the "Sec-Shared-Storage-Data-Origin"
// request header value to "https://google.com".
ASSERT_TRUE(InstallExtension(/*value_to_set=*/"https://google.com"));
ASSERT_TRUE(extension_registrar()->IsExtensionEnabled(extension_->id()));
AttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(kCrossOriginHost, kModulePath);
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(MakeFilter({kLoadFailedMessagePrefix}));
// Load worklet with cross-origin data origin.
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url),
content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
response.WaitForRequest();
if (!base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin") ||
response.http_request()
->headers.find("Sec-Shared-Storage-Data-Origin")
->second != "https://google.com") {
// There is a race condition that still sometimes prevents the extension
// from operating on the request before it is sent. Since the effect of the
// extension is needed to test the shared storage code path, we bail out
// with an early return in this case. TODO: Determine the cause and fix.
return;
}
// "Sec-Shared-Storage-Data-Origin" request header was modified by the
// extension before the request was sent to the server.
ASSERT_TRUE(base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin"));
ASSERT_EQ(response.http_request()
->headers.find("Sec-Shared-Storage-Data-Origin")
->second,
"https://google.com");
// The "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1" response header is
// missing, while the CorsURLLoader still sees the original
// "Sec-Shared-Storage-Data-Origin" request header.
response.Send(
/*http_status=*/net::HTTP_OK,
/*content_type=*/"application/javascript;charset=UTF-8",
/*content=*/content::JsReplace(kModuleTemplate, kModuleMessage),
/*cookies=*/{}, /*extra_headers=*/
{"Access-Control-Allow-Origin: *"});
response.Done();
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages()[0].message),
testing::HasSubstr(kLoadFailedMessagePrefix));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageExtensionBrowserTest,
CrossOrigin_ExtensionModifiesRequestHeader_CORSRetainsRequestHeader_ResponseHeader_Success) {
net::test_server::ControllableHttpResponse response(https_server(),
kModulePath);
ASSERT_TRUE(https_server()->Start());
// Install extension that will modify the "Sec-Shared-Storage-Data-Origin"
// request header value to "https://google.com".
ASSERT_TRUE(InstallExtension(/*value_to_set=*/"https://google.com"));
ASSERT_TRUE(extension_registrar()->IsExtensionEnabled(extension_->id()));
AttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage(
{kCrossOriginHost});
GURL script_url = https_server()->GetURL(kCrossOriginHost, kModulePath);
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(MakeFilter({kModuleMessage}));
// Load worklet with cross-origin data origin.
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'script-origin'});
})()
)",
script_url),
content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
response.WaitForRequest();
if (!base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin") ||
response.http_request()
->headers.find("Sec-Shared-Storage-Data-Origin")
->second != "https://google.com") {
// There is a race condition that still sometimes prevents the extension
// from operating on the request before it is sent. Since the effect of the
// extension is needed to test the shared storage code path, we bail out
// with an early return in this case. TODO: Determine the cause and fix.
return;
}
// "Sec-Shared-Storage-Data-Origin" request header was modified by the
// extension before the request was sent to the server.
ASSERT_TRUE(base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin"));
ASSERT_EQ(response.http_request()
->headers.find("Sec-Shared-Storage-Data-Origin")
->second,
"https://google.com");
response.Send(
/*http_status=*/net::HTTP_OK,
/*content_type=*/"application/javascript;charset=UTF-8",
/*content=*/content::JsReplace(kModuleTemplate, kModuleMessage),
/*cookies=*/{}, /*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"});
response.Done();
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ(kModuleMessage,
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageExtensionBrowserTest,
SameOrigin_ExtensionAddsRequestHeader_CORSRetainsRequestHeader_NoResponseHeader_Success) {
net::test_server::ControllableHttpResponse response(https_server(),
kModulePath);
ASSERT_TRUE(https_server()->Start());
std::string origin_str = https_server()->GetOrigin(kMainHost).Serialize();
// Install extension that will add the "Sec-Shared-Storage-Data-Origin"
// request header.
ASSERT_TRUE(InstallExtension(/*value_to_set=*/origin_str));
ASSERT_TRUE(extension_registrar()->IsExtensionEnabled(extension_->id()));
AttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage({});
GURL script_url = https_server()->GetURL(kMainHost, kModulePath);
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(MakeFilter({kModuleMessage}));
// Load worklet with same-origin script and data origin.
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'context-origin'});
})()
)",
script_url),
content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
response.WaitForRequest();
if (!base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin") ||
response.http_request()
->headers.find("Sec-Shared-Storage-Data-Origin")
->second != origin_str) {
// There is a race condition that still sometimes prevents the extension
// from operating on the request before it is sent. Since the effect of the
// extension is needed to test the shared storage code path, we bail out
// with an early return in this case. TODO: Determine the cause and fix.
return;
}
// "Sec-Shared-Storage-Data-Origin" request header was added by the
// extension before the request was sent to the server.
ASSERT_TRUE(base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin"));
ASSERT_EQ(response.http_request()
->headers.find("Sec-Shared-Storage-Data-Origin")
->second,
origin_str);
// The "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1" response header is
// not included, but the CorsURLLoader does not see the
// "Sec-Shared-Storage-Data-Origin" request header, so the response header is
// not needed.
response.Send(
/*http_status=*/net::HTTP_OK,
/*content_type=*/"application/javascript;charset=UTF-8",
/*content=*/content::JsReplace(kModuleTemplate, kModuleMessage),
/*cookies=*/{}, /*extra_headers=*/
{"Access-Control-Allow-Origin: *"});
response.Done();
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ(kModuleMessage,
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
}
IN_PROC_BROWSER_TEST_F(
SharedStorageExtensionBrowserTest,
SameOrigin_ExtensionAddsRequestHeader_CORSRetainsRequestHeader_ResponseHeader_Success) {
net::test_server::ControllableHttpResponse response(https_server(),
kModulePath);
ASSERT_TRUE(https_server()->Start());
std::string origin_str = https_server()->GetOrigin(kMainHost).Serialize();
// Install extension that will add the "Sec-Shared-Storage-Data-Origin"
// request header.
ASSERT_TRUE(InstallExtension(/*value_to_set=*/origin_str));
ASSERT_TRUE(extension_registrar()->IsExtensionEnabled(extension_->id()));
AttestMainHostPlusAdditionalSitesThenNavigateToMainHostPage({});
GURL script_url = https_server()->GetURL(kMainHost, kModulePath);
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
console_observer.SetFilter(MakeFilter({kModuleMessage}));
// Load worklet with same-origin script and data origin.
EXPECT_TRUE(content::ExecJs(
GetActiveWebContents(),
content::JsReplace(R"(
(async function() {
window.testWorklet = await sharedStorage.createWorklet($1,
{dataOrigin: 'context-origin'});
})()
)",
script_url),
content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
response.WaitForRequest();
if (!base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin") ||
response.http_request()
->headers.find("Sec-Shared-Storage-Data-Origin")
->second != origin_str) {
// There is a race condition that still sometimes prevents the extension
// from operating on the request before it is sent. Since the effect of the
// extension is needed to test the shared storage code path, we bail out
// with an early return in this case. TODO: Determine the cause and fix.
return;
}
// "Sec-Shared-Storage-Data-Origin" request header was added by the
// extension before the request was sent to the server.
ASSERT_TRUE(base::Contains(response.http_request()->headers,
"Sec-Shared-Storage-Data-Origin"));
ASSERT_EQ(response.http_request()
->headers.find("Sec-Shared-Storage-Data-Origin")
->second,
origin_str);
// An extraneous "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1" response
// header will not affect the outcome.
response.Send(
/*http_status=*/net::HTTP_OK,
/*content_type=*/"application/javascript;charset=UTF-8",
/*content=*/content::JsReplace(kModuleTemplate, kModuleMessage),
/*cookies=*/{}, /*extra_headers=*/
{"Access-Control-Allow-Origin: *",
"Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"});
response.Done();
ASSERT_TRUE(console_observer.Wait());
EXPECT_EQ(1u, console_observer.messages().size());
EXPECT_EQ(kModuleMessage,
base::UTF16ToUTF8(console_observer.messages()[0].message));
EXPECT_EQ(1u, content::GetAttachedSharedStorageWorkletHostsCount(
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetStoragePartition()));
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
} // namespace storage