| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <sstream> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_map.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/metrics_hashes.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/test_future.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "components/metrics/dwa/dwa_recorder.h" |
| #include "content/browser/fenced_frame/fenced_frame_config.h" |
| #include "content/browser/renderer_host/navigation_request.h" |
| #include "content/browser/shared_storage/shared_storage_browsertest_base.h" |
| #include "content/browser/shared_storage/shared_storage_document_service_impl.h" |
| #include "content/browser/shared_storage/shared_storage_event_params.h" |
| #include "content/browser/shared_storage/shared_storage_runtime_manager.h" |
| #include "content/browser/shared_storage/shared_storage_worklet_host.h" |
| #include "content/browser/shared_storage/test_shared_storage_observer.h" |
| #include "content/browser/shared_storage/test_shared_storage_runtime_manager.h" |
| #include "content/browser/shared_storage/test_shared_storage_worklet_host.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/back_forward_cache.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/document_user_data.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/network_service_util.h" |
| #include "content/public/common/page_type.h" |
| #include "content/public/test/back_forward_cache_util.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/fenced_frame_test_util.h" |
| #include "content/public/test/shared_storage_test_utils.h" |
| #include "content/public/test/test_devtools_protocol_client.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_select_url_fenced_frame_config_observer.h" |
| #include "content/public/test/test_shared_storage_header_observer.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "content/test/fenced_frame_test_utils.h" |
| #include "mojo/public/cpp/base/big_buffer.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/http/http_status_code.h" |
| #include "net/test/embedded_test_server/controllable_http_response.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "net/test/embedded_test_server/request_handler_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/chrome_debug_urls.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/features_generated.h" |
| #include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h" |
| #include "third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h" |
| #include "third_party/blink/public/common/messaging/cloneable_message.h" |
| #include "third_party/blink/public/common/shared_storage/shared_storage_utils.h" |
| #include "third_party/blink/public/mojom/devtools/console_message.mojom.h" |
| #include "third_party/blink/public/mojom/loader/code_cache.mojom.h" |
| #include "third_party/blink/public/mojom/shared_storage/shared_storage.mojom.h" |
| #include "ui/base/page_transition_types.h" |
| #include "url/url_constants.h" |
| |
| namespace content { |
| |
| using testing::ElementsAre; |
| using testing::Pair; |
| using testing::UnorderedElementsAre; |
| using SharedStorageReportingMap = base::flat_map<std::string, ::GURL>; |
| using SharedStorageUrlSpecWithMetadata = |
| SharedStorageEventParams::SharedStorageUrlSpecWithMetadata; |
| using AccessScope = blink::SharedStorageAccessScope; |
| using AccessMethod = TestSharedStorageObserver::AccessMethod; |
| |
| namespace { |
| |
| using WorkletHosts = SharedStorageRuntimeManager::WorkletHosts; |
| |
| constexpr char kSharedStorageWorkletExpiredMessage[] = |
| "The sharedStorage worklet cannot execute further operations because the " |
| "previous operation did not include the option \'keepAlive: true\'."; |
| |
| constexpr char kTitle1Path[] = "/title1.html"; |
| |
| constexpr char kTitle2Path[] = "/title2.html"; |
| |
| constexpr char kTitle3Path[] = "/title3.html"; |
| |
| constexpr char kTitle4Path[] = "/title4.html"; |
| |
| constexpr char kPngPath[] = "/shared_storage/pixel.png"; |
| |
| constexpr char kSharedStorageTrustedOriginsPath[] = |
| "/.well-known/shared-storage/trusted-origins"; |
| |
| constexpr char kDestroyedStatusHistogram[] = |
| "Storage.SharedStorage.Worklet.DestroyedStatus"; |
| |
| constexpr char kTimingKeepAliveDurationHistogram[] = |
| "Storage.SharedStorage.Worklet.Timing." |
| "KeepAliveEndedDueToOperationsFinished.KeepAliveDuration"; |
| |
| constexpr char kErrorTypeHistogram[] = |
| "Storage.SharedStorage.Worklet.Error.Type"; |
| |
| constexpr char kTimingUsefulResourceHistogram[] = |
| "Storage.SharedStorage.Worklet.Timing.UsefulResourceDuration"; |
| |
| constexpr char kTimingRunExecutedInWorkletHistogram[] = |
| "Storage.SharedStorage.Document.Timing.Run.ExecutedInWorklet"; |
| |
| constexpr char kEmptyAccessControlAllowOriginReplacement[] = ""; |
| |
| constexpr char kEmptySharedStorageCrossOriginAllowedReplacement[] = ""; |
| |
| std::string ReplacePortInString(std::string str, uint16_t port) { |
| const std::string kToReplace("{{port}}"); |
| size_t index = str.find(kToReplace); |
| while (index != std::string::npos) { |
| str = str.replace(index, kToReplace.size(), base::NumberToString(port)); |
| index = str.find(kToReplace); |
| } |
| return str; |
| } |
| |
| auto describe_param = [](const auto& info) { |
| return base::StrCat({"ResolveSelectURLTo", info.param ? "Config" : "URN"}); |
| }; |
| |
| FrameTreeNode* PrimaryFrameTreeNodeRootFromShell(Shell* shell) { |
| return static_cast<WebContentsImpl*>(shell->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| } |
| |
| bool IsLocalRoot(RenderFrameHost* rfh) { |
| CHECK(rfh); |
| return static_cast<RenderFrameHostImpl*>(rfh)->is_local_root(); |
| } |
| |
| std::string ValueToString(const base::Value* value) { |
| if (!value) { |
| return "[[NULL]]"; |
| } |
| switch (value->type()) { |
| case base::Value::Type::STRING: |
| return value->GetString(); |
| case base::Value::Type::NONE: |
| return "[[NONE]]"; |
| case base::Value::Type::BOOLEAN: |
| return value->GetBool() ? "true" : "false"; |
| case base::Value::Type::INTEGER: |
| return base::NumberToString(value->GetInt()); |
| case base::Value::Type::DOUBLE: |
| return base::NumberToString(value->GetDouble()); |
| case base::Value::Type::BINARY: { |
| const std::vector<uint8_t>& value_blob = value->GetBlob(); |
| return std::string(value_blob.begin(), value_blob.end()); |
| } |
| case base::Value::Type::LIST: |
| return base::WriteJson(value->GetList()).value_or("[[LIST]]"); |
| case base::Value::Type::DICT: |
| return base::WriteJson(value->GetDict()).value_or("[[DICT]]"); |
| } |
| NOTREACHED(); |
| } |
| |
| std::string SerializeVectorOfMapOfStrings( |
| const std::vector<std::map<std::string, std::string>>& input_vector) { |
| std::ostringstream oss; |
| oss << "["; |
| if (!input_vector.empty()) { |
| oss << "\n"; |
| } |
| for (const auto& input_map : input_vector) { |
| oss << " {"; |
| for (const auto& map_pair : input_map) { |
| oss << " {" << map_pair.first << ", " << map_pair.second << "} "; |
| } |
| oss << "}\n"; |
| } |
| oss << "]"; |
| return oss.str(); |
| } |
| |
| class TestSharedStorageDevToolsClient : public TestDevToolsProtocolClient { |
| public: |
| explicit TestSharedStorageDevToolsClient(RenderFrameHost* rfh) { |
| AttachToFrameTreeHost(rfh); |
| SendCommandSync("Storage.setSharedStorageTracking", |
| base::Value::Dict().Set("enable", true)); |
| } |
| ~TestSharedStorageDevToolsClient() override { DetachProtocolClient(); } |
| |
| void set_expected_notification_method( |
| const std::string& expected_notification_method) { |
| expected_notification_method_ = expected_notification_method; |
| } |
| |
| void DispatchProtocolMessage(DevToolsAgentHost* agent_host, |
| base::span<const uint8_t> message) override { |
| std::string_view message_str(reinterpret_cast<const char*>(message.data()), |
| message.size()); |
| base::Value parsed = *base::JSONReader::Read(message_str); |
| std::optional<int> id = parsed.GetDict().FindInt("id"); |
| if (!id) { |
| const std::string* notification = parsed.GetDict().FindString("method"); |
| ASSERT_TRUE(notification); |
| if (expected_notification_method_ == *notification) { |
| base::Value* params = parsed.GetDict().Find("params"); |
| ASSERT_TRUE(params); |
| params_for_notifications_with_expected_method_.push_back( |
| std::move(*params).TakeDict()); |
| } |
| } |
| |
| TestDevToolsProtocolClient::DispatchProtocolMessage(agent_host, |
| std::move(message)); |
| } |
| |
| std::vector<std::map<std::string, std::string>> |
| GetSelectedParamsAsStringsForNotificationsWithExpectedMethod( |
| std::vector<std::string> selected_key_paths) const { |
| std::sort(selected_key_paths.begin(), selected_key_paths.end()); |
| std::vector<std::map<std::string, std::string>> |
| selected_params_for_notifications_; |
| for (const base::Value::Dict& params : |
| params_for_notifications_with_expected_method_) { |
| selected_params_for_notifications_.push_back( |
| std::map<std::string, std::string>()); |
| for (const std::string& path : selected_key_paths) { |
| const base::Value* param = params.FindByDottedPath(path); |
| if (param) { |
| selected_params_for_notifications_.back().emplace( |
| path, ValueToString(param)); |
| } |
| } |
| } |
| return selected_params_for_notifications_; |
| } |
| |
| private: |
| std::string expected_notification_method_; |
| std::vector<base::Value::Dict> params_for_notifications_with_expected_method_; |
| }; |
| |
| } // namespace |
| |
| |
| class SharedStorageTrustedOriginsResponse |
| : public net::test_server::BasicHttpResponse { |
| public: |
| SharedStorageTrustedOriginsResponse( |
| const base::Value* json_trusted_origins_list, |
| uint16_t port, |
| bool force_server_error) |
| : force_server_error_(force_server_error) { |
| if (json_trusted_origins_list) { |
| json_trusted_origins_list_str_ = |
| base::WriteJson(*json_trusted_origins_list); |
| if (json_trusted_origins_list_str_) { |
| json_trusted_origins_list_str_ = |
| ReplacePortInString(*json_trusted_origins_list_str_, port); |
| } |
| } |
| } |
| |
| SharedStorageTrustedOriginsResponse( |
| const SharedStorageTrustedOriginsResponse&) = delete; |
| SharedStorageTrustedOriginsResponse& operator=( |
| const SharedStorageTrustedOriginsResponse&) = delete; |
| |
| ~SharedStorageTrustedOriginsResponse() override = default; |
| |
| void SendResponse( |
| base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) override { |
| if (!json_trusted_origins_list_str_ || force_server_error_) { |
| set_code(net::HttpStatusCode::HTTP_INTERNAL_SERVER_ERROR); |
| set_content_type("text/plain"); |
| set_content(""); |
| } else { |
| set_code(net::HttpStatusCode::HTTP_OK); |
| set_content_type("application/json"); |
| AddCustomHeader("Access-Control-Allow-Origin", "*"); |
| set_content(*json_trusted_origins_list_str_); |
| } |
| delegate->SendResponseHeaders(code(), GetHttpReasonPhrase(code()), |
| BuildHeaders()); |
| delegate->SendContents(content(), base::DoNothing()); |
| } |
| |
| private: |
| bool force_server_error_; |
| std::optional<std::string> json_trusted_origins_list_str_; |
| }; |
| |
| class SharedStorageBrowserTest : public SharedStorageBrowserTestBase, |
| public testing::WithParamInterface<bool> { |
| public: |
| SharedStorageBrowserTest() { |
| fenced_frame_api_change_feature_.InitWithFeatureState( |
| blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig()); |
| custom_data_origin_feature_.InitAndEnableFeature( |
| blink::features::kSharedStorageCreateWorkletCustomDataOrigin); |
| dwa_feature_.InitAndEnableFeature(metrics::dwa::kDwaFeature); |
| } |
| |
| bool ResolveSelectURLToConfig() override { return GetParam(); } |
| |
| mojo_base::BigBuffer GetCodeCacheDataForUrl(RenderFrameHost* rfh, |
| const GURL& url) { |
| mojo::PendingRemote<blink::mojom::CodeCacheHost> pending_code_cache_host; |
| RenderFrameHostImpl::From(rfh)->CreateCodeCacheHost( |
| pending_code_cache_host.InitWithNewPipeAndPassReceiver()); |
| |
| mojo::Remote<blink::mojom::CodeCacheHost> inspecting_code_cache_host( |
| std::move(pending_code_cache_host)); |
| |
| base::test::TestFuture<base::Time, mojo_base::BigBuffer> code_cache_future; |
| inspecting_code_cache_host->FetchCachedCode( |
| blink::mojom::CodeCacheType::kJavascript, url, |
| code_cache_future.GetCallback()); |
| |
| auto [response_time, data] = code_cache_future.Take(); |
| return std::move(data); |
| } |
| |
| ~SharedStorageBrowserTest() override = default; |
| |
| void set_trusted_origins_list_index(size_t trusted_origins_list_index) { |
| trusted_origins_list_index_ = trusted_origins_list_index; |
| } |
| |
| void set_force_server_error(bool force_server_error) { |
| force_server_error_ = force_server_error; |
| } |
| |
| void RegisterCustomRequestHandlers() override { |
| RegisterSharedStorageTrustedOriginsRequestHandler(); |
| } |
| |
| void RegisterSharedStorageTrustedOriginsRequestHandler() { |
| https_server()->RegisterRequestHandler(base::BindRepeating( |
| &SharedStorageBrowserTest::HandleSharedStorageTrustedOriginsRequest, |
| base::Unretained(this), BuildWellKnownTrustedOriginsLists())); |
| } |
| |
| // Virtual so that a derived class can build more a realistic vector of |
| // values. We use base::Value instead of base::Value::List, even though a |
| // correctly formatted entry would be a base::Value::List, so that a derived |
| // class can test the parse error that would happen if the JSON returned isn't |
| // a list. |
| // |
| // SharedStorageBrowserTest::BuildWellKnownTrustedOriginsLists builds a vector |
| // with a single list entry that simply allowlists all combinations of script |
| // and context origins. |
| virtual std::vector<base::Value> BuildWellKnownTrustedOriginsLists() { |
| std::vector<base::Value> trusted_origins_lists; |
| trusted_origins_lists.push_back(static_cast<base::Value>( |
| base::Value::List().Append(base::Value::Dict() |
| .Set("scriptOrigin", "*") |
| .Set("contextOrigin", "*")))); |
| return trusted_origins_lists; |
| } |
| |
| std::unique_ptr<net::test_server::HttpResponse> |
| HandleSharedStorageTrustedOriginsRequest( |
| const std::vector<base::Value>& json_well_known_trusted_origin_lists, |
| const net::test_server::HttpRequest& request) { |
| const auto& path = request.GetURL().path(); |
| if (path != kSharedStorageTrustedOriginsPath || |
| json_well_known_trusted_origin_lists.empty()) { |
| return nullptr; |
| } |
| size_t index = trusted_origins_list_index_ % |
| json_well_known_trusted_origin_lists.size(); |
| return std::make_unique<SharedStorageTrustedOriginsResponse>( |
| &json_well_known_trusted_origin_lists[index], port(), |
| force_server_error_); |
| } |
| |
| private: |
| base::test::ScopedFeatureList fenced_frame_api_change_feature_; |
| base::test::ScopedFeatureList custom_data_origin_feature_; |
| base::test::ScopedFeatureList dwa_feature_; |
| size_t trusted_origins_list_index_ = 0; |
| bool force_server_error_ = false; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, AddModule_Success) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // Navigate again to record histograms. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| WaitForHistograms( |
| {kDestroyedStatusHistogram, kTimingUsefulResourceHistogram}); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 1); |
| histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1); |
| |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| url::Origin::Create(url).Serialize(), |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, AddModule_ScriptNotFound) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| std::string expected_error = base::StrCat( |
| {"a JavaScript error: \"OperationError: Failed to load ", |
| https_server() |
| ->GetURL("a.test", "/shared_storage/nonexistent_module.js") |
| .spec(), |
| " HTTP status = 404 Not Found.\"\n"}); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/nonexistent_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(expected_error)); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(0u, console_observer.messages().size()); |
| |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| url::Origin::Create(url).Serialize(), |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/nonexistent_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, AddModule_RedirectNotAllowed) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| std::string expected_error = base::StrCat( |
| {"a JavaScript error: \"OperationError: Unexpected redirect on ", |
| https_server() |
| ->GetURL("a.test", |
| "/server-redirect?shared_storage/simple_module.js") |
| .spec(), |
| ".\"\n"}); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule( |
| '/server-redirect?shared_storage/simple_module.js'); |
| )"); |
| |
| EXPECT_THAT(result, content::EvalJsResult::ErrorIs(expected_error)); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(0u, console_observer.messages().size()); |
| |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| url::Origin::Create(url).Serialize(), |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL( |
| "a.test", "/server-redirect?shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| AddModule_ScriptExecutionFailure) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/erroneous_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "ReferenceError: undefinedVariable is not defined"))); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing erroneous_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| url::Origin::Create(url).Serialize(), |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/erroneous_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| AddModule_MultipleAddModuleFailure) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "addModule() can only be invoked once per worklet"))); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // Navigate again to record histograms. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| WaitForHistograms( |
| {kDestroyedStatusHistogram, kTimingUsefulResourceHistogram}); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 1); |
| histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| AddModue_TheThirdTimeCompilesWithV8CodeCache) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| GURL module_url = https_server()->GetURL( |
| "a.test", "/shared_storage/large_cacheable_script.js"); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| // Initially, the code cache has no data. |
| mojo_base::BigBuffer code_cache_data0 = GetCodeCacheDataForUrl( |
| shell()->web_contents()->GetPrimaryMainFrame(), module_url); |
| EXPECT_EQ(code_cache_data0.size(), 0u); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule( |
| 'shared_storage/large_cacheable_script.js'); |
| )")); |
| |
| mojo_base::BigBuffer code_cache_data1 = GetCodeCacheDataForUrl( |
| shell()->web_contents()->GetPrimaryMainFrame(), module_url); |
| EXPECT_GT(code_cache_data1.size(), 0u); |
| |
| // After the first script loading, the code cache has some data. |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule( |
| 'shared_storage/large_cacheable_script.js'); |
| )")); |
| |
| // After the second script loading, the code cache has more data. This implies |
| // that the code cache wasn't used for the second compilation. This is |
| // expected, as we won't store the cached code entirely for first seen URLs. |
| mojo_base::BigBuffer code_cache_data2 = GetCodeCacheDataForUrl( |
| shell()->web_contents()->GetPrimaryMainFrame(), module_url); |
| EXPECT_GT(code_cache_data2.size(), code_cache_data1.size()); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule( |
| 'shared_storage/large_cacheable_script.js'); |
| )")); |
| |
| // After the third script loading, the code cache does not change. This |
| // implies that the code cache was used for the third compilation. |
| mojo_base::BigBuffer code_cache_data3 = GetCodeCacheDataForUrl( |
| shell()->web_contents()->GetPrimaryMainFrame(), module_url); |
| EXPECT_EQ(code_cache_data3.size(), code_cache_data2.size()); |
| EXPECT_TRUE(std::equal(code_cache_data3.begin(), code_cache_data3.end(), |
| code_cache_data2.begin())); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, RunOperation_Success) { |
| metrics::dwa::DwaRecorder::Get()->EnableRecording(); |
| metrics::dwa::DwaRecorder::Get()->Purge(); |
| ASSERT_THAT(metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting(), |
| testing::IsEmpty()); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("{\"customKey\":\"customValue\"}", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| const auto& entries = |
| metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting(); |
| EXPECT_EQ(entries.size(), 1u); |
| EXPECT_THAT(entries[0]->event_hash, |
| base::HashMetricName("SharedStorage.RunFinishedInWorklet")); |
| EXPECT_THAT( |
| entries[0]->content_hash, |
| base::HashMetricName( |
| net::registry_controlled_domains::GetDomainAndRegistry( |
| url, |
| net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES))); |
| EXPECT_EQ(entries[0]->metrics.size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}}); |
| |
| ExpectOperationFinishedInfosObserved( |
| {{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0, |
| GetFirstWorkletHostDevToolsToken(), MainFrameId(), origin_str}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| RunOperation_Failure_RunOperationBeforeAddModule) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_THAT( |
| EvalJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}, |
| keepAlive: true}); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "sharedStorage.worklet.addModule() has to be called before run()"))); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| content::FetchHistogramsFromChildProcesses(); |
| |
| // Navigate to terminate the worklet. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 1); |
| histogram_tester_.ExpectBucketCount( |
| kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1); |
| histogram_tester_.ExpectBucketCount( |
| kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kRunWebVisible, |
| 1); |
| histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| RunOperation_Failure_InvalidOptionsArgument) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| function testFunction() {} |
| |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': testFunction}}); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "function testFunction() {} could not be cloned"))); |
| |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 0); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| RunOperation_VerifyUndefinedData) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('test-operation', /*options=*/{}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| RunOperation_Failure_BlobDataTypeNotSupportedInWorklet) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| const blob = new Blob(["abc"], {type: 'text/plain'}); |
| sharedStorage.run('test-operation', /*options=*/{data: blob}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("Cannot deserialize data.", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| RunOperation_VerifyCryptoKeyData) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| const myPromise = new Promise((resolve, reject) => { |
| crypto.subtle.generateKey( |
| { |
| name: "AES-GCM", |
| length: 256, |
| }, |
| true, |
| ["encrypt", "decrypt"] |
| ).then((key) => { |
| sharedStorage.run('test-operation', /*options=*/{data: key}) |
| .then(() => { resolve(); }); |
| }); |
| }); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ( |
| "CryptoKey, algorithm: {\"length\":256,\"name\":\"AES-GCM\"} usages: " |
| "[\"encrypt\",\"decrypt\"] extractable: true", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| RunOperation_Failure_ErrorInRunOperation) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule( |
| 'shared_storage/erroneous_function_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing erroneous_function_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[0].log_level); |
| EXPECT_EQ("Finish executing erroneous_function_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[0].log_level); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[2].log_level); |
| EXPECT_THAT( |
| base::UTF16ToUTF8(console_observer.messages()[3].message), |
| testing::HasSubstr("ReferenceError: undefinedVariable is not defined")); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError, |
| console_observer.messages()[3].log_level); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL( |
| "a.test", "/shared_storage/erroneous_function_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}}); |
| |
| ExpectOperationFinishedInfosObserved( |
| {{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0, |
| GetFirstWorkletHostDevToolsToken(), MainFrameId(), origin_str}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| RunOperation_SecondRunOperationAfterKeepAliveTrueRun_Success) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}, |
| keepAlive: true}); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("{\"customKey\":\"customValue\"}", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(8u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[5].message)); |
| EXPECT_EQ("{\"customKey\":\"customValue\"}", |
| base::UTF16ToUTF8(console_observer.messages()[6].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[7].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 2); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/1, /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}}); |
| |
| ExpectOperationFinishedInfosObserved( |
| {{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0, |
| GetFirstWorkletHostDevToolsToken(), MainFrameId(), origin_str}, |
| {base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/1, |
| GetFirstWorkletHostDevToolsToken(), MainFrameId(), origin_str}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| RunOperation_SecondRunOperationAfterKeepAliveFalseRun_Failure) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}, |
| keepAlive: false}); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("{\"customKey\":\"customValue\"}", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )"), |
| EvalJsResult::ErrorIs( |
| testing::HasSubstr(kSharedStorageWorkletExpiredMessage))); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| RunOperation_SecondRunOperationAfterKeepAliveDefaultRun_Failure) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("{\"customKey\":\"customValue\"}", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )"), |
| EvalJsResult::ErrorIs( |
| testing::HasSubstr(kSharedStorageWorkletExpiredMessage))); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, WorkletDestroyed) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| WaitForHistograms( |
| {kDestroyedStatusHistogram, kTimingUsefulResourceHistogram}); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 1); |
| histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1); |
| |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| url::Origin::Create(url).Serialize(), |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, TwoWorklets) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kPageWithBlankIframePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| RenderFrameHost* iframe = |
| PrimaryFrameTreeNodeRoot()->child_at(0)->current_frame_host(); |
| |
| EXPECT_TRUE(ExecJs(iframe, R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module2.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(2u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| NavigateIframeToURL(shell()->web_contents(), "test_iframe", |
| GURL(url::kAboutBlankURL)); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("Executing simple_module2.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| // Navigate again to record histograms. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| WaitForHistograms( |
| {kDestroyedStatusHistogram, kTimingUsefulResourceHistogram}); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 2); |
| histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 2); |
| |
| std::map<int, base::UnguessableToken>& cached_worklet_devtools_tokens = |
| GetCachedWorkletHostDevToolsTokens(); |
| EXPECT_EQ(cached_worklet_devtools_tokens.size(), 2u); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module2.js"), |
| /*worklet_ordinal=*/0, cached_worklet_devtools_tokens[0])}, |
| {AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/1, cached_worklet_devtools_tokens[1])}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| KeepAlive_StartBeforeAddModuleComplete_EndAfterAddModuleComplete) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| test_runtime_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )", |
| EXECUTE_SCRIPT_NO_RESOLVE_PROMISES); |
| |
| // Navigate to trigger keep-alive |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| test_runtime_manager().GetKeepAliveWorkletHost()->WaitForAddModule(); |
| |
| // Three pending messages are expected: two for console.log and one for |
| // `addModule()` response. |
| EXPECT_EQ(3u, test_runtime_manager() |
| .GetKeepAliveWorkletHost() |
| ->pending_worklet_messages() |
| .size()); |
| |
| // Execute all the deferred messages. This will terminate the keep-alive. |
| test_runtime_manager() |
| .GetKeepAliveWorkletHost() |
| ->ExecutePendingWorkletMessages(); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Expect no console logging, as messages logged during keep-alive are |
| // dropped. |
| EXPECT_EQ(0u, console_observer.messages().size()); |
| |
| WaitForHistograms({kDestroyedStatusHistogram, kTimingUsefulResourceHistogram, |
| kTimingKeepAliveDurationHistogram}); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus:: |
| kKeepAliveEndedDueToOperationsFinished, |
| 1); |
| histogram_tester_.ExpectTotalCount(kTimingKeepAliveDurationHistogram, 1); |
| histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1); |
| |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| url::Origin::Create(url).Serialize(), |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| KeepAlive_StartBeforeAddModuleComplete_EndAfterTimeout) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| test_runtime_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )", |
| EXECUTE_SCRIPT_NO_RESOLVE_PROMISES); |
| |
| // Navigate to trigger keep-alive |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| test_runtime_manager().GetKeepAliveWorkletHost()->WaitForAddModule(); |
| |
| // Three pending messages are expected: two for console.log and one for |
| // `addModule()` response. |
| EXPECT_EQ(3u, test_runtime_manager() |
| .GetKeepAliveWorkletHost() |
| ->pending_worklet_messages() |
| .size()); |
| |
| // Fire the keep-alive timer. This will terminate the keep-alive. |
| test_runtime_manager().GetKeepAliveWorkletHost()->FireKeepAliveTimerNow(); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| WaitForHistograms( |
| {kDestroyedStatusHistogram, kTimingUsefulResourceHistogram}); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus::kKeepAliveEndedDueToTimeout, |
| 1); |
| histogram_tester_.ExpectTotalCount(kTimingKeepAliveDurationHistogram, 0); |
| histogram_tester_.ExpectUniqueSample(kTimingUsefulResourceHistogram, 100, 1); |
| |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| url::Origin::Create(url).Serialize(), |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| KeepAlive_StartBeforeRunOperationComplete_EndAfterRunOperationComplete) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| |
| // Configure the worklet host to defer processing the subsequent `run()` |
| // response. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->set_should_defer_worklet_messages(true); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}) |
| )")); |
| |
| // Navigate to trigger keep-alive |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| test_runtime_manager().GetKeepAliveWorkletHost()->WaitForWorkletResponses(); |
| |
| // Four pending messages are expected: three for console.log and one for |
| // `run()` response. |
| EXPECT_EQ(4u, test_runtime_manager() |
| .GetKeepAliveWorkletHost() |
| ->pending_worklet_messages() |
| .size()); |
| |
| // Execute all the deferred messages. This will terminate the keep-alive. |
| test_runtime_manager() |
| .GetKeepAliveWorkletHost() |
| ->ExecutePendingWorkletMessages(); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Expect no more console logging, as messages logged during keep-alive was |
| // dropped. |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| |
| WaitForHistograms({kDestroyedStatusHistogram, kTimingUsefulResourceHistogram, |
| kTimingKeepAliveDurationHistogram, |
| kTimingRunExecutedInWorkletHistogram}); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus:: |
| kKeepAliveEndedDueToOperationsFinished, |
| 1); |
| histogram_tester_.ExpectTotalCount(kTimingKeepAliveDurationHistogram, 1); |
| histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}}); |
| |
| ExpectOperationFinishedInfosObserved( |
| {{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0, |
| GetFirstWorkletHostDevToolsToken(), |
| /*main_frame_id=*/GlobalRenderFrameHostId(), origin_str}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| KeepAlive_StartBeforeSelectURLComplete_EndAfterSelectURLComplete) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| // Configure the worklet host to defer processing the subsequent `selectURL()` |
| // response. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->set_should_defer_worklet_messages(true); |
| |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| {url: 'fenced_frames/title0.html'}, |
| {url: 'fenced_frames/title1.html'} |
| ], |
| { |
| data: {'mockResult': 1}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result.is_ok()); |
| |
| // Navigate to trigger keep-alive |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| test_runtime_manager().GetKeepAliveWorkletHost()->WaitForWorkletResponses(); |
| |
| // Four pending messages are expected: four for console.log and one for |
| // `selectURL()` response. |
| EXPECT_EQ(5u, test_runtime_manager() |
| .GetKeepAliveWorkletHost() |
| ->pending_worklet_messages() |
| .size()); |
| |
| // Execute all the deferred messages. This will terminate the keep-alive. |
| test_runtime_manager() |
| .GetKeepAliveWorkletHost() |
| ->ExecutePendingWorkletMessages(); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Expect no more console logging, as messages logged during keep-alive was |
| // dropped. |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| |
| WaitForHistograms({kDestroyedStatusHistogram, kTimingUsefulResourceHistogram, |
| kTimingKeepAliveDurationHistogram, |
| kTimingSelectUrlExecutedInWorkletHistogram, |
| kErrorTypeHistogram}); |
| |
| histogram_tester_.ExpectBucketCount( |
| kErrorTypeHistogram, blink::SharedStorageWorkletErrorType::kSuccess, 1); |
| |
| // Since we have navigated away from the page that called `selectURL()`, we |
| // can't do anything with the resulting Fenced Frame config. So we log this |
| // outcome as a web-visible "error". |
| histogram_tester_.ExpectBucketCount( |
| kErrorTypeHistogram, |
| blink::SharedStorageWorkletErrorType::kSelectURLWebVisible, 1); |
| |
| histogram_tester_.ExpectUniqueSample( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus:: |
| kKeepAliveEndedDueToOperationsFinished, |
| 1); |
| histogram_tester_.ExpectTotalCount(kTimingKeepAliveDurationHistogram, 1); |
| histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 1); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {}}, |
| {https_server()->GetURL("a.test", |
| "/fenced_frames/title1.html"), |
| {}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}}); |
| |
| ExpectOperationFinishedInfosObserved( |
| {{base::TimeDelta(), AccessMethod::kSelectURL, /*operation_id=*/0, |
| GetFirstWorkletHostDevToolsToken(), |
| /*main_frame_id=*/GlobalRenderFrameHostId(), origin_str}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, KeepAlive_SubframeWorklet) { |
| // The test assumes pages get deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kPageWithBlankIframePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| // Configure the worklet host for the subframe to defer worklet responses. |
| test_runtime_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true); |
| |
| RenderFrameHost* iframe = |
| PrimaryFrameTreeNodeRoot()->child_at(0)->current_frame_host(); |
| |
| EvalJsResult result = EvalJs(iframe, R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )", |
| EXECUTE_SCRIPT_NO_RESOLVE_PROMISES); |
| |
| // Navigate away to let the subframe's worklet enter keep-alive. |
| NavigateIframeToURL(shell()->web_contents(), "test_iframe", |
| GURL(url::kAboutBlankURL)); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Ensure that the response is deferred. |
| test_runtime_manager().GetKeepAliveWorkletHost()->WaitForAddModule(); |
| |
| // Three pending messages are expected: two for console.log and one for |
| // `addModule()` response. |
| EXPECT_EQ(3u, test_runtime_manager() |
| .GetKeepAliveWorkletHost() |
| ->pending_worklet_messages() |
| .size()); |
| |
| // Configure the worklet host for the main frame to handle worklet responses |
| // directly. |
| test_runtime_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(false); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module2.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Execute all the deferred messages. This will terminate the keep-alive. |
| test_runtime_manager() |
| .GetKeepAliveWorkletHost() |
| ->ExecutePendingWorkletMessages(); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // Expect loggings only from executing top document's worklet. |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("Executing simple_module2.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| // Navigate again to record histograms. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| WaitForHistograms({kDestroyedStatusHistogram, kTimingUsefulResourceHistogram, |
| kTimingKeepAliveDurationHistogram}); |
| |
| histogram_tester_.ExpectBucketCount( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus:: |
| kKeepAliveEndedDueToOperationsFinished, |
| 1); |
| histogram_tester_.ExpectBucketCount( |
| kDestroyedStatusHistogram, |
| blink::SharedStorageWorkletDestroyedStatus::kDidNotEnterKeepAlive, 1); |
| histogram_tester_.ExpectTotalCount(kTimingKeepAliveDurationHistogram, 1); |
| histogram_tester_.ExpectTotalCount(kTimingUsefulResourceHistogram, 2); |
| |
| std::map<int, base::UnguessableToken>& cached_worklet_devtools_tokens = |
| GetCachedWorkletHostDevToolsTokens(); |
| EXPECT_EQ(cached_worklet_devtools_tokens.size(), 2u); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, cached_worklet_devtools_tokens[0])}, |
| {AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module2.js"), |
| /*worklet_ordinal=*/1, cached_worklet_devtools_tokens[1])}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| RenderProcessHostDestroyedDuringWorkletKeepAlive_SameOrigin) { |
| // The test assumes pages gets deleted after navigation, letting the worklet |
| // enter keep-alive phase. To ensure this, disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| test_runtime_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )", |
| EXECUTE_SCRIPT_NO_RESOLVE_PROMISES); |
| |
| // Navigate to trigger keep-alive |
| RenderFrameDeletedObserver rfh_deleted_observer( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| EXPECT_TRUE(NavigateToURL(shell(), |
| https_server()->GetURL("c.test", kSimplePagePath))); |
| rfh_deleted_observer.WaitUntilDeleted(); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // The BrowserContext will be destroyed right after this test body, which will |
| // cause the RenderProcessHost to be destroyed before the keep-alive |
| // SharedStorageWorkletHost. Expect no fatal error. |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| RenderProcessHostDestroyedDuringWorkletKeepAlive_CrossOrigin) { |
| // The test assumes pages gets deleted after navigation, letting the worklet |
| // enter keep-alive phase. To ensure this, disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| test_runtime_manager() |
| .ConfigureShouldDeferWorkletMessagesOnWorkletHostCreation(true); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EvalJsResult result = EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1)", module_script_url.spec()), |
| EXECUTE_SCRIPT_NO_RESOLVE_PROMISES); |
| |
| // Navigate to trigger keep-alive |
| RenderFrameDeletedObserver rfh_deleted_observer( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| EXPECT_TRUE(NavigateToURL(shell(), |
| https_server()->GetURL("c.test", kSimplePagePath))); |
| rfh_deleted_observer.WaitUntilDeleted(); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(1u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // The BrowserContext will be destroyed right after this test body, which will |
| // cause the RenderProcessHost to be destroyed before the keep-alive |
| // SharedStorageWorkletHost. Expect no fatal error. |
| } |
| |
| // Test that there's no need to charge budget if the input urls' size is 1. |
| // This specifically tests the operation success scenario. |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| SelectURL_BudgetMetadata_OperationSuccess_SingleInputURL) { |
| metrics::dwa::DwaRecorder::Get()->EnableRecording(); |
| metrics::dwa::DwaRecorder::Get()->Purge(); |
| ASSERT_THAT(metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting(), |
| testing::IsEmpty()); |
| |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition()); |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html", |
| reportingMetadata: { |
| "click": "fenced_frames/report1.html", |
| "mouse interaction": "fenced_frames/report2.html" |
| } |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config = |
| config_observer.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config.has_value()); |
| EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value()); |
| |
| SharedStorageBudgetMetadata* metadata = |
| GetSharedStorageBudgetMetadata(observed_urn_uuid.value()); |
| EXPECT_TRUE(metadata); |
| EXPECT_EQ(metadata->site, |
| net::SchemefulSite(https_server()->GetOrigin("a.test"))); |
| EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0); |
| |
| EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()), |
| UnorderedElementsAre( |
| Pair("click", https_server()->GetURL( |
| "a.test", "/fenced_frames/report1.html")), |
| Pair("mouse interaction", |
| https_server()->GetURL("a.test", |
| "/fenced_frames/report2.html")))); |
| |
| EXPECT_EQ("Finish executing 'test-url-selection-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram, |
| kSelectUrlBudgetStatusHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| histogram_tester_.ExpectUniqueSample( |
| kSelectUrlBudgetStatusHistogram, |
| blink::SharedStorageSelectUrlBudgetStatus::kSufficientBudget, 1); |
| |
| const auto& entries = |
| metrics::dwa::DwaRecorder::Get()->GetEntriesForTesting(); |
| EXPECT_EQ(entries.size(), 1u); |
| EXPECT_THAT(entries[0]->event_hash, |
| base::HashMetricName("SharedStorage.SelectUrlFinishedInWorklet")); |
| EXPECT_THAT( |
| entries[0]->content_hash, |
| base::HashMetricName( |
| net::registry_controlled_domains::GetDomainAndRegistry( |
| main_url, |
| net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES))); |
| EXPECT_EQ(entries[0]->metrics.size(), 1u); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {{"click", |
| https_server() |
| ->GetURL("a.test", "/fenced_frames/report1.html") |
| .spec()}, |
| {"mouse interaction", |
| https_server() |
| ->GetURL("a.test", "/fenced_frames/report2.html") |
| .spec()}}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| // Test that there's no need to charge budget if the input urls' size is 1. |
| // This specifically tests the operation failure scenario. |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| SelectURL_BudgetMetadata_OperationFailure_SingleInputURL) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition()); |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html", |
| reportingMetadata: { |
| "click": "fenced_frames/report1.html" |
| } |
| } |
| ], |
| { |
| data: {'mockResult': -1}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config = |
| config_observer.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config.has_value()); |
| EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value()); |
| |
| SharedStorageBudgetMetadata* metadata = |
| GetSharedStorageBudgetMetadata(observed_urn_uuid.value()); |
| EXPECT_TRUE(metadata); |
| EXPECT_EQ(metadata->site, |
| net::SchemefulSite(https_server()->GetOrigin("a.test"))); |
| EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0); |
| |
| EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()), |
| UnorderedElementsAre( |
| Pair("click", https_server()->GetURL( |
| "a.test", "/fenced_frames/report1.html")))); |
| |
| EXPECT_EQ( |
| "Promise resolved to a number outside the length of the input urls.", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {{"click", |
| https_server() |
| ->GetURL("a.test", "/fenced_frames/report1.html") |
| .spec()}}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| SelectURL_BudgetMetadata_Origin) { |
| EXPECT_TRUE(NavigateToURL( |
| shell(), https_server()->GetURL("a.test", kPageWithBlankIframePath))); |
| |
| GURL iframe_url = https_server()->GetURL("b.test", kSimplePagePath); |
| NavigateIframeToURL(shell()->web_contents(), "test_iframe", iframe_url); |
| |
| RenderFrameHost* iframe = |
| PrimaryFrameTreeNodeRoot()->child_at(0)->current_frame_host(); |
| |
| EXPECT_TRUE(ExecJs(iframe, R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_TRUE(ExecJs(iframe, JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition()); |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result = EvalJs(iframe, R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| }, |
| { |
| url: "fenced_frames/title1.html", |
| reportingMetadata: { |
| "click": "fenced_frames/report1.html" |
| } |
| }, |
| { |
| url: "fenced_frames/title2.html" |
| } |
| ], |
| { |
| data: {'mockResult': 1}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config = |
| config_observer.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config.has_value()); |
| EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value()); |
| |
| SharedStorageBudgetMetadata* metadata = |
| GetSharedStorageBudgetMetadata(observed_urn_uuid.value()); |
| EXPECT_TRUE(metadata); |
| EXPECT_EQ(metadata->site, |
| net::SchemefulSite(https_server()->GetOrigin("b.test"))); |
| EXPECT_DOUBLE_EQ(metadata->budget_to_charge, std::log2(3)); |
| |
| SharedStorageReportingMap reporting_map = |
| GetSharedStorageReportingMap(observed_urn_uuid.value()); |
| EXPECT_FALSE(reporting_map.empty()); |
| EXPECT_EQ(1U, reporting_map.size()); |
| EXPECT_EQ("click", reporting_map.begin()->first); |
| EXPECT_EQ(https_server()->GetURL("b.test", "/fenced_frames/report1.html"), |
| reporting_map.begin()->second); |
| |
| WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(iframe_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("b.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("b.test", |
| "/fenced_frames/title0.html"), |
| {}}, |
| {https_server()->GetURL("b.test", |
| "/fenced_frames/title1.html"), |
| {{"click", |
| https_server() |
| ->GetURL("b.test", "/fenced_frames/report1.html") |
| .spec()}}}, |
| {https_server()->GetURL("b.test", |
| "/fenced_frames/title2.html"), |
| {}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| SelectURL_SecondSelectURLAfterKeepAliveTrueSelectURL_Success) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer1( |
| GetStoragePartition()); |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result1 = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig, |
| keepAlive: true |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result1.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid1 = config_observer1.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid1.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid1.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result1.ExtractString(), observed_urn_uuid1->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer1.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config1 = |
| config_observer1.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config1.has_value()); |
| EXPECT_EQ(fenced_frame_config1->urn_uuid(), observed_urn_uuid1.value()); |
| |
| EXPECT_EQ("Finish executing 'test-url-selection-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer2( |
| GetStoragePartition()); |
| |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result2 = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result2.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid2 = config_observer2.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid2.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid2.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result2.ExtractString(), observed_urn_uuid2->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer2.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config2 = |
| config_observer2.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config2.has_value()); |
| EXPECT_EQ(fenced_frame_config2->urn_uuid(), observed_urn_uuid2.value()); |
| |
| EXPECT_EQ("Finish executing 'test-url-selection-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 2); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 2u); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/1, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[1], |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| SelectURL_SecondSelectURLAfterKeepAliveFalseSelectURL_Failure) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer1( |
| GetStoragePartition()); |
| |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result1 = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig, |
| keepAlive: false |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result1.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid1 = config_observer1.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid1.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid1.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result1.ExtractString(), observed_urn_uuid1->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer1.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config1 = |
| config_observer1.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config1.has_value()); |
| EXPECT_EQ(fenced_frame_config1->urn_uuid(), observed_urn_uuid1.value()); |
| |
| EXPECT_EQ("Finish executing 'test-url-selection-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer2( |
| GetStoragePartition()); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"), |
| EvalJsResult::ErrorIs( |
| testing::HasSubstr(kSharedStorageWorkletExpiredMessage))); |
| |
| EXPECT_FALSE(config_observer2.ConfigObserved()); |
| |
| WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| SelectURL_SecondSelectURLAfterKeepAliveDefaultSelectURL_Failure) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer1( |
| GetStoragePartition()); |
| |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| const char select_url_script[] = R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"; |
| |
| EvalJsResult result1 = EvalJs(shell(), select_url_script); |
| |
| EXPECT_TRUE(result1.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid1 = config_observer1.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid1.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid1.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result1.ExtractString(), observed_urn_uuid1->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer1.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config1 = |
| config_observer1.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config1.has_value()); |
| EXPECT_EQ(fenced_frame_config1->urn_uuid(), observed_urn_uuid1.value()); |
| |
| EXPECT_EQ("Finish executing 'test-url-selection-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer2( |
| GetStoragePartition()); |
| |
| EXPECT_THAT(EvalJs(shell(), select_url_script), |
| EvalJsResult::ErrorIs( |
| testing::HasSubstr(kSharedStorageWorkletExpiredMessage))); |
| |
| EXPECT_FALSE(config_observer2.ConfigObserved()); |
| |
| WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| SelectURL_SelectURLAfterKeepAliveFalseRun_Failure) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}, |
| keepAlive: false}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition()); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"), |
| EvalJsResult::ErrorIs( |
| testing::HasSubstr(kSharedStorageWorkletExpiredMessage))); |
| |
| EXPECT_FALSE(config_observer.ConfigObserved()); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("{\"customKey\":\"customValue\"}", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| SelectURL_SelectURLAfterKeepAliveTrueRun_Success) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}, |
| keepAlive: true}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("{\"customKey\":\"customValue\"}", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition()); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config = |
| config_observer.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config.has_value()); |
| EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value()); |
| |
| EXPECT_EQ("Finish executing 'test-url-selection-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram, |
| kTimingSelectUrlExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/1, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| SelectURL_SelectURLAfterKeepAliveDefaultRun_Failure) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition()); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"), |
| EvalJsResult::ErrorIs( |
| testing::HasSubstr(kSharedStorageWorkletExpiredMessage))); |
| |
| EXPECT_FALSE(config_observer.ConfigObserved()); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("Start executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("{\"customKey\":\"customValue\"}", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| RunOperation_RunAfterKeepAliveTrueSelectURL_Success) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition()); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig, |
| keepAlive: true |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config = |
| config_observer.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config.has_value()); |
| EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value()); |
| |
| EXPECT_EQ("Finish executing 'test-url-selection-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ("Finish executing 'test-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram, |
| kTimingSelectUrlExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/1, /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| RunOperation_RunAfterKeepAliveFalseSelectURL_Failure) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition()); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result1 = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig, |
| keepAlive: false |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result1.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result1.ExtractString(), observed_urn_uuid->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config = |
| config_observer.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config.has_value()); |
| EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value()); |
| |
| EXPECT_EQ("Finish executing 'test-url-selection-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )"), |
| EvalJsResult::ErrorIs( |
| testing::HasSubstr(kSharedStorageWorkletExpiredMessage))); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| RunOperation_RunAfterKeepAliveDefaultSelectURL_Failure) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("Start executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("Finish executing simple_module.js", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition()); |
| |
| EXPECT_EQ(1u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result1 = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html" |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result1.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result1.ExtractString(), observed_urn_uuid->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config = |
| config_observer.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config.has_value()); |
| EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value()); |
| |
| EXPECT_EQ("Finish executing 'test-url-selection-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| EXPECT_THAT(EvalJs(shell(), R"( |
| sharedStorage.run( |
| 'test-operation', {data: {'customKey': 'customValue'}}); |
| )"), |
| EvalJsResult::ErrorIs( |
| testing::HasSubstr(kSharedStorageWorkletExpiredMessage))); |
| |
| EXPECT_EQ(0u, test_runtime_manager().GetAttachedWorkletHostsCount()); |
| EXPECT_EQ(0u, test_runtime_manager().GetKeepAliveWorkletHostsCount()); |
| |
| WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| SelectURL_ReportingMetadata_EmptyReportEvent) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js'); |
| )")); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;", |
| ResolveSelectURLToConfig()))); |
| |
| TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition()); |
| // There is 1 more "worklet operation": `selectURL()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EvalJsResult result = EvalJs(shell(), R"( |
| (async function() { |
| window.select_url_result = await sharedStorage.selectURL( |
| 'test-url-selection-operation', |
| [ |
| { |
| url: "fenced_frames/title0.html", |
| reportingMetadata: { |
| "": "fenced_frames/report1.html" |
| } |
| } |
| ], |
| { |
| data: {'mockResult': 0}, |
| resolveToConfig: resolveSelectURLToConfig |
| } |
| ); |
| if (resolveSelectURLToConfig && |
| !(select_url_result instanceof FencedFrameConfig)) { |
| throw new Error('selectURL() did not return a FencedFrameConfig.'); |
| } |
| return window.select_url_result; |
| })() |
| )"); |
| |
| EXPECT_TRUE(result.is_ok()); |
| const std::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid(); |
| EXPECT_TRUE(observed_urn_uuid.has_value()); |
| EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value())); |
| |
| if (!ResolveSelectURLToConfig()) { |
| EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec()); |
| } |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| ASSERT_TRUE(config_observer.ConfigObserved()); |
| const std::optional<FencedFrameConfig>& fenced_frame_config = |
| config_observer.GetConfig(); |
| EXPECT_TRUE(fenced_frame_config.has_value()); |
| EXPECT_EQ(fenced_frame_config->urn_uuid(), observed_urn_uuid.value()); |
| |
| SharedStorageBudgetMetadata* metadata = |
| GetSharedStorageBudgetMetadata(observed_urn_uuid.value()); |
| EXPECT_TRUE(metadata); |
| EXPECT_EQ(metadata->site, |
| net::SchemefulSite(https_server()->GetOrigin("a.test"))); |
| EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0); |
| |
| EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()), |
| UnorderedElementsAre( |
| Pair("", https_server()->GetURL( |
| "a.test", "/fenced_frames/report1.html")))); |
| |
| EXPECT_EQ("Finish executing 'test-url-selection-operation'", |
| base::UTF16ToUTF8(console_observer.messages().back().message)); |
| |
| WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram, |
| 1); |
| |
| ASSERT_EQ(urn_uuids_observed().size(), 1u); |
| |
| std::string origin_str = url::Origin::Create(main_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| https_server()->GetURL("a.test", |
| "/shared_storage/simple_module.js"), |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kSelectURL, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSelectURLForTesting( |
| "test-url-selection-operation", /*operation_id=*/0, |
| /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), |
| std::vector<SharedStorageUrlSpecWithMetadata>( |
| {{https_server()->GetURL("a.test", |
| "/fenced_frames/title0.html"), |
| {{"", https_server() |
| ->GetURL("a.test", "/fenced_frames/report1.html") |
| .spec()}}}}), |
| ResolveSelectURLToConfig(), |
| /*saved_query=*/std::string(), urn_uuids_observed()[0], |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, SetAppendOperationInDocument) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| |
| sharedStorage.set('key1', 'value1'); |
| sharedStorage.set('key1', 'value111'); |
| |
| sharedStorage.set('key2', 'value2'); |
| sharedStorage.set('key2', 'value222', {ignoreIfPresent: true}); |
| |
| sharedStorage.set('key3', 'value3'); |
| sharedStorage.append('key3', 'value333'); |
| )")); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('key0')); |
| console.log(await sharedStorage.get('key1')); |
| console.log(await sharedStorage.get('key2')); |
| console.log(await sharedStorage.get('key3')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("value0", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("value111", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("value2", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("value3value333", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("4", base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key0", "value0", false)}, |
| {AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key1", "value1", false)}, |
| {AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key1", "value111", false)}, |
| {AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key2", "value2", false)}, |
| {AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key2", "value222", true)}, |
| {AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key3", "value3", false)}, |
| {AccessScope::kWindow, AccessMethod::kAppend, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForAppend("key3", "value333")}, |
| {AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key0", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key1", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key2", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key3", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, DeleteOperationInDocument) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| sharedStorage.delete('key0'); |
| )")); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.length()); |
| console.log(await sharedStorage.get('key0')); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[0].log_level); |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[1].log_level); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key0", "value0", false)}, |
| {AccessScope::kWindow, AccessMethod::kDelete, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForDelete("key0")}, |
| {AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key0", GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, ClearOperationInDocument) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| sharedStorage.clear(); |
| )")); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key0", "value0", false)}, |
| {AccessScope::kWindow, AccessMethod::kClear, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForClear()}, |
| {AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, SetAppendOperationInWorklet) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| |
| sharedStorage.set('key1', 'value1'); |
| sharedStorage.set('key1', 'value111'); |
| |
| sharedStorage.set('key2', 'value2'); |
| sharedStorage.set('key2', 'value222', {ignoreIfPresent: true}); |
| |
| sharedStorage.set('key3', 'value3'); |
| sharedStorage.append('key3', 'value333'); |
| |
| console.log(await sharedStorage.get('key0')); |
| console.log(await sharedStorage.get('key1')); |
| console.log(await sharedStorage.get('key2')); |
| console.log(await sharedStorage.get('key3')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("value0", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("value111", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("value2", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("value3value333", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("4", base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key0", "value0", false, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key1", "value1", false, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key1", "value111", false, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key2", "value2", false, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key2", "value222", true, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key3", "value3", false, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kAppend, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForAppend( |
| "key3", "value333", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key0", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key1", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key2", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key3", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| AppendOperationFailedInWorklet) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| await sharedStorage.set('k', 'a'.repeat(2621439)); |
| |
| // This will fail due to the storage quota being reached. |
| await sharedStorage.append('k', 'a'); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages()[0].message), |
| testing::HasSubstr("sharedStorage.append() failed")); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError, |
| console_observer.messages()[0].log_level); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "k", std::string(2621439, 'a'), false, |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kAppend, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForAppend( |
| "k", "a", GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, DeleteOperationInWorklet) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| console.log(await sharedStorage.length()); |
| console.log(await sharedStorage.get('key0')); |
| |
| sharedStorage.delete('key0'); |
| |
| console.log(await sharedStorage.length()); |
| console.log(await sharedStorage.get('key0')); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("value0", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[0].log_level); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[1].log_level); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[2].log_level); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[3].log_level); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key0", "value0", false, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key0", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kDelete, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForDelete( |
| "key0", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key0", GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, ClearOperationInWorklet) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| console.log(await sharedStorage.length()); |
| console.log(await sharedStorage.get('key0')); |
| |
| sharedStorage.clear(); |
| |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("value0", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key0", "value0", false, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key0", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kClear, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, ConsoleErrorInWorklet) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.error('error0'); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kError, |
| console_observer.messages()[0].log_level); |
| EXPECT_EQ("error0", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, GetOperationInWorklet) { |
| base::SimpleTestClock clock; |
| base::RunLoop loop; |
| static_cast<StoragePartitionImpl*>(GetStoragePartition()) |
| ->GetSharedStorageManager() |
| ->OverrideClockForTesting(&clock, loop.QuitClosure()); |
| loop.Run(); |
| clock.SetNow(base::Time::Now()); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| )")); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| GURL script_url = |
| https_server()->GetURL("a.test", "/shared_storage/getter_module.js"); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url))); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('get-operation', {data: {'key': 'key0'}, |
| keepAlive: true}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| // Advance clock so that key will expire. |
| clock.Advance(base::Days(kStalenessThresholdDays) + base::Seconds(1)); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('get-operation', {data: {'key': 'key0'}}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("sharedStorage.length(): 1", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("sharedStorage.get('key0'): value0", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("sharedStorage.length(): 0", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("sharedStorage.get('key0'): undefined", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[0].log_level); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[1].log_level); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[2].log_level); |
| EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kInfo, |
| console_observer.messages()[3].log_level); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 2); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key0", "value0", false)}, |
| {AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "get-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key0", GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "get-operation", /*operation_id=*/1, /*keep_alive=*/false, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key0", GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| AccessStorageInSameOriginDocument) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| // Cache the main frame ID for comparison below, since it will change with |
| // navigation. |
| GlobalRenderFrameHostId cached_main_frame_id = MainFrameId(); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| )")); |
| |
| EXPECT_TRUE( |
| NavigateToURL(shell(), https_server()->GetURL("a.test", "/title1.html"))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kSet, cached_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForSet("key0", "value0", false)}, |
| {AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| AccessStorageInDifferentOriginDocument) { |
| GURL url1 = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url1)); |
| |
| // Cache the main frame ID for comparison below, since it will change with |
| // navigation. |
| GlobalRenderFrameHostId cached_main_frame_id = MainFrameId(); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| )")); |
| |
| GURL url2 = https_server()->GetURL("b.test", "/title1.html"); |
| EXPECT_TRUE(NavigateToURL(shell(), url2)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin2_str = url::Origin::Create(url2).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kSet, cached_main_frame_id, |
| url::Origin::Create(url1).Serialize(), |
| SharedStorageEventParams::CreateForSet("key0", "value0", false)}, |
| {AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin2_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin2_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin2_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, KeysAndEntriesOperation) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| sharedStorage.set('key1', 'value1'); |
| sharedStorage.set('key2', 'value2'); |
| )")); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| for await (const key of sharedStorage.keys()) { |
| console.log(key); |
| } |
| for await (const [key, value] of sharedStorage.entries()) { |
| console.log(key + ';' + value); |
| } |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(6u, console_observer.messages().size()); |
| EXPECT_EQ("key0", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("key1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("key2", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("key0;value0", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("key1;value1", |
| base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| EXPECT_EQ("key2;value2", |
| base::UTF16ToUTF8(console_observer.messages()[5].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key0", "value0", false)}, |
| {AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key1", "value1", false)}, |
| {AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key2", "value2", false)}, |
| {AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kKeys, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kEntries, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, ValuesOperation) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| sharedStorage.set('key1', 'value1'); |
| sharedStorage.set('key2', 'value2'); |
| )")); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| for await (const value of sharedStorage.values()) { |
| console.log(value); |
| } |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("value0", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("value1", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("value2", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key0", "value0", false)}, |
| {AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key1", "value1", false)}, |
| {AccessScope::kWindow, AccessMethod::kSet, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet("key2", "value2", false)}, |
| {AccessScope::kWindow, AccessMethod::kAddModule, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kValues, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| KeysAndEntriesOperation_MultipleBatches) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| for (let i = 0; i < 150; ++i) { |
| sharedStorage.set('key' + i.toString().padStart(3, '0'), |
| 'value' + i.toString().padStart(3, '0')); |
| } |
| )")); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| for await (const key of sharedStorage.keys()) { |
| console.log(key); |
| } |
| for await (const [key, value] of sharedStorage.entries()) { |
| console.log(key + ';' + value); |
| } |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(300u, console_observer.messages().size()); |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| std::vector<TestSharedStorageObserver::Access> expected_accesses; |
| for (int i = 0; i < 150; ++i) { |
| std::string zero_padded_i = base::NumberToString(i); |
| zero_padded_i.insert(zero_padded_i.begin(), 3 - zero_padded_i.size(), '0'); |
| |
| std::string padded_key = base::StrCat({"key", zero_padded_i}); |
| std::string padded_value = base::StrCat({"value", zero_padded_i}); |
| EXPECT_EQ(padded_key, |
| base::UTF16ToUTF8(console_observer.messages()[i].message)); |
| EXPECT_EQ(base::JoinString({padded_key, padded_value}, ";"), |
| base::UTF16ToUTF8(console_observer.messages()[i + 150].message)); |
| |
| expected_accesses.emplace_back(AccessScope::kWindow, AccessMethod::kSet, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForSet( |
| padded_key, padded_value, false)); |
| } |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 1); |
| |
| expected_accesses.emplace_back(AccessScope::kWindow, AccessMethod::kAddModule, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, /*worklet_ordinal=*/0, |
| GetFirstWorkletHostDevToolsToken())); |
| expected_accesses.emplace_back( |
| AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())); |
| expected_accesses.emplace_back( |
| AccessScope::kSharedStorageWorklet, AccessMethod::kKeys, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())); |
| expected_accesses.emplace_back( |
| AccessScope::kSharedStorageWorklet, AccessMethod::kEntries, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| GetFirstWorkletHostDevToolsToken())); |
| ExpectAccessObserved(expected_accesses); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, WebLocksUsageHistograms) { |
| // The test assumes pages gets deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| await sharedStorage.set('key0', 'value0'); |
| await sharedStorage.set('key0', 'value0'); |
| |
| await navigator.locks.request("lock1", async (lock) => { |
| await sharedStorage.set('key0', 'value0', { withLock: 'lock2' }); |
| }); |
| |
| await sharedStorage.batchUpdate([ |
| new SharedStorageSetMethod('key0', 'value0'), |
| new SharedStorageAppendMethod('key1', 'value1') |
| ], { withLock: 'lock1' }); |
| )", |
| &out_script_url); |
| |
| // Navigate again to record histograms. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| histogram_tester_.ExpectBucketCount( |
| "Storage.SharedStorage.UpdateMethod.HasLockOption", true, 1); |
| histogram_tester_.ExpectBucketCount( |
| "Storage.SharedStorage.UpdateMethod.HasLockOption", false, 2); |
| histogram_tester_.ExpectUniqueSample( |
| "Storage.SharedStorage.BatchUpdateMethod.HasLockOption", true, 1); |
| histogram_tester_.ExpectUniqueSample( |
| "Storage.SharedStorage.Worklet.NavigatorLocksInvoked", true, 1); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key0", "value0", |
| /*ignore_if_present=*/false, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key0", "value0", |
| /*ignore_if_present=*/false, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key0", "value0", /*ignore_if_present=*/false, |
| GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/"lock2", |
| /*batch_update_id=*/std::nullopt)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kBatchUpdate, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForBatchUpdate( |
| GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/"lock1", |
| /*batch_update_id=*/0, |
| /*batch_size=*/2u)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key0", "value0", |
| /*ignore_if_present=*/false, GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/std::nullopt, |
| /*batch_update_id=*/0)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kAppend, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForAppend( |
| "key1", "value1", GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/std::nullopt, |
| /*batch_update_id=*/0)}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, MulipleBatchUpdates) { |
| // The test assumes pages gets deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| await sharedStorage.batchUpdate([ |
| new SharedStorageSetMethod('key0', 'value0'), |
| new SharedStorageAppendMethod('key1', 'value1') |
| ]); |
| |
| await sharedStorage.batchUpdate([ |
| new SharedStorageSetMethod('key2', 'value2'), |
| ], { withLock: 'lock1' }); |
| |
| await sharedStorage.batchUpdate([ |
| new SharedStorageSetMethod("key1", "value0", {ignoreIfPresent: true}), |
| new SharedStorageAppendMethod("key1", "value1"), |
| new SharedStorageDeleteMethod("key2"), |
| new SharedStorageClearMethod() |
| ], { withLock: 'lock1' }); |
| )", |
| &out_script_url); |
| |
| // Navigate again to record histograms. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| histogram_tester_.ExpectBucketCount( |
| "Storage.SharedStorage.BatchUpdateMethod.HasLockOption", false, 1); |
| histogram_tester_.ExpectBucketCount( |
| "Storage.SharedStorage.BatchUpdateMethod.HasLockOption", true, 2); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kBatchUpdate, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForBatchUpdate( |
| GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/std::nullopt, |
| /*batch_update_id=*/0, |
| /*batch_size=*/2u)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key0", "value0", /*ignore_if_present=*/false, |
| GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/std::nullopt, |
| /*batch_update_id=*/0)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kAppend, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForAppend( |
| "key1", "value1", GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/std::nullopt, |
| /*batch_update_id=*/0)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kBatchUpdate, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForBatchUpdate( |
| GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/"lock1", |
| /*batch_update_id=*/1, |
| /*batch_size=*/1u)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key2", "value2", /*ignore_if_present=*/false, |
| GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/std::nullopt, |
| /*batch_update_id=*/1)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kBatchUpdate, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForBatchUpdate( |
| GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/"lock1", |
| /*batch_update_id=*/2, |
| /*batch_size=*/4u)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key1", "value0", |
| /*ignore_if_present=*/true, GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/std::nullopt, |
| /*batch_update_id=*/2)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kAppend, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForAppend( |
| "key1", "value1", GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/std::nullopt, |
| /*batch_update_id=*/2)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kDelete, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForDelete( |
| "key2", GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/std::nullopt, |
| /*batch_update_id=*/2)}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kClear, |
| initial_main_frame_id, origin_str, |
| SharedStorageEventParams::CreateForClear( |
| GetFirstWorkletHostDevToolsToken(), |
| /*with_lock=*/std::nullopt, |
| /*batch_update_id=*/2)}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, EmptyBatchUpdate) { |
| // The test assumes pages gets deleted after navigation. To ensure this, |
| // disable back/forward cache. |
| content::DisableBackForwardCacheForTesting( |
| shell()->web_contents(), |
| content::BackForwardCache::TEST_REQUIRES_NO_CACHING); |
| |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| GlobalRenderFrameHostId initial_main_frame_id = MainFrameId(); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| await sharedStorage.batchUpdate([], { withLock: 'lock1' }); |
| )", |
| &out_script_url); |
| |
| // Navigate again to record histograms. |
| EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL))); |
| |
| histogram_tester_.ExpectUniqueSample( |
| "Storage.SharedStorage.BatchUpdateMethod.HasLockOption", true, 1); |
| |
| // The empty batchUpdate does not receive an event notification. |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kAddModule, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForAddModule( |
| out_script_url, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}, |
| {AccessScope::kWindow, AccessMethod::kRun, initial_main_frame_id, |
| origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| CreateWorklet_SameOrigin_Success) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "a.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| kEmptyAccessControlAllowOriginReplacement, |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.createWorklet($1)", |
| module_script_url.spec()))); |
| |
| TestSharedStorageWorkletHost* worklet_host = |
| test_runtime_manager().GetAttachedWorkletHost(); |
| EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(), |
| worklet_host->GetProcessHost()); |
| |
| EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kCreateWorklet, |
| worklet_host->creation_method()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| CreateWorklet_CrossOriginScript_DefaultDataOrigin_FailedCors) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| kEmptyAccessControlAllowOriginReplacement, |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EXPECT_THAT(EvalJs(shell(), JsReplace("sharedStorage.createWorklet($1)", |
| module_script_url.spec())), |
| EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| CreateWorklet_CrossOriginScript_ContextDataOrigin_FailedCors) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| kEmptyAccessControlAllowOriginReplacement, |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace( |
| "sharedStorage.createWorklet($1, {dataOrigin: 'context-origin'})", |
| module_script_url.spec())), |
| EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| CreateWorklet_CrossOriginScript_ScriptDataOrigin_FailedCors) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| kEmptyAccessControlAllowOriginReplacement, |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace( |
| "sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})", |
| module_script_url.spec())), |
| EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| CreateWorklet_CrossOriginScript_CustomDataOrigin_FailedCors) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| kEmptyAccessControlAllowOriginReplacement, |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize())), |
| EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| CreateWorklet_CrossOriginScript_ScriptDataOrigin_FailedSharedStorageWorkletAllowedResponseHeaderCheck) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace( |
| "sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})", |
| module_script_url.spec())), |
| EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| CreateWorklet_CrossOriginScript_ContextDataOrigin_NoSharedStorageWorkletAllowedResponseHeader_Success) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace( |
| "sharedStorage.createWorklet($1, {dataOrigin: 'context-origin'})", |
| module_script_url.spec()))); |
| |
| TestSharedStorageWorkletHost* worklet_host = |
| test_runtime_manager().GetAttachedWorkletHost(); |
| |
| // The worklet host should reuse the main frame's process because the context |
| // origin is being used as the data origin. |
| bool use_new_process = |
| (shell()->web_contents()->GetPrimaryMainFrame()->GetProcess() != |
| worklet_host->GetProcessHost()); |
| |
| EXPECT_FALSE(use_new_process); |
| |
| EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kCreateWorklet, |
| worklet_host->creation_method()); |
| |
| std::string data_origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| data_origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| module_script_url, "context-origin", |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| CreateWorklet_CrossOriginScript_DefaultDataOrigin_NoSharedStorageWorkletAllowedResponseHeader_Success) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.createWorklet($1)", |
| module_script_url.spec()))); |
| |
| TestSharedStorageWorkletHost* worklet_host = |
| test_runtime_manager().GetAttachedWorkletHost(); |
| |
| // The worklet host should reuse the main frame's process because the context |
| // origin is being used as the data origin. |
| bool use_new_process = |
| (shell()->web_contents()->GetPrimaryMainFrame()->GetProcess() != |
| worklet_host->GetProcessHost()); |
| |
| EXPECT_FALSE(use_new_process); |
| |
| EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kCreateWorklet, |
| worklet_host->creation_method()); |
| |
| // The default data origin is the context origin. |
| std::string data_origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| data_origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| module_script_url, "context-origin", |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| CreateWorklet_CrossOriginScript_ScriptDataOrigin_Success) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace( |
| "sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})", |
| module_script_url.spec()))); |
| |
| TestSharedStorageWorkletHost* worklet_host = |
| test_runtime_manager().GetAttachedWorkletHost(); |
| |
| // The worklet host should reuse the main frame's process on Android without |
| // strict site isolation; otherwise, it should use a new process. |
| bool expected_use_new_process = AreAllSitesIsolatedForTesting(); |
| bool actual_use_new_process = |
| (shell()->web_contents()->GetPrimaryMainFrame()->GetProcess() != |
| worklet_host->GetProcessHost()); |
| |
| EXPECT_EQ(expected_use_new_process, actual_use_new_process); |
| |
| EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kCreateWorklet, |
| worklet_host->creation_method()); |
| |
| std::string data_origin_str = |
| url::Origin::Create(module_script_url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| data_origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| module_script_url, "script-origin", |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| CreateWorklet_CrossOriginScript_CustomDataOrigin_Success) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize()))); |
| |
| TestSharedStorageWorkletHost* worklet_host = |
| test_runtime_manager().GetAttachedWorkletHost(); |
| |
| // The worklet host should reuse the main frame's process on Android without |
| // strict site isolation; otherwise, it should use a new process. |
| bool expected_use_new_process = AreAllSitesIsolatedForTesting(); |
| bool actual_use_new_process = |
| (shell()->web_contents()->GetPrimaryMainFrame()->GetProcess() != |
| worklet_host->GetProcessHost()); |
| |
| EXPECT_EQ(expected_use_new_process, actual_use_new_process); |
| |
| EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kCreateWorklet, |
| worklet_host->creation_method()); |
| |
| std::string custom_data_origin_str = custom_data_origin.Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| custom_data_origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| module_script_url, custom_data_origin_str, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| // Start a worklet under b.test using the script origin as the data |
| // originb(cross-origin to the main frame's origin), and then append a subframe |
| // under b.test. Assert that they share the same process. |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| CreateWorkletAndSubframe_CrossOrigin) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace( |
| "sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})", |
| module_script_url.spec()))); |
| |
| TestSharedStorageWorkletHost* worklet_host = |
| test_runtime_manager().GetAttachedWorkletHost(); |
| |
| GURL iframe_url = https_server()->GetURL("b.test", "/empty.thml"); |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url); |
| |
| EXPECT_EQ(worklet_host->GetProcessHost(), |
| iframe_node->current_frame_host()->GetProcess()); |
| } |
| |
| // Append a subframe under b.test (cross-origin to the main frame's origin), and |
| // then start a worklet under b.test using the script origin as the data origin. |
| // Assert that they share the same process. |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| CreateSubframeAndWorklet_CrossOrigin) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL iframe_url = https_server()->GetURL("b.test", "/empty.thml"); |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace( |
| "sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})", |
| module_script_url.spec()))); |
| |
| TestSharedStorageWorkletHost* worklet_host = |
| test_runtime_manager().GetAttachedWorkletHost(); |
| |
| EXPECT_EQ(worklet_host->GetProcessHost(), |
| iframe_node->current_frame_host()->GetProcess()); |
| } |
| |
| // Start one worklet under b.test using the script origin as the data origin |
| // (cross-origin to the main frame's origin), and then start another worklet |
| // under b.test. Assert that they share the same process. |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| CreateTwoWorklets_CrossOrigin) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace( |
| "sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})", |
| module_script_url.spec()))); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace( |
| "sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'})", |
| module_script_url.spec()))); |
| |
| std::vector<TestSharedStorageWorkletHost*> worklet_hosts = |
| test_runtime_manager().GetAttachedWorkletHosts(); |
| |
| EXPECT_EQ(worklet_hosts.size(), 2u); |
| EXPECT_EQ(worklet_hosts[0]->GetProcessHost(), |
| worklet_hosts[1]->GetProcessHost()); |
| } |
| |
| // Start a worklet under b.test via createWorklet() using the script origin as |
| // the data origin, and then start a worklet under b.test's iframe. Assert that |
| // the data stored in the first worklet can be retrieved in the second worklet. |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| CrossOriginWorklet_VerifyDataOrigin) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace(R"( |
| new Promise((resolve, reject) => { |
| sharedStorage.createWorklet($1, {dataOrigin: 'script-origin'}) |
| .then((worklet) => { |
| window.testWorklet = worklet; |
| resolve(); |
| }); |
| }) |
| )", |
| module_script_url.spec()))); |
| |
| // Expect the run() operation. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| window.testWorklet.run('test-operation', { |
| data: { |
| 'set-key': 'key0', |
| 'set-value': 'value0' |
| }, |
| keepAlive: true |
| }); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| GURL iframe_url = https_server()->GetURL("b.test", "/empty.thml"); |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node->current_frame_host(), R"( |
| console.log(await sharedStorage.get('key0')); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("value0", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| } |
| |
| // Start a worklet with b.test script in a.test's context via createWorklet(), |
| // and then start a worklet with same-origin script in a.test's context. Assert |
| // that the data stored in the first worklet can be retrieved in the second |
| // worklet. |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| CrossOriginScript_ContextDataOrigin_VerifyDataOrigin) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace(R"( |
| new Promise((resolve, reject) => { |
| sharedStorage.createWorklet($1, {dataOrigin: 'context-origin'}) |
| .then((worklet) => { |
| window.testWorklet = worklet; |
| resolve(); |
| }); |
| }) |
| )", |
| module_script_url.spec()))); |
| |
| // Expect the run() operation. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| window.testWorklet.run('test-operation', { |
| data: { |
| 'set-key': 'key0', |
| 'set-value': 'value0' |
| }, |
| keepAlive: true |
| }); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| GURL iframe_url = https_server()->GetURL("a.test", "/empty.thml"); |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node->current_frame_host(), R"( |
| console.log(await sharedStorage.get('key0')); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("value0", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| } |
| |
| // Start a worklet with b.test script in a.test's context via createWorklet(), |
| // and then start a worklet with same-origin script in a.test's context. Assert |
| // that the data stored in the first worklet can be retrieved in the second |
| // worklet. |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| CrossOriginScript_DefaultDataOrigin_VerifyDataOrigin) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Cross-Origin-Worklet-Allowed: ?1"))); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace(R"( |
| new Promise((resolve, reject) => { |
| sharedStorage.createWorklet($1) |
| .then((worklet) => { |
| window.testWorklet = worklet; |
| resolve(); |
| }); |
| }) |
| )", |
| module_script_url.spec()))); |
| |
| // Expect the run() operation. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| window.testWorklet.run('test-operation', { |
| data: { |
| 'set-key': 'key0', |
| 'set-value': 'value0' |
| }, |
| keepAlive: true |
| }); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| GURL iframe_url = https_server()->GetURL("a.test", "/empty.thml"); |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node->current_frame_host(), R"( |
| console.log(await sharedStorage.get('key0')); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("value0", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| } |
| |
| // Start a worklet with b.test script in a.test's context with data origin |
| // c.test via createWorklet(), and then start a worklet with same-origin script |
| // in c.test's context. Assert that the data stored in the first worklet can be |
| // retrieved in the second worklet. |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| CrossOriginScript_CustomDataOrigin_VerifyDataOrigin) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| GURL custom_data_origin_url = |
| https_server()->GetURL("c.test", kSimplePagePath); |
| url::Origin custom_data_origin = url::Origin::Create(custom_data_origin_url); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace(R"( |
| new Promise((resolve, reject) => { |
| sharedStorage.createWorklet($1, {dataOrigin: $2}) |
| .then((worklet) => { |
| window.testWorklet = worklet; |
| resolve(); |
| }); |
| }) |
| )", |
| module_script_url.spec(), |
| custom_data_origin.Serialize()))); |
| |
| // Expect the run() operation. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| window.testWorklet.run('test-operation', { |
| data: { |
| 'set-key': 'key0', |
| 'set-value': 'value0' |
| }, |
| keepAlive: true |
| }); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), custom_data_origin_url); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node->current_frame_host(), R"( |
| console.log(await sharedStorage.get('key0')); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("value0", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| AddModule_CrossOriginScript_FailedCors) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| kEmptyAccessControlAllowOriginReplacement, |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| EXPECT_THAT(EvalJs(shell(), JsReplace("sharedStorage.worklet.addModule($1)", |
| module_script_url.spec())), |
| EvalJsResult::ErrorIs(testing::HasSubstr("Failed to load"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| AddModule_CrossOriginScript_Success) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.worklet.addModule($1)", |
| module_script_url.spec()))); |
| } |
| |
| // Start a worklet with b.test script (cross-origin to the main frame's origin), |
| // but a.test data and then append a subframe under b.test. Assert that they |
| // share the same process. |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| CreateWorkletAndSubframe_AddModule_CrossOriginScript) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.worklet.addModule($1)", |
| module_script_url.spec()))); |
| |
| TestSharedStorageWorkletHost* worklet_host = |
| test_runtime_manager().GetAttachedWorkletHost(); |
| |
| GURL iframe_url = https_server()->GetURL("a.test", "/empty.thml"); |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url); |
| |
| EXPECT_EQ(worklet_host->GetProcessHost(), |
| iframe_node->current_frame_host()->GetProcess()); |
| |
| EXPECT_EQ(blink::mojom::SharedStorageWorkletCreationMethod::kAddModule, |
| worklet_host->creation_method()); |
| } |
| |
| // Start a worklet with b.test script but a.test data, and then start a worklet |
| // under a.test's iframe. Assert that the data stored in the first worklet can |
| // be retrieved in the second worklet. |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| AddModule_CrossOriginScript_VerifyDataOrigin) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", net::test_server::GetFilePathWithReplacements( |
| "/shared_storage/module_with_custom_header.js", |
| SharedStorageCrossOriginWorkletResponseHeaderReplacement( |
| "Access-Control-Allow-Origin: *", |
| kEmptySharedStorageCrossOriginAllowedReplacement))); |
| |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("sharedStorage.worklet.addModule($1)", |
| module_script_url.spec()))); |
| |
| // Expect the run() operation. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('test-operation', { |
| data: { |
| 'set-key': 'key0', |
| 'set-value': 'value0' |
| }, |
| keepAlive: true |
| }); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| GURL iframe_url = https_server()->GetURL("a.test", "/empty.thml"); |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node->current_frame_host(), R"( |
| console.log(await sharedStorage.get('key0')); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("value0", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SharedStorageBrowserTest, |
| testing::Bool(), |
| describe_param); |
| |
| class SharedStorageAllowURNsInIframesBrowserTest |
| : public SharedStorageBrowserTestBase, |
| public testing::WithParamInterface<bool> { |
| public: |
| SharedStorageAllowURNsInIframesBrowserTest() { |
| allow_urns_in_frames_feature_.InitAndEnableFeature( |
| blink::features::kAllowURNsInIframes); |
| fenced_frame_api_change_feature_.InitWithFeatureState( |
| blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig()); |
| } |
| |
| bool ResolveSelectURLToConfig() override { return GetParam(); } |
| |
| private: |
| base::test::ScopedFeatureList allow_urns_in_frames_feature_; |
| base::test::ScopedFeatureList fenced_frame_api_change_feature_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageAllowURNsInIframesBrowserTest, |
| RenderSelectURLResultInIframe) { |
| GURL main_url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| url::Origin shared_storage_origin = |
| url::Origin::Create(https_server()->GetURL("b.test", kSimplePagePath)); |
| |
| std::optional<GURL> urn_uuid = |
| SelectFrom8URLsInContext(shared_storage_origin); |
| ASSERT_TRUE(urn_uuid.has_value()); |
| |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), *urn_uuid); |
| |
| EXPECT_EQ(iframe_node->current_url(), |
| https_server()->GetURL("b.test", "/fenced_frames/title1.html")); |
| |
| EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin), kBudgetAllowed); |
| |
| GURL new_page_url = https_server()->GetURL("c.test", kSimplePagePath); |
| |
| TestNavigationObserver top_navigation_observer(shell()->web_contents()); |
| EXPECT_TRUE( |
| ExecJs(iframe_node, JsReplace("top.location = $1", new_page_url))); |
| top_navigation_observer.Wait(); |
| |
| // After the top navigation, log(8)=3 bits should have been withdrawn from the |
| // original shared storage origin. |
| EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin), |
| kBudgetAllowed - 3); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SharedStorageAllowURNsInIframesBrowserTest, |
| testing::Bool(), |
| describe_param); |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| StringRoundTrip_SetThenGet_UnpairedSurrogatesArePreserved) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| GURL script_url = |
| https_server()->GetURL("a.test", "/shared_storage/round_trip.js"); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url))); |
| |
| std::vector<std::tuple<std::vector<uint16_t>, bool>> string_test_cases = { |
| {std::vector<uint16_t>({0x0068}), true}, // letter 'h' |
| {std::vector<uint16_t>({0xd800}), false}, // lone high surrogate |
| {std::vector<uint16_t>({0xdc00}), false}, // lone low surrogate |
| {std::vector<uint16_t>({0xd800, 0xdc00}), true}, // surrogate pair |
| }; |
| |
| for (size_t i = 0; i < string_test_cases.size(); ++i) { |
| const std::vector<uint16_t>& char_code_array = |
| std::get<0>(string_test_cases[i]); |
| std::u16string test_string = |
| std::u16string(char_code_array.begin(), char_code_array.end()); |
| base::Value::List char_code_values; |
| for (uint16_t char_code : char_code_array) { |
| char_code_values.Append(static_cast<int>(char_code)); |
| } |
| |
| const bool expected_valid = std::get<1>(string_test_cases[i]); |
| |
| // Check validity of UTF-16. |
| std::string base_output; |
| EXPECT_EQ(expected_valid, |
| base::UTF16ToUTF8(test_string.c_str(), test_string.size(), |
| &base_output)); |
| |
| // The dummy assignment is necessary, because otherwise under the hood, |
| // `ExecJs` makes a call that tries to evaluate the most recent script |
| // result as a `base::Value`, and `char_code_values` causes that to fail. |
| EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(window.charCodeArray = $1; |
| window.dummyAssignment = 0;)", |
| std::move(char_code_values)))); |
| |
| // We will wait for 1 "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('set-get-operation', |
| {data: {'key': 'asValue', |
| 'valueCharCodeArray' : charCodeArray}, |
| keepAlive: true}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(i + 1, console_observer.messages().size()); |
| EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages().back().message), |
| testing::HasSubstr("was retrieved: true")); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| StringRoundTrip_SetThenKeys_UnpairedSurrogatesArePreserved) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| GURL script_url = |
| https_server()->GetURL("a.test", "/shared_storage/round_trip.js"); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url))); |
| |
| std::vector<std::tuple<std::vector<uint16_t>, bool>> string_test_cases = { |
| {std::vector<uint16_t>({0x0068}), true}, // letter 'h' |
| {std::vector<uint16_t>({0xd800}), false}, // lone high surrogate |
| {std::vector<uint16_t>({0xdc00}), false}, // lone low surrogate |
| {std::vector<uint16_t>({0xd800, 0xdc00}), true}, // surrogate pair |
| }; |
| |
| for (size_t i = 0; i < string_test_cases.size(); ++i) { |
| const std::vector<uint16_t>& char_code_array = |
| std::get<0>(string_test_cases[i]); |
| std::u16string test_string = |
| std::u16string(char_code_array.begin(), char_code_array.end()); |
| base::Value::List char_code_values; |
| for (uint16_t char_code : char_code_array) { |
| char_code_values.Append(static_cast<int>(char_code)); |
| } |
| |
| const bool expected_valid = std::get<1>(string_test_cases[i]); |
| |
| // Check validity of UTF-16. |
| std::string base_output; |
| EXPECT_EQ(expected_valid, |
| base::UTF16ToUTF8(test_string.c_str(), test_string.size(), |
| &base_output)); |
| |
| // The dummy assignment is necessary, because otherwise under the hood, |
| // `ExecJs` makes a call that tries to evaluate the most recent script |
| // result as a `base::Value`, and `char_code_values` causes that to fail. |
| EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(window.charCodeArray = $1; |
| window.dummyAssignment = 0;)", |
| std::move(char_code_values)))); |
| |
| // We will wait for 1 "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('set-keys-operation', |
| {data: {'keyCharCodeArray' : charCodeArray, |
| 'value': 'asKey'}, |
| keepAlive: true}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(i + 1, console_observer.messages().size()); |
| EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages().back().message), |
| testing::HasSubstr("was retrieved: true")); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| StringRoundTrip_AppendThenDelete_UnpairedSurrogatesArePreserved) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| GURL script_url = |
| https_server()->GetURL("a.test", "/shared_storage/round_trip.js"); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url))); |
| |
| std::vector<std::tuple<std::vector<uint16_t>, bool>> string_test_cases = { |
| {std::vector<uint16_t>({0x0068}), true}, // letter 'h' |
| {std::vector<uint16_t>({0xd800}), false}, // lone high surrogate |
| {std::vector<uint16_t>({0xdc00}), false}, // lone low surrogate |
| {std::vector<uint16_t>({0xd800, 0xdc00}), true}, // surrogate pair |
| }; |
| |
| for (size_t i = 0; i < string_test_cases.size(); ++i) { |
| const std::vector<uint16_t>& char_code_array = |
| std::get<0>(string_test_cases[i]); |
| std::u16string test_string = |
| std::u16string(char_code_array.begin(), char_code_array.end()); |
| base::Value::List char_code_values; |
| for (uint16_t char_code : char_code_array) { |
| char_code_values.Append(static_cast<int>(char_code)); |
| } |
| |
| const bool expected_valid = std::get<1>(string_test_cases[i]); |
| |
| // Check validity of UTF-16. |
| std::string base_output; |
| EXPECT_EQ(expected_valid, |
| base::UTF16ToUTF8(test_string.c_str(), test_string.size(), |
| &base_output)); |
| |
| // The dummy assignment is necessary, because otherwise under the hood, |
| // `ExecJs` makes a call that tries to evaluate the most recent script |
| // result as a `base::Value`, and `char_code_values` causes that to fail. |
| EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(window.charCodeArray = $1; |
| window.dummyAssignment = 0;)", |
| std::move(char_code_values)))); |
| |
| // We will wait for 1 "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('append-delete-operation', |
| {data: {'keyCharCodeArray' : charCodeArray, |
| 'value': 'asKey'}, |
| keepAlive: true}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(i + 1, console_observer.messages().size()); |
| EXPECT_EQ(u"delete success: true", |
| console_observer.messages().back().message); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| StringRoundTrip_AppendThenEntries_UnpairedSurrogatesArePreserved) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| GURL script_url = |
| https_server()->GetURL("a.test", "/shared_storage/round_trip.js"); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url))); |
| |
| std::vector<std::tuple<std::vector<uint16_t>, bool>> string_test_cases = { |
| {std::vector<uint16_t>({0x0068}), true}, // letter 'h' |
| {std::vector<uint16_t>({0xd800}), false}, // lone high surrogate |
| {std::vector<uint16_t>({0xdc00}), false}, // lone low surrogate |
| {std::vector<uint16_t>({0xd800, 0xdc00}), true}, // surrogate pair |
| }; |
| |
| for (size_t i = 0; i < string_test_cases.size(); ++i) { |
| const std::vector<uint16_t>& char_code_array = |
| std::get<0>(string_test_cases[i]); |
| std::u16string test_string = |
| std::u16string(char_code_array.begin(), char_code_array.end()); |
| base::Value::List char_code_values; |
| for (uint16_t char_code : char_code_array) { |
| char_code_values.Append(static_cast<int>(char_code)); |
| } |
| |
| const bool expected_valid = std::get<1>(string_test_cases[i]); |
| |
| // Check validity of UTF-16. |
| std::string base_output; |
| EXPECT_EQ(expected_valid, |
| base::UTF16ToUTF8(test_string.c_str(), test_string.size(), |
| &base_output)); |
| |
| // The dummy assignment is necessary, because otherwise under the hood, |
| // `ExecJs` makes a call that tries to evaluate the most recent script |
| // result as a `base::Value`, and `char_code_values` causes that to fail. |
| EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(window.charCodeArray = $1; |
| window.dummyAssignment = 0;)", |
| std::move(char_code_values)))); |
| |
| // We will wait for 1 "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('append-entries-operation', |
| {data: {'key': 'asValue', |
| 'valueCharCodeArray': charCodeArray}, |
| keepAlive: true}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(i + 1, console_observer.messages().size()); |
| EXPECT_THAT(base::UTF16ToUTF8(console_observer.messages().back().message), |
| testing::HasSubstr("was retrieved: true")); |
| } |
| } |
| |
| class SharedStorageHeaderObserverBrowserTest |
| : public SharedStorageBrowserTestBase { |
| public: |
| using OperationResult = storage::SharedStorageManager::OperationResult; |
| |
| void FinishSetup() override { |
| https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath()); |
| https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| |
| auto observer = std::make_unique<TestSharedStorageHeaderObserver>( |
| GetStoragePartition()); |
| observer_ = observer->GetMutableWeakPtr(); |
| static_cast<StoragePartitionImpl*>(GetStoragePartition()) |
| ->OverrideSharedStorageHeaderObserverForTesting(std::move(observer)); |
| } |
| |
| bool NavigateToURLWithResponse( |
| Shell* window, |
| const GURL& url, |
| net::test_server::ControllableHttpResponse& response, |
| net::HttpStatusCode http_status, |
| const std::string& content_type = std::string("text/html"), |
| const std::string& content = std::string(), |
| const std::vector<std::string>& cookies = {}, |
| const std::vector<std::string>& extra_headers = {}) { |
| auto* web_contents = window->web_contents(); |
| DCHECK(web_contents); |
| |
| // Prepare for the navigation. |
| WaitForLoadStop(web_contents); |
| TestNavigationObserver same_tab_observer( |
| web_contents, |
| /*expected_number_of_navigations=*/1, |
| MessageLoopRunner::QuitMode::IMMEDIATE, |
| /*ignore_uncommitted_navigations=*/false); |
| if (!blink::IsRendererDebugURL(url)) { |
| same_tab_observer.set_expected_initial_url(url); |
| } |
| |
| // This mimics behavior of Shell::LoadURL... |
| NavigationController::LoadURLParams params(url); |
| params.transition_type = ui::PageTransitionFromInt( |
| ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); |
| web_contents->GetController().LoadURLWithParams(params); |
| web_contents->GetOutermostWebContents()->Focus(); |
| |
| response.WaitForRequest(); |
| |
| response.Send(http_status, content_type, content, cookies, extra_headers); |
| response.Done(); |
| |
| // Wait until the expected number of navigations finish. |
| same_tab_observer.Wait(); |
| |
| if (!IsLastCommittedEntryOfPageType(web_contents, PAGE_TYPE_NORMAL)) { |
| return false; |
| } |
| |
| bool is_same_url = web_contents->GetLastCommittedURL() == url; |
| if (!is_same_url) { |
| DLOG(WARNING) << "Expected URL " << url << " but observed " |
| << web_contents->GetLastCommittedURL(); |
| } |
| return is_same_url; |
| } |
| |
| std::string ReplacePortInString(std::string str) { |
| return ::content::ReplacePortInString(str, port()); |
| } |
| |
| void SetUpResponsesAndNavigateMainPage( |
| std::string main_hostname, |
| std::string subresource_or_subframe_hostname, |
| std::optional<std::string> shared_storage_permissions = std::nullopt, |
| bool is_image = false, |
| std::vector<std::string> redirect_hostnames = {}) { |
| subresource_or_subframe_content_type_ = |
| is_image ? "image/png" : "text/plain;charset=UTF-8"; |
| const char* subresource_or_subframe_path = |
| is_image ? kPngPath : kTitle1Path; |
| subresource_or_subframe_response_ = |
| std::make_unique<net::test_server::ControllableHttpResponse>( |
| https_server(), subresource_or_subframe_path); |
| |
| std::unique_ptr<net::test_server::ControllableHttpResponse> main_response; |
| |
| if (shared_storage_permissions.has_value()) { |
| main_response = |
| std::make_unique<net::test_server::ControllableHttpResponse>( |
| https_server(), kSimplePagePath); |
| } |
| |
| DCHECK_LT(redirect_hostnames.size(), 4u) |
| << "You need to add more paths to " |
| "`SetUpResponsesAndNavigateMainPage()`. Currently there are enough " |
| "for up to 3 redirects."; |
| std::vector<std::string> paths({kTitle2Path, kTitle3Path, kTitle4Path}); |
| for (size_t i = 0; i < redirect_hostnames.size(); i++) { |
| auto response = |
| std::make_unique<net::test_server::ControllableHttpResponse>( |
| https_server(), paths[i]); |
| redirected_responses_.push_back(std::move(response)); |
| } |
| |
| ASSERT_TRUE(https_server()->Start()); |
| |
| main_url_ = |
| https_server()->GetURL(std::move(main_hostname), kSimplePagePath); |
| subresource_or_subframe_url_ = |
| https_server()->GetURL(std::move(subresource_or_subframe_hostname), |
| subresource_or_subframe_path); |
| subresource_or_subframe_origin_ = |
| url::Origin::Create(subresource_or_subframe_url_); |
| for (size_t i = 0; i < redirect_hostnames.size(); i++) { |
| redirect_urls_.emplace_back( |
| https_server()->GetURL(redirect_hostnames[i], paths[i])); |
| redirect_origins_.emplace_back( |
| url::Origin::Create(redirect_urls_.back())); |
| } |
| |
| if (shared_storage_permissions.has_value()) { |
| EXPECT_TRUE(NavigateToURLWithResponse( |
| shell(), main_url_, *main_response, |
| /*http_status=*/net::HTTP_OK, |
| /*content_type=*/"text/plain;charset=UTF-8", |
| /*content=*/{}, /*cookies=*/{}, /*extra_headers=*/ |
| {"Permissions-Policy: shared-storage=" + |
| ReplacePortInString( |
| std::move(shared_storage_permissions.value()))})); |
| } else { |
| EXPECT_TRUE(NavigateToURL(shell(), main_url_)); |
| } |
| } |
| |
| void WaitForRequestAndSendResponse( |
| net::test_server::ControllableHttpResponse& response, |
| bool expect_writable_header, |
| net::HttpStatusCode http_status, |
| const std::string& content_type, |
| const std::vector<std::string>& extra_headers) { |
| response.WaitForRequest(); |
| if (expect_writable_header) { |
| ASSERT_TRUE(base::Contains(response.http_request()->headers, |
| "Sec-Shared-Storage-Writable")); |
| } else { |
| EXPECT_FALSE(base::Contains(response.http_request()->headers, |
| "Sec-Shared-Storage-Writable")); |
| } |
| EXPECT_EQ(response.http_request()->content, ""); |
| response.Send(http_status, content_type, |
| /*content=*/{}, /*cookies=*/{}, extra_headers); |
| response.Done(); |
| } |
| |
| void WaitForSubresourceOrSubframeRequestAndSendResponse( |
| bool expect_writable_header, |
| net::HttpStatusCode http_status, |
| const std::vector<std::string>& extra_headers) { |
| WaitForRequestAndSendResponse( |
| *subresource_or_subframe_response_, expect_writable_header, http_status, |
| subresource_or_subframe_content_type_, extra_headers); |
| } |
| |
| void WaitForRedirectRequestAndSendResponse( |
| bool expect_writable_header, |
| net::HttpStatusCode http_status, |
| const std::vector<std::string>& extra_headers, |
| size_t redirect_index = 0) { |
| ASSERT_LT(redirect_index, redirected_responses_.size()); |
| WaitForRequestAndSendResponse( |
| *redirected_responses_[redirect_index], expect_writable_header, |
| http_status, subresource_or_subframe_content_type_, extra_headers); |
| } |
| |
| void FetchWithSharedStorageWritable(const ToRenderFrameHost& execution_target, |
| const GURL& url) { |
| EXPECT_TRUE(ExecJs(execution_target, |
| JsReplace(R"( |
| fetch($1, {sharedStorageWritable: true}); |
| )", |
| url.spec()), |
| EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| } |
| |
| void StartServerAndLoadMainURLWithSameOriginSubresourceOrSubframe( |
| std::string main_hostname, |
| std::string main_path) { |
| ASSERT_TRUE(https_server()->Start()); |
| main_url_ = |
| https_server()->GetURL(std::move(main_hostname), std::move(main_path)); |
| subresource_or_subframe_origin_ = url::Origin::Create(main_url_); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url_)); |
| } |
| |
| void CreateSharedStorageWritableImage( |
| const ToRenderFrameHost& execution_target, |
| const GURL& url) { |
| EXPECT_TRUE(ExecJs(execution_target, JsReplace(R"( |
| let img = document.createElement('img'); |
| img.src = $1; |
| img.sharedStorageWritable = true; |
| document.body.appendChild(img); |
| )", |
| url.spec()))); |
| } |
| |
| void CreateSharedStorageWritableIframe( |
| const ToRenderFrameHost& execution_target, |
| const GURL& url) { |
| EXPECT_TRUE(ExecJs(execution_target, JsReplace(R"( |
| let frame = document.createElement('iframe'); |
| frame.sharedStorageWritable = true; |
| frame.src = $1; |
| document.body.appendChild(frame); |
| )", |
| url.spec()))); |
| } |
| |
| protected: |
| base::WeakPtr<TestSharedStorageHeaderObserver> observer_; |
| std::unique_ptr<net::test_server::ControllableHttpResponse> |
| subresource_or_subframe_response_; |
| std::vector<std::unique_ptr<net::test_server::ControllableHttpResponse>> |
| redirected_responses_; |
| GURL main_url_; |
| GURL subresource_or_subframe_url_; |
| std::vector<GURL> redirect_urls_; |
| url::Origin subresource_or_subframe_origin_; |
| std::vector<url::Origin> redirect_origins_; |
| std::string subresource_or_subframe_content_type_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_SameOrigin_PermissionsDefault_ClearSetAppend) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_SameOrigin_PermissionsNone) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/"()"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=wont;key=set"}); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_SameOrigin_PermissionsAll_ClearSetAppend) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/"*"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_SameOrigin_PermissionsSelf_ClearSetAppend) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/"self"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_CrossOrigin_PermissionsDefault_ClearSetAppend) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| // Create an iframe that's same-origin to the fetch URL. |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), |
| https_server()->GetURL("b.test", kTitle2Path)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_CrossOrigin_PermissionsNone) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/"()"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=wont;key=set"}); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_CrossOrigin_PermissionsAll_ClearSetAppend) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/"*"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| |
| // Create an iframe that's same-origin to the fetch URL. |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), |
| https_server()->GetURL("b.test", kTitle2Path)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_CrossOrigin_PermissionsSelf) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/"self"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=wont;key=set"}); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Fetch_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteInitial) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options1; |
| methods_with_options1.push_back(MojomClearMethod()); |
| methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options1.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options1))); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *"}); |
| |
| // There won't be additional operations invoked. |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options2; |
| methods_with_options2.push_back(MojomClearMethod()); |
| methods_with_options2.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options2.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options2))); |
| |
| // Create an iframe that's same-origin to the original fetch URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node1, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Fetch_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteFinal) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *"}); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back()); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options))); |
| |
| // Create an iframe that's same-origin to the original fetch URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node1, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| |
| // Nothing was set in b.test's shared storage. |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // Create an iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2u); |
| |
| // The entry was set in c.test's shared storage. |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Fetch_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteBoth) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options1; |
| methods_with_options1.push_back(MojomClearMethod()); |
| methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options1.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options1))); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}); |
| |
| // There will now have been a total of 2 operations (1 previous, 1 current). |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(2); |
| |
| EXPECT_EQ(observer_->header_results().size(), 2u); |
| EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back()); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options2; |
| methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 2u); |
| EXPECT_EQ(observer_->operations()[1], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options2))); |
| |
| // Create an iframe that's same-origin to the original fetch URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node1, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| // Only one entry was set in b.test's shared storage. |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| // Create an iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2u); |
| |
| // One entry was set in c.test's shared storage. |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_CrossOrigin_Redirect_InititalAllowed_FinalDenied) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options1; |
| methods_with_options1.push_back(MojomClearMethod()); |
| methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options1.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options1))); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=wont;key=set"}); |
| |
| // No new operations are invoked. |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options2; |
| methods_with_options2.push_back(MojomClearMethod()); |
| methods_with_options2.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options2.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options2))); |
| |
| // Create an iframe that's same-origin to the original fetch URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node1, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // Create an iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| EXPECT_THAT(EvalJs(iframe_node2, R"( |
| sharedStorage.worklet.addModule('/shared_storage/simple_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "The \"shared-storage\" Permissions Policy " |
| "denied the method"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Fetch_CrossOrigin_Redirect_InititalAllowed_IntermediateDenied_FinalAllowed_WriteInitialAndFinal) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://d.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test", "d.test"})); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.front().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options1; |
| methods_with_options1.push_back(MojomClearMethod()); |
| methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options1.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options1))); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_TEMPORARY_REDIRECT, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: append;key=wont;value=set"}, |
| /*redirect_index=*/0); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}, |
| /*redirect_index=*/1); |
| |
| // There will now have been a total of 2 operations (1 previous, 1 current). |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(2); |
| |
| EXPECT_EQ(observer_->header_results().size(), 2u); |
| EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back()); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options2; |
| methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 2u); |
| EXPECT_EQ(observer_->operations()[1], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options2))); |
| |
| // Create an iframe that's same-origin to the original fetch URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node1, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| // Only one entry was set in b.test's shared storage. |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| // Create an iframe that's same-origin to the first redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.front()); |
| |
| // c.test does not have permission to use shared storage. |
| EXPECT_THAT(EvalJs(iframe_node2, R"( |
| sharedStorage.worklet.addModule('/shared_storage/simple_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "The \"shared-storage\" Permissions Policy " |
| "denied the method"))); |
| |
| // Create an iframe that's same-origin to the second redirect URL. |
| FrameTreeNode* iframe_node3 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| ExecuteScriptInWorklet(iframe_node3, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2u); |
| |
| // One entry was set in d.test's shared storage. |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Fetch_CrossOrigin_Redirect_InitialDenied_FinalAllowed_WriteFinal) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://c.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| |
| // No operations are invoked. |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}); |
| |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), redirect_origins_.back()); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| // Create an iframe that's same-origin to the original fetch URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| EXPECT_THAT(EvalJs(iframe_node1, R"( |
| sharedStorage.worklet.addModule('/shared_storage/simple_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "The \"shared-storage\" Permissions Policy " |
| "denied the method"))); |
| |
| // Create an iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| // one key was set in c.test's shared storage. |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_SameOrigin_PermissionsDefault_VerifyDelete) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test"); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| EXPECT_TRUE(ExecJs(shell(), R"(sharedStorage.set('hello', 'world');)")); |
| |
| GURL script_url = |
| https_server()->GetURL("a.test", "/shared_storage/getter_module.js"); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url))); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('get-operation', {data: {'key': 'hello'}, |
| keepAlive: true}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("sharedStorage.length(): 1", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("sharedStorage.get('hello'): world", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: \"delete\";key=hello"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"hello")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('get-operation', {data: {'key': 'hello'}}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("sharedStorage.length(): 0", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("sharedStorage.get('hello'): undefined", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_SameOrigin_PermissionsDefault_VerifyClear) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test"); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| EXPECT_TRUE(ExecJs(shell(), R"(sharedStorage.set('hello', 'world');)")); |
| |
| GURL script_url = |
| https_server()->GetURL("a.test", "/shared_storage/getter_module.js"); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), JsReplace("sharedStorage.worklet.addModule($1)", script_url))); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('get-operation', {data: {'key': 'hello'}, |
| keepAlive: true}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("sharedStorage.length(): 1", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("sharedStorage.get('hello'): world", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: \"clear\""}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| |
| // There is 1 more "worklet operation": `run()`. |
| test_runtime_manager() |
| .GetAttachedWorkletHost() |
| ->SetExpectedWorkletResponsesCount(1); |
| |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| sharedStorage.run('get-operation', {data: {'key': 'hello'}}); |
| )")); |
| |
| test_runtime_manager().GetAttachedWorkletHost()->WaitForWorkletResponses(); |
| |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("sharedStorage.length(): 0", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("sharedStorage.get('hello'): undefined", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Fetch_SameOrigin_PermissionsDefault_MultipleSet_Bytes) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: set;key=:aGVsbG8=:;value=:d29ybGQ=:, " |
| "set;value=:ZnJpZW5k:;key=:aGVsbG8=:;ignore_if_present=?0, " |
| "set;ignore_if_present;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/false)); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"friend", |
| /*ignore_if_present=*/false)); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"there", |
| /*ignore_if_present=*/true)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("friend", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| NetworkServiceRestarts_HeaderObserverContinuesWorking) { |
| subresource_or_subframe_response_ = |
| std::make_unique<net::test_server::ControllableHttpResponse>( |
| https_server(), kTitle1Path); |
| ASSERT_TRUE(https_server()->Start()); |
| |
| if (IsInProcessNetworkService()) { |
| return; |
| } |
| |
| main_url_ = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url_)); |
| ASSERT_TRUE(observer_); |
| |
| SimulateNetworkServiceCrash(); |
| static_cast<StoragePartitionImpl*>(GetStoragePartition()) |
| ->FlushNetworkInterfaceForTesting(); |
| |
| // We should still have an `observer_`. |
| ASSERT_TRUE(observer_); |
| |
| // We need to reinitialize and renavigate to `main_url_` after network service |
| // restart, if we want to prevent the shared storage operations below from |
| // being deferred then dropped due to switching to a new |
| // `NavigationOrDocumentHandle` for the main frame which hasn't yet seen a |
| // commit. |
| main_url_ = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url_)); |
| |
| // We will still have an `observer_` after renavigating. |
| ASSERT_TRUE(observer_); |
| |
| // We also need to reinitialize `subresource_or_subframe_url_` after network |
| // service restart. Fetching with `sharedStorageWritable` works as expected. |
| subresource_or_subframe_url_ = https_server()->GetURL("a.test", kTitle1Path); |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| subresource_or_subframe_origin_ = |
| url::Origin::Create(subresource_or_subframe_url_); |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| InvalidHeader_NoOperationsInvoked) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: clear, invalid?item"}); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| ParsableUnrecognizedItemSkipped_RecognizedOperationsInvoked) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: clear, unrecognized;unknown_param=1," |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| ExtraParametersIgnored) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: clear;unknown_param=1," |
| "set;another_unknown=willIgnore;key=\"hello\";value=\"world\", " |
| "append;key=extra;key=hello;value=there;ignore_if_present;pi=3.14," |
| "delete;value=ignored;key=toDelete;ignore_if_present=?0"}); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/false)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"toDelete")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| KeyOrValueLengthInvalid_ItemSkipped) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test"); |
| |
| FetchWithSharedStorageWritable(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {base::StrCat({"Shared-Storage-Write: clear, ", |
| "set;key=\"\";value=v,append;key=\"\";value=v,", |
| "delete;key=\"\",clear"})}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomClearMethod()); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Image_SameOrigin_PermissionsDefault) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/std::nullopt, |
| /*is_image=*/true); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: set;key=a;value=b"}); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Image_SameOrigin_PermissionsNone) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/"()", |
| /*is_image=*/true); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Image_SameOrigin_PermissionsAll) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/"*", |
| /*is_image=*/true); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Image_SameOrigin_PermissionsSelf) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/"self", |
| /*is_image=*/true); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Image_CrossOrigin_PermissionsDefault) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/std::nullopt, |
| /*is_image=*/true); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: set;key=a;value=b"}); |
| |
| // Create iframe that's same-origin to the image URL. |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), |
| https_server()->GetURL("b.test", kTitle2Path)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node, R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Image_CrossOrigin_PermissionsNone) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/"()", |
| /*is_image=*/true); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Image_CrossOrigin_PermissionsAll) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/"*", |
| /*is_image=*/true); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| // Create iframe that's same-origin to the image URL. |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), |
| https_server()->GetURL("b.test", kTitle2Path)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node, R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Image_CrossOrigin_PermissionsSelf) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/"self", |
| /*is_image=*/true); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Image_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteInitial) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")", |
| /*is_image=*/true, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *"}); |
| |
| // Create an iframe that's same-origin to the original image URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // There won't be additional operations invoked from the redirect, just the |
| // original 3. |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Image_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteFinal) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")", |
| /*is_image=*/true, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *"}); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back()); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options))); |
| |
| // Create an iframe that's same-origin to the original image URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node1, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| |
| // Nothing was set in b.test's shared storage. |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // Create another iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2u); |
| |
| // The entry was set in c.test's shared storage. |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Image_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteBoth) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")", |
| /*is_image=*/true, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}); |
| |
| // There will now have been a total of 2 operations (1 previous, 1 current). |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(2); |
| |
| EXPECT_EQ(observer_->header_results().size(), 2u); |
| EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back()); |
| EXPECT_EQ(observer_->operations().size(), 2u); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options1; |
| methods_with_options1.push_back(MojomClearMethod()); |
| methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options1.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options1))); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options2; |
| methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations()[1], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options2))); |
| |
| // Create an iframe that's same-origin to the original image URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node1, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| // Only one entry was set in b.test's shared storage. |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| // Create another iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2u); |
| |
| // One entry was set in c.test's shared storage. |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Image_CrossOrigin_Redirect_InititalAllowed_FinalDenied) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\")", |
| /*is_image=*/true, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=wont;key=set"}); |
| |
| // There won't be additional operations invoked from the redirect, just the |
| // original 3. |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| |
| // Create an iframe that's same-origin to the original image URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node1, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // Create another iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| EXPECT_THAT(EvalJs(iframe_node2, R"( |
| sharedStorage.worklet.addModule('/shared_storage/simple_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "The \"shared-storage\" Permissions Policy " |
| "denied the method"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Image_CrossOrigin_Redirect_InititalAllowed_IntermediateDenied_FinalAllowed_WriteInitialAndFinal) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://d.test:{{port}}\")", |
| /*is_image=*/true, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test", "d.test"})); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.front().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options1; |
| methods_with_options1.push_back(MojomClearMethod()); |
| methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options1.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options1))); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_TEMPORARY_REDIRECT, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: append;key=wont;value=set"}, |
| /*redirect_index=*/0); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}, |
| /*redirect_index=*/1); |
| |
| // There will now have been a total of 2 operations (1 previous, 1 current). |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(2); |
| |
| EXPECT_EQ(observer_->header_results().size(), 2u); |
| EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back()); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options2; |
| methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 2u); |
| EXPECT_EQ(observer_->operations()[1], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options2))); |
| |
| // Create an iframe that's same-origin to the original image URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node1, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| // Only one entry was set in b.test's shared storage. |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| // Create an iframe that's same-origin to the first redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.front()); |
| |
| // c.test does not have permission to use shared storage. |
| EXPECT_THAT(EvalJs(iframe_node2, R"( |
| sharedStorage.worklet.addModule('/shared_storage/simple_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "The \"shared-storage\" Permissions Policy " |
| "denied the method"))); |
| |
| // Create an iframe that's same-origin to the second redirect URL. |
| FrameTreeNode* iframe_node3 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| ExecuteScriptInWorklet(iframe_node3, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2u); |
| |
| // One entry was set in d.test's shared storage. |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Image_CrossOrigin_Redirect_InitialDenied_FinalAllowed_WriteFinal) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://c.test:{{port}}\")", |
| /*is_image=*/true, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| CreateSharedStorageWritableImage(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| |
| // No operations are invoked. |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}); |
| |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), redirect_origins_.back()); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| // Create an iframe that's same-origin to the original image URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| EXPECT_THAT(EvalJs(iframe_node1, R"( |
| sharedStorage.worklet.addModule('/shared_storage/simple_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "The \"shared-storage\" Permissions Policy " |
| "denied the method"))); |
| |
| // Create an iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| // one key was set in c.test's shared storage. |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Image_ContentAttributeIncluded_Set_2ndImageCached_NotSet) { |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| StartServerAndLoadMainURLWithSameOriginSubresourceOrSubframe( |
| /*main_hostname=*/"a.test", |
| /*main_path=*/ |
| "/shared_storage/page-with-shared-storage-writable-image.html"); |
| |
| // Wait for the image onload to fire. |
| EXPECT_TRUE(console_observer.Wait()); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("Image Loaded", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options1; |
| methods_with_options1.push_back( |
| MojomAppendMethod(/*key=*/u"a", /*value=*/u"b")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options1))); |
| |
| EXPECT_EQ( |
| true, |
| EvalJs( |
| shell(), |
| JsReplace( |
| R"( |
| new Promise((resolve, reject) => { |
| let img = document.createElement('img'); |
| img.src = $1; |
| img.onload = () => resolve(true); |
| img.sharedStorageWritable = true; |
| document.body.appendChild(img); |
| }) |
| )", |
| https_server() |
| ->GetURL("a.test", |
| "/shared_storage/shared-storage-writable-pixel.png") |
| .spec()))); |
| |
| // Create an iframe that's same-origin in order to run a second worklet. |
| FrameTreeNode* iframe_node = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), main_url_); |
| |
| ExecuteScriptInWorklet(iframe_node, R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2u); |
| |
| // The value 'b' for the key 'a' is unchanged (nothing is appended to it). |
| EXPECT_EQ(6u, console_observer.messages().size()); |
| EXPECT_EQ("Image Loaded", |
| base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[5].message)); |
| |
| // No new operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options2; |
| methods_with_options2.push_back( |
| MojomAppendMethod(/*key=*/u"a", /*value=*/u"b")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options2))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Image_ContentAttributeNotIncluded_NotSet) { |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| StartServerAndLoadMainURLWithSameOriginSubresourceOrSubframe( |
| /*main_hostname=*/"a.test", |
| /*main_path=*/ |
| "/shared_storage/page-with-non-shared-storage-writable-image.html"); |
| |
| // Wait for the image onload to fire. |
| EXPECT_TRUE(console_observer.Wait()); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("Image Loaded", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Iframe_SameOrigin_PermissionsDefault) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/std::nullopt, |
| /*is_image=*/false); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: set;key=a;value=b"}); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Iframe_SameOrigin_PermissionsNone) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/"()", |
| /*is_image=*/false); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Iframe_SameOrigin_PermissionsAll) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/"*", |
| /*is_image=*/false); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Iframe_SameOrigin_PermissionsSelf) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"a.test", |
| /*shared_storage_permissions=*/"self", |
| /*is_image=*/false); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Iframe_CrossOrigin_PermissionsDefault) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/std::nullopt, |
| /*is_image=*/false); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Shared-Storage-Write: set;key=a;value=b"}); |
| |
| // Create another iframe that's same-origin to the first iframe URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), |
| https_server()->GetURL("b.test", kTitle2Path)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Iframe_CrossOrigin_PermissionsNone) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/"()", |
| /*is_image=*/false); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Iframe_CrossOrigin_PermissionsAll) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/"*", |
| /*is_image=*/false); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| // Create another iframe that's same-origin to the first iframe URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), |
| https_server()->GetURL("b.test", kTitle2Path)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Iframe_CrossOrigin_PermissionsSelf) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/"self", |
| /*is_image=*/false); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: set;key=a;value=b"}); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Iframe_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteInitial) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *"}); |
| |
| // Create another iframe that's same-origin to the original iframe URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // There won't be additional operations invoked from the redirect, just the |
| // original 3. |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Iframe_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteFinal) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *"}); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back()); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options))); |
| |
| // Create another iframe that's same-origin to the original iframe URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| |
| // Nothing was set in b.test's shared storage. |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // Create another iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node3 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| ExecuteScriptInWorklet(iframe_node3, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2u); |
| |
| // The entry was set in c.test's shared storage. |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Iframe_CrossOrigin_Redirect_InititalAllowed_FinalAllowed_WriteBoth) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://c.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}); |
| |
| // There will now have been a total of 2 operations (1 previous, 1 current). |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(2); |
| |
| EXPECT_EQ(observer_->header_results().size(), 2u); |
| EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back()); |
| EXPECT_EQ(observer_->operations().size(), 2u); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options1; |
| methods_with_options1.push_back(MojomClearMethod()); |
| methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options1.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options1))); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options2; |
| methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations()[1], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options2))); |
| |
| // Create another iframe that's same-origin to the original iframe URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| // Only one entry was set in b.test's shared storage. |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| // Create another iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node3 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| ExecuteScriptInWorklet(iframe_node3, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2u); |
| |
| // One entry was set in c.test's shared storage. |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Iframe_CrossOrigin_Redirect_InititalAllowed_FinalDenied) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=wont;key=set"}); |
| |
| // There won't be additional operations invoked from the redirect, just the |
| // original 3. |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomClearMethod()); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| |
| // Create an iframe that's same-origin to the original iframe URL. |
| FrameTreeNode* iframe_node1 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node1, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| |
| // Create another iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| EXPECT_THAT(EvalJs(iframe_node2, R"( |
| sharedStorage.worklet.addModule('/shared_storage/simple_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "The \"shared-storage\" Permissions Policy " |
| "denied the method"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Iframe_CrossOrigin_Redirect_InititalAllowed_IntermediateDenied_FinalAllowed_WriteInitialAndFinal) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://b.test:{{port}}\" \"https://d.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test", "d.test"})); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.front().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options1; |
| methods_with_options1.push_back(MojomClearMethod()); |
| methods_with_options1.push_back(MojomSetMethod(/*key=*/u"hello", |
| /*value=*/u"world", |
| /*ignore_if_present=*/true)); |
| methods_with_options1.push_back( |
| MojomAppendMethod(/*key=*/u"hello", /*value=*/u"there")); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options1))); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_TEMPORARY_REDIRECT, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: append;key=wont;value=set"}, |
| /*redirect_index=*/0); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}, |
| /*redirect_index=*/1); |
| |
| // There will now have been a total of 2 operations (1 previous, 1 current). |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(2); |
| |
| EXPECT_EQ(observer_->header_results().size(), 2u); |
| EXPECT_EQ(observer_->header_results().back(), redirect_origins_.back()); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options2; |
| methods_with_options2.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options2.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 2u); |
| EXPECT_EQ(observer_->operations()[1], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options2))); |
| |
| // Create an iframe that's same-origin to the original iframe URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node2, R"( |
| console.log(await sharedStorage.get('hello')); |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("worldthere", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| // Only one entry was set in b.test's shared storage. |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| // Create an iframe that's same-origin to the first redirect URL. |
| FrameTreeNode* iframe_node3 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.front()); |
| |
| // c.test does not have permission to use shared storage. |
| EXPECT_THAT(EvalJs(iframe_node3, R"( |
| sharedStorage.worklet.addModule('/shared_storage/simple_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "The \"shared-storage\" Permissions Policy " |
| "denied the method"))); |
| |
| // Create an iframe that's same-origin to the second redirect URL. |
| FrameTreeNode* iframe_node4 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| ExecuteScriptInWorklet(iframe_node4, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url, /*expected_total_host_count=*/2u); |
| |
| // One entry was set in d.test's shared storage. |
| EXPECT_EQ(5u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[4].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SharedStorageHeaderObserverBrowserTest, |
| Iframe_CrossOrigin_Redirect_InitialDenied_FinalAllowed_WriteFinal) { |
| SetUpResponsesAndNavigateMainPage( |
| /*main_hostname=*/"a.test", |
| /*subresource_or_subframe_hostname=*/"b.test", |
| /*shared_storage_permissions=*/ |
| "(self \"https://c.test:{{port}}\")", |
| /*is_image=*/false, |
| /*redirect_hostnames=*/std::vector<std::string>({"c.test"})); |
| |
| CreateSharedStorageWritableIframe(shell(), subresource_or_subframe_url_); |
| |
| WaitForSubresourceOrSubframeRequestAndSendResponse( |
| /*expect_writable_header=*/false, |
| /*http_status=*/net::HTTP_FOUND, |
| /*extra_headers=*/ |
| {base::StrCat({"Location: ", redirect_urls_.back().spec()}), |
| "Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: clear, " |
| "set;key=\"hello\";value=\"world\";ignore_if_present, " |
| "append;key=hello;value=there"}); |
| |
| ASSERT_TRUE(observer_); |
| |
| // No operations are invoked. |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| |
| WaitForRedirectRequestAndSendResponse( |
| /*expect_writable_header=*/true, |
| /*http_status=*/net::HTTP_OK, |
| /*extra_headers=*/ |
| {"Access-Control-Allow-Origin: *", |
| "Shared-Storage-Write: delete;key=a, set;value=will;key=set"}); |
| |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), redirect_origins_.back()); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomDeleteMethod(/*key=*/u"a")); |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"set", |
| /*value=*/u"will", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(redirect_origins_.back(), |
| std::move(methods_with_options))); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| // Create an iframe that's same-origin to the original iframe URL. |
| FrameTreeNode* iframe_node2 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), subresource_or_subframe_url_); |
| |
| EXPECT_THAT(EvalJs(iframe_node2, R"( |
| sharedStorage.worklet.addModule('/shared_storage/simple_module.js'); |
| )"), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "The \"shared-storage\" Permissions Policy " |
| "denied the method"))); |
| |
| // Create an iframe that's same-origin to the redirect URL. |
| FrameTreeNode* iframe_node3 = |
| CreateIFrame(PrimaryFrameTreeNodeRoot(), redirect_urls_.back()); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(iframe_node3, R"( |
| console.log(await sharedStorage.get('set')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| // one key was set in c.test's shared storage. |
| EXPECT_EQ(2u, console_observer.messages().size()); |
| EXPECT_EQ("will", base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Iframe_ContentAttributeIncluded_Set) { |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| StartServerAndLoadMainURLWithSameOriginSubresourceOrSubframe( |
| /*main_hostname=*/"a.test", |
| /*main_path=*/ |
| "/shared_storage/page-with-shared-storage-writable-iframe.html"); |
| |
| EXPECT_TRUE(console_observer.Wait()); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("Iframe Loaded", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("b", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| ASSERT_TRUE(observer_); |
| observer_->WaitForOperations(1); |
| |
| EXPECT_EQ(observer_->header_results().size(), 1u); |
| EXPECT_EQ(observer_->header_results().front(), |
| subresource_or_subframe_origin_); |
| |
| std::vector<MethodWithOptionsPtr> methods_with_options; |
| methods_with_options.push_back(MojomSetMethod(/*key=*/u"a", /*value=*/u"b", |
| /*ignore_if_present=*/false)); |
| EXPECT_EQ(observer_->operations().size(), 1u); |
| EXPECT_EQ(observer_->operations()[0], |
| HeaderOperationSuccess(subresource_or_subframe_origin_, |
| std::move(methods_with_options))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SharedStorageHeaderObserverBrowserTest, |
| Iframe_ContentAttributeNotIncluded_NotSet) { |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| StartServerAndLoadMainURLWithSameOriginSubresourceOrSubframe( |
| /*main_hostname=*/"a.test", |
| /*main_path=*/ |
| "/shared_storage/page-with-non-shared-storage-writable-iframe.html"); |
| |
| EXPECT_TRUE(console_observer.Wait()); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ("Iframe Loaded", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorklet(shell(), R"( |
| console.log(await sharedStorage.get('a')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url); |
| |
| EXPECT_EQ(3u, console_observer.messages().size()); |
| EXPECT_EQ("undefined", |
| base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("0", base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| |
| // No operations are invoked. |
| ASSERT_TRUE(observer_); |
| EXPECT_TRUE(observer_->header_results().empty()); |
| EXPECT_TRUE(observer_->operations().empty()); |
| } |
| |
| class SharedStorageCreateWorkletCustomDataOriginBrowserTest |
| : public SharedStorageBrowserTest { |
| public: |
| ~SharedStorageCreateWorkletCustomDataOriginBrowserTest() override = default; |
| |
| std::vector<base::Value> BuildWellKnownTrustedOriginsLists() override { |
| std::vector<base::Value> trusted_origins_lists; |
| // We expect failure for script with origin "https://b.test:{{port}}" and |
| // context origin "https://a.test:{{port}}" when one of the following values |
| // is served. |
| trusted_origins_lists.push_back(static_cast<base::Value>( |
| base::Value::Dict() |
| .Set("scriptOrigin", "https://b.test:{{port}}") |
| .Set("contextOrigin", "https://a.test:{{port}}"))); |
| trusted_origins_lists.push_back( |
| static_cast<base::Value>(base::Value::List())); |
| trusted_origins_lists.push_back(static_cast<base::Value>( |
| base::Value::List().Append(base::Value::List() |
| .Append("https://b.test:{{port}}") |
| .Append("https://a.test:{{port}}")))); |
| trusted_origins_lists.push_back(static_cast<base::Value>( |
| base::Value::List().Append(base::Value::Dict().Set( |
| "contextOrigin", "https://a.test:{{port}}")))); |
| trusted_origins_lists.push_back( |
| static_cast<base::Value>(base::Value::List().Append( |
| base::Value::Dict() |
| .Set("scriptOrigin", base::Value::List()) |
| .Set("contextOrigin", "https://a.test:{{port}}")))); |
| trusted_origins_lists.push_back(static_cast<base::Value>( |
| base::Value::List().Append(base::Value::Dict().Set( |
| "scriptOrigin", "https://b.test:{{port}}")))); |
| trusted_origins_lists.push_back( |
| static_cast<base::Value>(base::Value::List().Append( |
| base::Value::Dict() |
| .Set("scriptOrigin", "https://b.test:{{port}}") |
| .Set("contextOrigin", base::Value::List())))); |
| trusted_origins_lists.push_back(static_cast<base::Value>( |
| base::Value::List() |
| .Append(base::Value::Dict() |
| .Set("scriptOrigin", "https://a.test:{{port}}") |
| .Set("contextOrigin", "*")) |
| .Append(base::Value::Dict() |
| .Set("scriptOrigin", "*") |
| .Set("contextOrigin", "https://b.test:{{port}}")))); |
| // We expect success for script with origin "https://b.test:{{port}}" and |
| // context origin "https://a.test:{{port}}" when one of the following values |
| // is served. |
| trusted_origins_lists.push_back( |
| static_cast<base::Value>(base::Value::List().Append( |
| base::Value::Dict() |
| .Set("scriptOrigin", "https://b.test:{{port}}") |
| .Set("contextOrigin", "https://a.test:{{port}}")))); |
| trusted_origins_lists.push_back( |
| static_cast<base::Value>(base::Value::List().Append( |
| base::Value::Dict() |
| .Set("scriptOrigin", "https://b.test:{{port}}") |
| .Set("contextOrigin", "*")))); |
| trusted_origins_lists.push_back( |
| static_cast<base::Value>(base::Value::List().Append( |
| base::Value::Dict() |
| .Set("scriptOrigin", "*") |
| .Set("contextOrigin", "https://a.test:{{port}}")))); |
| trusted_origins_lists.push_back( |
| static_cast<base::Value>(base::Value::List().Append( |
| base::Value::Dict() |
| .Set("scriptOrigin", base::Value::List() |
| .Append("https://x.test:{{port}}") |
| .Append("https://b.test:{{port}}")) |
| .Set("contextOrigin", |
| base::Value::List() |
| .Append("https://a.test:{{port}}") |
| .Append("https://y.test:{{port}}"))))); |
| trusted_origins_lists.push_back( |
| static_cast<base::Value>(base::Value::List().Append( |
| base::Value::Dict() |
| .Set("scriptOrigin", base::Value::List() |
| .Append("https://b.test:{{port}}") |
| .Append("https://y.test:{{port}}") |
| .Append("https://x.test:{{port}}")) |
| .Set("contextOrigin", |
| base::Value::List() |
| .Append("https://y.test:{{port}}") |
| .Append("*") |
| .Append("https://z.test:{{port}}"))))); |
| trusted_origins_lists.push_back( |
| static_cast<base::Value>(base::Value::List().Append( |
| base::Value::Dict() |
| .Set("scriptOrigin", base::Value::List() |
| .Append("https://x.test:{{port}}") |
| .Append("https://y.test:{{port}}") |
| .Append("*")) |
| .Set("contextOrigin", |
| base::Value::List() |
| .Append("https://y.test:{{port}}") |
| .Append("https://a.test:{{port}}"))))); |
| return trusted_origins_lists; |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| testing::Bool(), |
| describe_param); |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Failure_ServerError) { |
| set_force_server_error(true); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize())), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "no response, an invalid response, or an unexpected mime type"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Failure_NotAList) { |
| set_trusted_origins_list_index(0); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize())), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "because there was no parse result or the result was not a list"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Failure_EmptyList) { |
| set_trusted_origins_list_index(1); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize())), |
| EvalJsResult::ErrorIs(testing::HasSubstr("is an empty list"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Failure_NonDictItem) { |
| set_trusted_origins_list_index(2); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize())), |
| EvalJsResult::ErrorIs( |
| testing::HasSubstr("non-dictionary item was encountered"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Failure_ScriptOriginKeyNotFound) { |
| set_trusted_origins_list_index(3); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize())), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "dictionary item's `scriptOrigin` key was not found"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Failure_ScriptOriginValueEmptyList) { |
| set_trusted_origins_list_index(4); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize())), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "`scriptOrigin` key was not found, or its value was an empty list"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Failure_ContextOriginKeyNotFound) { |
| set_trusted_origins_list_index(5); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize())), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "dictionary item's `contextOrigin` key was not found"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Failure_ContextOriginValueEmptyList) { |
| set_trusted_origins_list_index(6); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize())), |
| EvalJsResult::ErrorIs(testing::HasSubstr( |
| "`contextOrigin` key was not found, or its value was " |
| "an empty list"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Failure_NotAllowed) { |
| set_trusted_origins_list_index(7); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_THAT( |
| EvalJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize())), |
| EvalJsResult::ErrorIs(testing::HasSubstr("has not been allowed"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Success_FullySpecified) { |
| set_trusted_origins_list_index(8); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize()))); |
| |
| std::string custom_data_origin_str = custom_data_origin.Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| custom_data_origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| module_script_url, custom_data_origin_str, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Success_SpecifiedScriptOrigin_WildcardContextOrigin) { |
| set_trusted_origins_list_index(9); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize()))); |
| |
| std::string custom_data_origin_str = custom_data_origin.Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| custom_data_origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| module_script_url, custom_data_origin_str, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Success_WildcardScriptOrigin_SpecifiedContextOrigin) { |
| set_trusted_origins_list_index(10); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize()))); |
| |
| std::string custom_data_origin_str = custom_data_origin.Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| custom_data_origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| module_script_url, custom_data_origin_str, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Success_Lists_FullySpecified) { |
| set_trusted_origins_list_index(11); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize()))); |
| |
| std::string custom_data_origin_str = custom_data_origin.Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| custom_data_origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| module_script_url, custom_data_origin_str, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Success_Lists_SpecifiedScriptOrigin_WildcardContextOrigin) { |
| set_trusted_origins_list_index(12); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize()))); |
| |
| std::string custom_data_origin_str = custom_data_origin.Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| custom_data_origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| module_script_url, custom_data_origin_str, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageCreateWorkletCustomDataOriginBrowserTest, |
| CrossOriginScript_Success_Lists_WildcardScriptOrigin_SpecifiedContextOrigin) { |
| set_trusted_origins_list_index(13); |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| GURL module_script_url = https_server()->GetURL( |
| "b.test", "/shared_storage/module_with_cors_header.js"); |
| |
| url::Origin custom_data_origin = |
| url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath)); |
| |
| EXPECT_TRUE(ExecJs( |
| shell(), |
| JsReplace("sharedStorage.createWorklet($1, {dataOrigin: $2})", |
| module_script_url.spec(), custom_data_origin.Serialize()))); |
| |
| std::string custom_data_origin_str = custom_data_origin.Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| custom_data_origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| module_script_url, custom_data_origin_str, |
| /*worklet_ordinal=*/0, GetFirstWorkletHostDevToolsToken())}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, |
| TwoWorkletsInSameFrame_OrdinalIDsAreCorrect) { |
| GURL url = https_server()->GetURL("a.test", kSimplePagePath); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| WebContentsConsoleObserver console_observer(shell()->web_contents()); |
| |
| GURL out_script_url1; |
| ExecuteScriptInWorkletUsingCreateWorklet(shell(), R"( |
| sharedStorage.set('key0', 'value0'); |
| sharedStorage.append('key0', 'value1'); |
| |
| console.log(await sharedStorage.get('key0')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url1); |
| |
| GURL out_script_url2; |
| ExecuteScriptInWorkletUsingCreateWorklet(shell(), R"( |
| sharedStorage.set('key1', 'value2'); |
| sharedStorage.append('key1', 'value3'); |
| |
| console.log(await sharedStorage.get('key1')); |
| console.log(await sharedStorage.length()); |
| )", |
| &out_script_url2, |
| /*expected_total_host_count=*/2u); |
| |
| EXPECT_EQ(4u, console_observer.messages().size()); |
| EXPECT_EQ("value0value1", |
| base::UTF16ToUTF8(console_observer.messages()[0].message)); |
| EXPECT_EQ("1", base::UTF16ToUTF8(console_observer.messages()[1].message)); |
| EXPECT_EQ("value2value3", |
| base::UTF16ToUTF8(console_observer.messages()[2].message)); |
| EXPECT_EQ("2", base::UTF16ToUTF8(console_observer.messages()[3].message)); |
| |
| WaitForHistograms({kTimingRunExecutedInWorkletHistogram}); |
| histogram_tester_.ExpectTotalCount(kTimingRunExecutedInWorkletHistogram, 2); |
| |
| std::map<int, base::UnguessableToken>& cached_worklet_devtools_tokens = |
| GetCachedWorkletHostDevToolsTokens(); |
| EXPECT_EQ(cached_worklet_devtools_tokens.size(), 2u); |
| |
| std::string origin_str = url::Origin::Create(url).Serialize(); |
| ExpectAccessObserved( |
| {{AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| out_script_url1, "context-origin", |
| /*worklet_ordinal=*/0, cached_worklet_devtools_tokens[0])}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), cached_worklet_devtools_tokens[0])}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key0", "value0", false, cached_worklet_devtools_tokens[0])}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kAppend, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForAppend( |
| "key0", "value1", cached_worklet_devtools_tokens[0])}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key0", cached_worklet_devtools_tokens[0])}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| cached_worklet_devtools_tokens[0])}, |
| {AccessScope::kWindow, AccessMethod::kCreateWorklet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForCreateWorklet( |
| out_script_url2, "context-origin", |
| /*worklet_ordinal=*/1, cached_worklet_devtools_tokens[1])}, |
| {AccessScope::kWindow, AccessMethod::kRun, MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForRunForTesting( |
| "test-operation", /*operation_id=*/0, /*keep_alive=*/true, |
| SharedStorageEventParams::PrivateAggregationConfigWrapper(), |
| blink::CloneableMessage(), cached_worklet_devtools_tokens[1])}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kSet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForSet( |
| "key1", "value2", false, cached_worklet_devtools_tokens[1])}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kAppend, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateForAppend( |
| "key1", "value3", cached_worklet_devtools_tokens[1])}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kGet, MainFrameId(), |
| origin_str, |
| SharedStorageEventParams::CreateForGet( |
| "key1", cached_worklet_devtools_tokens[1])}, |
| {AccessScope::kSharedStorageWorklet, AccessMethod::kLength, |
| MainFrameId(), origin_str, |
| SharedStorageEventParams::CreateWithWorkletToken( |
| cached_worklet_devtools_tokens[1])}}); |
| |
| ExpectOperationFinishedInfosObserved( |
| {{base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0, |
| cached_worklet_devtools_tokens[0], MainFrameId(), origin_str}, |
| {base::TimeDelta(), AccessMethod::kRun, /*operation_id=*/0, |
| cached_worklet_devtools_tokens[1], MainFrameId(), origin_str}}); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| TwoWindows_DevToolsAccessNotificationsFilteredByMainFrame) { |
| GURL url1 = https_server()->GetURL("a.test", kPageWithBlankIframePath); |
| GURL url2 = https_server()->GetURL("b.test", kPageWithBlankIframePath); |
| GURL iframe_url1 = https_server()->GetURL("c.test", kSimplePagePath); |
| GURL iframe_url2 = https_server()->GetURL("d.test", kSimplePagePath); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url1)); |
| RenderFrameHost* main_rfh1 = PrimaryFrameTreeNodeRoot()->current_frame_host(); |
| TestSharedStorageDevToolsClient main_frame_devtools_client1(main_rfh1); |
| std::string expected_method = "Storage.sharedStorageAccessed"; |
| main_frame_devtools_client1.set_expected_notification_method(expected_method); |
| |
| EXPECT_TRUE( |
| NavigateIframeToURL(shell()->web_contents(), "test_iframe", iframe_url1)); |
| RenderFrameHost* iframe1 = |
| PrimaryFrameTreeNodeRoot()->child_at(0)->current_frame_host(); |
| TestSharedStorageDevToolsClient iframe_devtools_client1(iframe1); |
| iframe_devtools_client1.set_expected_notification_method(expected_method); |
| |
| EXPECT_TRUE(ExecJs(iframe1, "sharedStorage.delete('key0')")); |
| |
| Shell* shell2 = Shell::CreateNewWindow( |
| shell()->web_contents()->GetBrowserContext(), url2, nullptr, gfx::Size()); |
| ASSERT_TRUE(WaitForLoadStop(shell2->web_contents())); |
| |
| RenderFrameHost* main_rfh2 = |
| PrimaryFrameTreeNodeRootFromShell(shell2)->current_frame_host(); |
| TestSharedStorageDevToolsClient main_frame_devtools_client2(main_rfh2); |
| main_frame_devtools_client2.set_expected_notification_method(expected_method); |
| |
| EXPECT_TRUE( |
| NavigateIframeToURL(shell2->web_contents(), "test_iframe", iframe_url2)); |
| RenderFrameHost* iframe2 = PrimaryFrameTreeNodeRootFromShell(shell2) |
| ->child_at(0) |
| ->current_frame_host(); |
| TestSharedStorageDevToolsClient iframe_devtools_client2(iframe2); |
| iframe_devtools_client2.set_expected_notification_method(expected_method); |
| |
| EXPECT_TRUE(ExecJs(iframe2, "sharedStorage.set('key2', 'value2')")); |
| |
| GURL out_script_url1; |
| ExecuteScriptInWorklet(main_rfh1, R"( |
| sharedStorage.set('key0', 'value0'); |
| sharedStorage.append('key0', 'value1'); |
| )", |
| &out_script_url1); |
| |
| GURL out_script_url2; |
| ExecuteScriptInWorkletUsingCreateWorklet(main_rfh2, R"( |
| sharedStorage.delete('key1'); |
| sharedStorage.clear(); |
| )", |
| &out_script_url2, |
| /*expected_total_host_count=*/2u); |
| |
| std::vector<std::string> selected_key_paths( |
| {"method", "ownerSite", "params.operationId", "params.operationName", |
| "params.ignoreIfPresent", "params.key", "params.value", |
| "params.workletOrdinal", "scope"}); |
| std::vector<std::map<std::string, std::string>> |
| selected_params_observed_main1 = |
| main_frame_devtools_client1 |
| .GetSelectedParamsAsStringsForNotificationsWithExpectedMethod( |
| selected_key_paths); |
| |
| // `main_frame_devtools_client1` receives the expected shared storage |
| // notifications for `shell()`, including any notifications from subframes of |
| // this main frame, but no further "Storage.sharedStorageAccessed" |
| // notifications. In particular, it does not receive those from `shell2`. |
| ASSERT_EQ(selected_params_observed_main1.size(), 5u) |
| << SerializeVectorOfMapOfStrings(selected_params_observed_main1); |
| EXPECT_THAT( |
| selected_params_observed_main1[0], |
| ElementsAre(Pair("method", "delete"), Pair("ownerSite", "https://c.test"), |
| Pair("params.key", "key0"), Pair("scope", "window"))); |
| EXPECT_THAT( |
| selected_params_observed_main1[1], |
| ElementsAre(Pair("method", "addModule"), |
| Pair("ownerSite", "https://a.test"), |
| Pair("params.workletOrdinal", "0"), Pair("scope", "window"))); |
| EXPECT_THAT( |
| selected_params_observed_main1[2], |
| ElementsAre(Pair("method", "run"), Pair("ownerSite", "https://a.test"), |
| Pair("params.operationId", "0"), |
| Pair("params.operationName", "test-operation"), |
| Pair("scope", "window"))); |
| EXPECT_THAT( |
| selected_params_observed_main1[3], |
| ElementsAre(Pair("method", "set"), Pair("ownerSite", "https://a.test"), |
| Pair("params.ignoreIfPresent", "false"), |
| Pair("params.key", "key0"), Pair("params.value", "value0"), |
| Pair("scope", "sharedStorageWorklet"))); |
| EXPECT_THAT( |
| selected_params_observed_main1[4], |
| ElementsAre(Pair("method", "append"), Pair("ownerSite", "https://a.test"), |
| Pair("params.key", "key0"), Pair("params.value", "value1"), |
| Pair("scope", "sharedStorageWorklet"))); |
| |
| std::vector<std::map<std::string, std::string>> |
| selected_params_observed_main2 = |
| main_frame_devtools_client2 |
| .GetSelectedParamsAsStringsForNotificationsWithExpectedMethod( |
| selected_key_paths); |
| |
| // `main_frame_devtools_client2` receives the expected shared storage |
| // notifications for `shell2`, including any notifications from subframes of |
| // this main frame, but no further "Storage.sharedStorageAccessed" |
| // notifications. In particular, it does not receive those from `shell()`. |
| ASSERT_EQ(selected_params_observed_main2.size(), 5u) |
| << SerializeVectorOfMapOfStrings(selected_params_observed_main2); |
| EXPECT_THAT( |
| selected_params_observed_main2[0], |
| ElementsAre(Pair("method", "set"), Pair("ownerSite", "https://d.test"), |
| Pair("params.ignoreIfPresent", "false"), |
| Pair("params.key", "key2"), Pair("params.value", "value2"), |
| Pair("scope", "window"))); |
| EXPECT_THAT( |
| selected_params_observed_main2[1], |
| ElementsAre(Pair("method", "createWorklet"), |
| Pair("ownerSite", "https://b.test"), |
| Pair("params.workletOrdinal", "1"), Pair("scope", "window"))); |
| EXPECT_THAT( |
| selected_params_observed_main2[2], |
| ElementsAre(Pair("method", "run"), Pair("ownerSite", "https://b.test"), |
| Pair("params.operationId", "0"), |
| Pair("params.operationName", "test-operation"), |
| Pair("scope", "window"))); |
| EXPECT_THAT( |
| selected_params_observed_main2[3], |
| ElementsAre(Pair("method", "delete"), Pair("ownerSite", "https://b.test"), |
| Pair("params.key", "key1"), |
| Pair("scope", "sharedStorageWorklet"))); |
| EXPECT_THAT( |
| selected_params_observed_main2[4], |
| ElementsAre(Pair("method", "clear"), Pair("ownerSite", "https://b.test"), |
| Pair("scope", "sharedStorageWorklet"))); |
| |
| ASSERT_EQ(IsLocalRoot(iframe1), IsLocalRoot(iframe2)); |
| |
| if (!IsLocalRoot(iframe1)) { |
| // In this case, `iframe_devtools_client1` and `iframe_devtools_client2` are |
| // actually attached to their respective main frame hosts. They will have |
| // received the same notifications as above. |
| return; |
| } |
| |
| std::vector<std::map<std::string, std::string>> |
| selected_params_observed_iframe1 = |
| iframe_devtools_client1 |
| .GetSelectedParamsAsStringsForNotificationsWithExpectedMethod( |
| selected_key_paths); |
| |
| std::vector<std::map<std::string, std::string>> |
| selected_params_observed_iframe2 = |
| iframe_devtools_client2 |
| .GetSelectedParamsAsStringsForNotificationsWithExpectedMethod( |
| std::move(selected_key_paths)); |
| |
| // Neither `iframe_devtools_client1` nor `iframe_devtools_client2` receives |
| // any shared storage notifications. The notifications for the subframes' |
| // events were received by their respective main frame clients above. |
| EXPECT_TRUE(selected_params_observed_iframe1.empty()) |
| << SerializeVectorOfMapOfStrings(selected_params_observed_iframe1); |
| EXPECT_TRUE(selected_params_observed_iframe2.empty()) |
| << SerializeVectorOfMapOfStrings(selected_params_observed_iframe2); |
| } |
| |
| IN_PROC_BROWSER_TEST_P( |
| SharedStorageBrowserTest, |
| TwoWindows_DevToolsOperationFinishedNotificationsFilteredByMainFrame) { |
| GURL url1 = https_server()->GetURL("a.test", kPageWithBlankIframePath); |
| GURL url2 = https_server()->GetURL("b.test", kPageWithBlankIframePath); |
| GURL iframe_url1 = https_server()->GetURL("c.test", kSimplePagePath); |
| GURL iframe_url2 = https_server()->GetURL("d.test", kSimplePagePath); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url1)); |
| RenderFrameHost* main_rfh1 = PrimaryFrameTreeNodeRoot()->current_frame_host(); |
| TestSharedStorageDevToolsClient main_frame_devtools_client1(main_rfh1); |
| std::string expected_method = |
| "Storage.sharedStorageWorkletOperationExecutionFinished"; |
| main_frame_devtools_client1.set_expected_notification_method(expected_method); |
| |
| EXPECT_TRUE( |
| NavigateIframeToURL(shell()->web_contents(), "test_iframe", iframe_url1)); |
| RenderFrameHost* iframe1 = |
| PrimaryFrameTreeNodeRoot()->child_at(0)->current_frame_host(); |
| TestSharedStorageDevToolsClient iframe_devtools_client1(iframe1); |
| iframe_devtools_client1.set_expected_notification_method(expected_method); |
| |
| EXPECT_TRUE(ExecJs(iframe1, R"( |
| sharedStorage.createWorklet('shared_storage/simple_module.js') |
| .then((worklet) => worklet.run("test-operation", {keepAlive: true})); |
| )")); |
| |
| Shell* shell2 = Shell::CreateNewWindow( |
| shell()->web_contents()->GetBrowserContext(), url2, nullptr, gfx::Size()); |
| ASSERT_TRUE(WaitForLoadStop(shell2->web_contents())); |
| |
| RenderFrameHost* main_rfh2 = |
| PrimaryFrameTreeNodeRootFromShell(shell2)->current_frame_host(); |
| TestSharedStorageDevToolsClient main_frame_devtools_client2(main_rfh2); |
| main_frame_devtools_client2.set_expected_notification_method(expected_method); |
| |
| EXPECT_TRUE( |
| NavigateIframeToURL(shell2->web_contents(), "test_iframe", iframe_url2)); |
| RenderFrameHost* iframe2 = PrimaryFrameTreeNodeRootFromShell(shell2) |
| ->child_at(0) |
| ->current_frame_host(); |
| TestSharedStorageDevToolsClient iframe_devtools_client2(iframe2); |
| iframe_devtools_client2.set_expected_notification_method(expected_method); |
| |
| EXPECT_TRUE(ExecJs(iframe2, R"( |
| sharedStorage.worklet.addModule('shared_storage/simple_module.js') |
| .then(() => sharedStorage.selectURL("test-url-selection-operation", |
| [{url: 'fenced_frames/title0.html'}, |
| {url: 'fenced_frames/title1.html'}], |
| {keepAlive: true})); |
| )")); |
| |
| EXPECT_TRUE(ExecJs(main_rfh1, R"( |
| sharedStorage.createWorklet('shared_storage/simple_module.js') |
| .then((worklet) => worklet.selectURL("test-url-selection-operation", |
| [{url: 'fenced_frames/title0.html'}, |
| {url: 'fenced_frames/title1.html'}], |
| {keepAlive: true})); |
| )")); |
| |
| GURL out_script_url; |
| ExecuteScriptInWorkletUsingCreateWorklet(main_rfh2, R"( |
| sharedStorage.clear(); |
| )", |
| &out_script_url, |
| /*expected_total_host_count=*/4u); |
| |
| // Ensure that execution of all 4 operations has finished. |
| WaitForHistogramsWithCounts( |
| {std::make_tuple(kTimingRunExecutedInWorkletHistogram, 2), |
| std::make_tuple(kTimingSelectUrlExecutedInWorkletHistogram, 2)}); |
| |
| std::vector<std::string> selected_key_paths( |
| {"method", "operationId", "ownerOrigin"}); |
| std::vector<std::map<std::string, std::string>> |
| selected_params_observed_main1 = |
| main_frame_devtools_client1 |
| .GetSelectedParamsAsStringsForNotificationsWithExpectedMethod( |
| selected_key_paths); |
| |
| std::string main_origin_str1 = url::Origin::Create(url1).Serialize(); |
| std::string main_origin_str2 = url::Origin::Create(url2).Serialize(); |
| std::string iframe_origin_str1 = url::Origin::Create(iframe_url1).Serialize(); |
| std::string iframe_origin_str2 = url::Origin::Create(iframe_url2).Serialize(); |
| |
| // `main_frame_devtools_client1` receives the expected shared storage |
| // notifications for `shell()`, including any notifications from subframes of |
| // this main frame, but no further "Storage.sharedStorageAccessed" |
| // notifications. In particular, it does not receive those from `shell2`. |
| ASSERT_EQ(selected_params_observed_main1.size(), 2u) |
| << SerializeVectorOfMapOfStrings(selected_params_observed_main1); |
| if (selected_params_observed_main1[0]["ownerOrigin"] != iframe_origin_str1) { |
| // The order in which the operations finish executing is nondeterministic. |
| // If necessary, swap to put the iframe's results first. |
| std::swap(selected_params_observed_main1[0], |
| selected_params_observed_main1[1]); |
| } |
| EXPECT_THAT(selected_params_observed_main1[0], |
| ElementsAre(Pair("method", "run"), Pair("operationId", "0"), |
| Pair("ownerOrigin", iframe_origin_str1))); |
| EXPECT_THAT(selected_params_observed_main1[1], |
| ElementsAre(Pair("method", "selectURL"), Pair("operationId", "0"), |
| Pair("ownerOrigin", main_origin_str1))); |
| |
| std::vector<std::map<std::string, std::string>> |
| selected_params_observed_main2 = |
| main_frame_devtools_client2 |
| .GetSelectedParamsAsStringsForNotificationsWithExpectedMethod( |
| selected_key_paths); |
| |
| // `main_frame_devtools_client2` receives the expected shared storage |
| // notifications for `shell2`, including any notifications from subframes of |
| // this main frame, but no further "Storage.sharedStorageAccessed" |
| // notifications. In particular, it does not receive those from `shell()`. |
| ASSERT_EQ(selected_params_observed_main2.size(), 2u) |
| << SerializeVectorOfMapOfStrings(selected_params_observed_main2); |
| if (selected_params_observed_main2[0]["ownerOrigin"] != iframe_origin_str2) { |
| // The order in which the operations finish executing is nondeterministic. |
| // If necessary, swap to put the iframe's results first. |
| std::swap(selected_params_observed_main2[0], |
| selected_params_observed_main2[1]); |
| } |
| EXPECT_THAT(selected_params_observed_main2[0], |
| ElementsAre(Pair("method", "selectURL"), Pair("operationId", "0"), |
| Pair("ownerOrigin", iframe_origin_str2))); |
| EXPECT_THAT(selected_params_observed_main2[1], |
| ElementsAre(Pair("method", "run"), Pair("operationId", "0"), |
| Pair("ownerOrigin", main_origin_str2))); |
| |
| ASSERT_EQ(IsLocalRoot(iframe1), IsLocalRoot(iframe2)); |
| |
| if (!IsLocalRoot(iframe1)) { |
| // In this case, `iframe_devtools_client1` and `iframe_devtools_client2` are |
| // actually attached to their respective main frame hosts. They will have |
| // received the same notifications as above. |
| return; |
| } |
| |
| std::vector<std::map<std::string, std::string>> |
| selected_params_observed_iframe1 = |
| iframe_devtools_client1 |
| .GetSelectedParamsAsStringsForNotificationsWithExpectedMethod( |
| selected_key_paths); |
| |
| std::vector<std::map<std::string, std::string>> |
| selected_params_observed_iframe2 = |
| iframe_devtools_client2 |
| .GetSelectedParamsAsStringsForNotificationsWithExpectedMethod( |
| std::move(selected_key_paths)); |
| |
| // Neither `iframe_devtools_client1` nor `iframe_devtools_client2` receives |
| // any shared storage notifications. The notifications for the subframes' |
| // events were received by their respective main frame clients above. |
| EXPECT_TRUE(selected_params_observed_iframe1.empty()) |
| << SerializeVectorOfMapOfStrings(selected_params_observed_iframe1); |
| EXPECT_TRUE(selected_params_observed_iframe2.empty()) |
| << SerializeVectorOfMapOfStrings(selected_params_observed_iframe2); |
| } |
| |
| } // namespace content |