blob: 370ff824dff7b76cc3c1f72405e3ff28559c64a6 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/shared_storage/shared_storage_header_observer.h"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "content/browser/navigation_or_document_handle.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/common/content_client.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/shared_storage_test_utils.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_shared_storage_header_observer.h"
#include "content/test/test_web_contents.h"
#include "services/network/public/mojom/optional_bool.mojom.h"
#include "services/network/public/mojom/url_loader_network_service_observer.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest-param-test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
const char kMainOrigin[] = "https://main.test";
const char kMainOrigin1[] = "https://main1.test";
const char kMainOrigin2[] = "https://main2.test";
const char kChildOrigin1[] = "https://child1.test";
const char kChildOrigin2[] = "https://child2.test";
const char kTestOrigin1[] = "https://a.test";
const char kTestOrigin2[] = "https://b.test";
const char kTestOrigin3[] = "https://c.test";
using OperationPtr = network::mojom::SharedStorageOperationPtr;
using OperationType = network::mojom::SharedStorageOperationType;
using OperationResult = storage::SharedStorageManager::OperationResult;
using GetResult = storage::SharedStorageManager::GetResult;
using ContextType = StoragePartitionImpl::ContextType;
enum class TestCaseType {
kNavigationRequestContextIframePermissionEnabled = 0,
kNavigationRequestContextIframePermissionDisabled = 1,
kNavigationRequestContextMainFrame = 2,
kRenderFrameHostContextIframePermissionEnabled = 3,
kRenderFrameHostContextIframePermissionDisabled = 4,
kRenderFrameHostContextIframeArriveBeforeCommit = 5,
kRenderFrameHostContextMainFramePermissionEnabled = 6,
kRenderFrameHostContextMainFramePermissionDisabled = 7,
kRenderFrameHostContextMainFrameArriveBeforeCommit = 8,
kRenderFrameHostContextNoRenderFrameHost = 9,
};
[[nodiscard]] blink::ParsedPermissionsPolicy MakeSharedStoragePermissionsPolicy(
const url::Origin& request_origin,
bool shared_storage_enabled_for_request,
bool shared_storage_enabled_for_all) {
std::vector<blink::OriginWithPossibleWildcards> allowed_origins =
shared_storage_enabled_for_request
? std::vector<blink::OriginWithPossibleWildcards>(
{*blink::OriginWithPossibleWildcards::FromOrigin(
request_origin)})
: std::vector<blink::OriginWithPossibleWildcards>();
return blink::ParsedPermissionsPolicy(
{blink::ParsedPermissionsPolicyDeclaration(
blink::mojom::PermissionsPolicyFeature::kSharedStorage,
std::move(allowed_origins),
/*self_if_matches=*/std::nullopt,
/*matches_all_origins=*/shared_storage_enabled_for_all,
/*matches_opaque_src=*/false)});
}
[[nodiscard]] OperationPtr MakeSharedStorageOperationPtr(
OperationType operation_type,
std::optional<std::string> key,
std::optional<std::string> value,
std::optional<bool> ignore_if_present) {
return network::mojom::SharedStorageOperation::New(
operation_type, std::move(key), std::move(value),
AbslToMojomOptionalBool(ignore_if_present));
}
[[nodiscard]] std::vector<OperationPtr> MakeOperationVector(
std::vector<std::tuple<OperationType,
std::optional<std::string>,
std::optional<std::string>,
std::optional<bool>>> operation_tuples) {
std::vector<OperationPtr> operations;
for (const auto& operation_tuple : operation_tuples) {
OperationPtr operation = MakeSharedStorageOperationPtr(
std::get<0>(operation_tuple), std::get<1>(operation_tuple),
std::get<2>(operation_tuple), std::get<3>(operation_tuple));
operations.push_back(std::move(operation));
}
return operations;
}
[[nodiscard]] SharedStorageWriteOperationAndResult SetOperation(
const url::Origin& request_origin,
std::string key,
std::string value,
std::optional<bool> ignore_if_present,
OperationResult result) {
return SharedStorageWriteOperationAndResult::SetOperation(
request_origin, std::move(key), std::move(value), ignore_if_present,
result);
}
[[nodiscard]] SharedStorageWriteOperationAndResult AppendOperation(
const url::Origin& request_origin,
std::string key,
std::string value,
OperationResult result) {
return SharedStorageWriteOperationAndResult::AppendOperation(
request_origin, std::move(key), std::move(value), result);
}
[[nodiscard]] SharedStorageWriteOperationAndResult DeleteOperation(
const url::Origin& request_origin,
std::string key,
OperationResult result) {
return SharedStorageWriteOperationAndResult::DeleteOperation(
request_origin, std::move(key), result);
}
[[nodiscard]] SharedStorageWriteOperationAndResult ClearOperation(
const url::Origin& request_origin,
OperationResult result) {
return SharedStorageWriteOperationAndResult::ClearOperation(request_origin,
result);
}
class MockContentBrowserClient : public ContentBrowserClient {
public:
bool IsSharedStorageAllowed(
content::BrowserContext* browser_context,
content::RenderFrameHost* rfh,
const url::Origin& top_frame_origin,
const url::Origin& accessing_origin,
std::string* out_debug_message = nullptr) override {
if (bypass_shared_storage_allowed_count_ > 0) {
bypass_shared_storage_allowed_count_--;
return true;
}
return ContentBrowserClient::IsSharedStorageAllowed(
browser_context, rfh, top_frame_origin, accessing_origin,
out_debug_message);
}
void set_bypass_shared_storage_allowed_count(int count) {
CHECK_EQ(bypass_shared_storage_allowed_count_, 0);
CHECK_GE(count, 0);
bypass_shared_storage_allowed_count_ = count;
}
private:
int bypass_shared_storage_allowed_count_ = 0;
};
class SharedStorageHeaderObserverTest
: public RenderViewHostTestHarness,
public testing::WithParamInterface<TestCaseType> {
public:
SharedStorageHeaderObserverTest() {
feature_list_.InitAndEnableFeature(blink::features::kSharedStorageAPI);
}
~SharedStorageHeaderObserverTest() override = default;
void SetUp() override {
RenderViewHostTestHarness::SetUp();
mock_content_browser_client_ = std::make_unique<MockContentBrowserClient>();
old_content_browser_client_ =
content::SetBrowserClientForTesting(mock_content_browser_client_.get());
observer_ = std::make_unique<TestSharedStorageHeaderObserver>(
browser_context()->GetDefaultStoragePartition());
NavigateMainFrameAndGetHandle(GURL(kMainOrigin),
url::Origin::Create(GURL(kMainOrigin)),
/*shared_storage_enabled_for_request=*/true,
/*shared_storage_enabled_for_all=*/true);
// Intentionally use duplicates to test the cases where the previously used
// URL is the same as well as where it is different.
main_urls_ = {kMainOrigin1, kMainOrigin2, kMainOrigin2};
child_urls_ = {kChildOrigin1, kChildOrigin1, kChildOrigin2};
}
void TearDown() override {
// `RenderViewHostTestHarness::TearDown()` leads to the destruction of
// `TestBrowserContext` which leads to
// `BrowserContextImpl::ShutdownStoragePartitions()` being called. This
// destroys the `StoragePartitionImpl` that
// `SharedStorageHeaderObserver::storage_partition_` points to.
// Destroy the observer before `RenderViewHostTestHarness::TearDown()` to
// avoid it holding an Across Tasks Dangling Ptr to the
// `StoragePartitionImpl`.
observer_.reset();
content::SetBrowserClientForTesting(old_content_browser_client_);
old_content_browser_client_ = nullptr;
RenderViewHostTestHarness::TearDown();
}
void set_bypass_shared_storage_allowed_count(int count) {
mock_content_browser_client_->set_bypass_shared_storage_allowed_count(
count);
}
bool ExpectSuccess() const {
return GetParam() ==
TestCaseType::kNavigationRequestContextIframePermissionEnabled ||
GetParam() ==
TestCaseType::kRenderFrameHostContextIframePermissionEnabled ||
GetParam() ==
TestCaseType::kRenderFrameHostContextIframeArriveBeforeCommit ||
GetParam() ==
TestCaseType::
kRenderFrameHostContextMainFramePermissionEnabled ||
GetParam() ==
TestCaseType::kRenderFrameHostContextMainFrameArriveBeforeCommit;
}
ContextType GetContextType() {
switch (GetParam()) {
case TestCaseType::kNavigationRequestContextIframePermissionEnabled:
[[fallthrough]];
case TestCaseType::kNavigationRequestContextIframePermissionDisabled:
[[fallthrough]];
case TestCaseType::kNavigationRequestContextMainFrame:
return ContextType::kNavigationRequestContext;
case TestCaseType::kRenderFrameHostContextIframePermissionEnabled:
[[fallthrough]];
case TestCaseType::kRenderFrameHostContextIframePermissionDisabled:
[[fallthrough]];
case TestCaseType::kRenderFrameHostContextIframeArriveBeforeCommit:
[[fallthrough]];
case TestCaseType::kRenderFrameHostContextMainFramePermissionEnabled:
[[fallthrough]];
case TestCaseType::kRenderFrameHostContextMainFramePermissionDisabled:
[[fallthrough]];
case TestCaseType::kRenderFrameHostContextMainFrameArriveBeforeCommit:
[[fallthrough]];
case TestCaseType::kRenderFrameHostContextNoRenderFrameHost:
return ContextType::kRenderFrameHostContext;
default:
NOTREACHED_NORETURN();
}
}
GURL NextMainURL() {
size_t next_index = main_url_index_++;
main_url_index_ %= main_urls_.size();
return GURL(main_urls_[next_index]);
}
GURL NextChildURL() {
size_t next_index = child_url_index_++;
child_url_index_ %= child_urls_.size();
return GURL(child_urls_[next_index]);
}
NavigationOrDocumentHandle* GetNavigationOrDocumentHandle(
const url::Origin& request_origin) {
switch (GetParam()) {
case TestCaseType::kNavigationRequestContextIframePermissionEnabled:
// Renavigate main frame to ensure that iframe's parent policy is set as
// needed.
NavigateMainFrameAndGetHandle(
NextMainURL(), request_origin,
/*shared_storage_enabled_for_request=*/true,
/*shared_storage_enabled_for_all=*/false);
return CreateIframeNavigationRequestAndGetHandle(
NextChildURL(), request_origin,
/*shared_storage_enabled_for_request=*/true,
/*shared_storage_enabled_for_all=*/false);
case TestCaseType::kNavigationRequestContextIframePermissionDisabled:
// Renavigate main frame to ensure that iframe's parent policy is set as
// needed.
NavigateMainFrameAndGetHandle(
NextMainURL(), request_origin,
/*shared_storage_enabled_for_request=*/false,
/*shared_storage_enabled_for_all=*/false);
return CreateIframeNavigationRequestAndGetHandle(
NextChildURL(), request_origin,
/*shared_storage_enabled_for_request=*/false,
/*shared_storage_enabled_for_all=*/false);
case TestCaseType::kNavigationRequestContextMainFrame:
return CreateMainFrameNavigationRequestAndGetHandle(
NextMainURL(), request_origin,
/*shared_storage_enabled_for_request=*/true,
/*shared_storage_enabled_for_all=*/false);
case TestCaseType::kRenderFrameHostContextIframePermissionEnabled:
return CreateAndNavigateIframeAndGetHandle(
NextChildURL(), request_origin,
/*shared_storage_enabled_for_request=*/true,
/*shared_storage_enabled_for_all=*/false);
case TestCaseType::kRenderFrameHostContextIframePermissionDisabled:
return CreateAndNavigateIframeAndGetHandle(
NextChildURL(), request_origin,
/*shared_storage_enabled_for_request=*/false,
/*shared_storage_enabled_for_all=*/false);
case TestCaseType::kRenderFrameHostContextIframeArriveBeforeCommit:
return CreateAndGetIframeReadyToCommitAndGetHandle(
NextChildURL(), request_origin,
/*shared_storage_enabled_for_request=*/true,
/*shared_storage_enabled_for_all=*/false);
case TestCaseType::kRenderFrameHostContextMainFramePermissionEnabled:
return NavigateMainFrameAndGetHandle(
NextMainURL(), request_origin,
/*shared_storage_enabled_for_request=*/true,
/*shared_storage_enabled_for_all=*/false);
case TestCaseType::kRenderFrameHostContextMainFramePermissionDisabled:
return NavigateMainFrameAndGetHandle(
NextMainURL(), request_origin,
/*shared_storage_enabled_for_request=*/false,
/*shared_storage_enabled_for_all=*/false);
case TestCaseType::kRenderFrameHostContextMainFrameArriveBeforeCommit:
return GetMainFrameReadyToCommitAndGetHandle(
NextMainURL(), request_origin,
/*shared_storage_enabled_for_request=*/true,
/*shared_storage_enabled_for_all=*/true);
case TestCaseType::kRenderFrameHostContextNoRenderFrameHost:
if (!handle_for_null_rfh_) {
handle_for_null_rfh_ = NavigationOrDocumentHandle::CreateForDocument(
GlobalRenderFrameHostId());
}
return handle_for_null_rfh_.get();
default:
NOTREACHED_NORETURN();
}
}
void SetUpMainFrameNavigation(const GURL& url,
const url::Origin& request_origin,
bool shared_storage_enabled_for_request,
bool shared_storage_enabled_for_all) {
main_frame_navigation_simulator_ =
content::NavigationSimulator::CreateBrowserInitiated(url,
web_contents());
main_frame_navigation_simulator_->SetPermissionsPolicyHeader(
MakeSharedStoragePermissionsPolicy(request_origin,
shared_storage_enabled_for_request,
shared_storage_enabled_for_all));
main_frame_navigation_simulator_->Start();
}
NavigationOrDocumentHandle* CreateMainFrameNavigationRequestAndGetHandle(
const GURL& url,
const url::Origin& request_origin,
bool shared_storage_enabled_for_request,
bool shared_storage_enabled_for_all) {
SetUpMainFrameNavigation(url, request_origin,
shared_storage_enabled_for_request,
shared_storage_enabled_for_all);
return NavigationRequest::From(
main_frame_navigation_simulator_->GetNavigationHandle())
->navigation_or_document_handle()
.get();
}
NavigationOrDocumentHandle* NavigateMainFrameAndGetHandle(
const GURL& url,
const url::Origin& request_origin,
bool shared_storage_enabled_for_request,
bool shared_storage_enabled_for_all) {
SetUpMainFrameNavigation(url, request_origin,
shared_storage_enabled_for_request,
shared_storage_enabled_for_all);
main_frame_navigation_simulator_->Commit();
return static_cast<RenderFrameHostImpl*>(
main_frame_navigation_simulator_->GetFinalRenderFrameHost())
->GetNavigationOrDocumentHandle()
.get();
}
NavigationOrDocumentHandle* GetMainFrameReadyToCommitAndGetHandle(
const GURL& url,
const url::Origin& request_origin,
bool shared_storage_enabled_for_request,
bool shared_storage_enabled_for_all) {
SetUpMainFrameNavigation(url, request_origin,
shared_storage_enabled_for_request,
shared_storage_enabled_for_all);
main_frame_navigation_simulator_->ReadyToCommit();
return static_cast<RenderFrameHostImpl*>(
main_frame_navigation_simulator_->GetFinalRenderFrameHost())
->GetNavigationOrDocumentHandle()
.get();
}
void SetUpIframeNavigation(const GURL& url,
const url::Origin& request_origin,
bool shared_storage_enabled_for_request,
bool shared_storage_enabled_for_all) {
content::RenderFrameHost* child_rfh =
content::RenderFrameHostTester::For(main_rfh())
->AppendChild("myiframe");
iframe_navigation_simulator_ =
content::NavigationSimulator::CreateRendererInitiated(url, child_rfh);
iframe_navigation_simulator_->SetTransition(
ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
iframe_navigation_simulator_->Start();
iframe_navigation_simulator_->SetPermissionsPolicyHeader(
MakeSharedStoragePermissionsPolicy(request_origin,
shared_storage_enabled_for_request,
shared_storage_enabled_for_all));
}
NavigationOrDocumentHandle* CreateIframeNavigationRequestAndGetHandle(
const GURL& url,
const url::Origin& request_origin,
bool shared_storage_enabled_for_request,
bool shared_storage_enabled_for_all) {
SetUpIframeNavigation(url, request_origin,
shared_storage_enabled_for_request,
shared_storage_enabled_for_all);
return NavigationRequest::From(
iframe_navigation_simulator_->GetNavigationHandle())
->navigation_or_document_handle()
.get();
}
NavigationOrDocumentHandle* CreateAndNavigateIframeAndGetHandle(
const GURL& url,
const url::Origin& request_origin,
bool shared_storage_enabled_for_request,
bool shared_storage_enabled_for_all) {
SetUpIframeNavigation(url, request_origin,
shared_storage_enabled_for_request,
shared_storage_enabled_for_all);
iframe_navigation_simulator_->Commit();
return static_cast<RenderFrameHostImpl*>(
iframe_navigation_simulator_->GetFinalRenderFrameHost())
->GetNavigationOrDocumentHandle()
.get();
}
NavigationOrDocumentHandle* CreateAndGetIframeReadyToCommitAndGetHandle(
const GURL& url,
const url::Origin& request_origin,
bool shared_storage_enabled_for_request,
bool shared_storage_enabled_for_all) {
SetUpIframeNavigation(url, request_origin,
shared_storage_enabled_for_request,
shared_storage_enabled_for_all);
iframe_navigation_simulator_->ReadyToCommit();
return static_cast<RenderFrameHostImpl*>(
iframe_navigation_simulator_->GetFinalRenderFrameHost())
->GetNavigationOrDocumentHandle()
.get();
}
void RunHeaderReceived(const url::Origin& request_origin,
std::vector<OperationPtr> operations) {
base::RunLoop loop;
base::OnceCallback<void(std::string_view error)> bad_message_callback =
base::BindLambdaForTesting([&](std::string_view error) {
LOG(ERROR) << error;
std::move(loop.QuitClosure()).Run();
});
auto* navigation_or_document_handle =
GetNavigationOrDocumentHandle(request_origin);
observer_->HeaderReceived(
request_origin, GetContextType(), navigation_or_document_handle,
std::move(operations), loop.QuitClosure(),
std::move(bad_message_callback), /*can_defer=*/true);
loop.Run();
if (GetContextType() == ContextType::kRenderFrameHostContext &&
navigation_or_document_handle->GetDocument() &&
navigation_or_document_handle->GetDocument()->IsInLifecycleState(
RenderFrameHost::LifecycleState::kPendingCommit)) {
base::RunLoop commit_loop;
base::OnceCallback<void(NavigationOrDocumentHandle*)> commit_callback =
base::BindLambdaForTesting([&](NavigationOrDocumentHandle* handle) {
std::move(commit_loop.QuitClosure()).Run();
});
static_cast<RenderFrameHostImpl*>(
navigation_or_document_handle->GetDocument())
->AddDeferredSharedStorageHeaderCallback(std::move(commit_callback));
switch (GetParam()) {
case TestCaseType::kRenderFrameHostContextIframeArriveBeforeCommit:
ASSERT_TRUE(iframe_navigation_simulator_);
iframe_navigation_simulator_->Commit();
break;
case TestCaseType::kRenderFrameHostContextMainFrameArriveBeforeCommit:
ASSERT_TRUE(main_frame_navigation_simulator_);
main_frame_navigation_simulator_->Commit();
break;
default:
LOG(ERROR)
<< "Encountered unexpectedly deferred shared storage operations";
NOTREACHED_NORETURN();
}
commit_loop.Run();
}
}
storage::SharedStorageManager* GetSharedStorageManager() {
return static_cast<StoragePartitionImpl*>(
browser_context()->GetDefaultStoragePartition())
->GetSharedStorageManager();
}
std::string GetExistingValue(const url::Origin& request_origin,
std::string key) {
base::test::TestFuture<GetResult> future;
auto* manager = GetSharedStorageManager();
DCHECK(manager);
std::u16string utf16_key;
EXPECT_TRUE(base::UTF8ToUTF16(key.c_str(), key.size(), &utf16_key));
manager->Get(request_origin, utf16_key, future.GetCallback());
GetResult result = future.Take();
EXPECT_EQ(result.result, OperationResult::kSuccess);
std::string utf8_value;
EXPECT_TRUE(base::UTF16ToUTF8(result.data.c_str(), result.data.size(),
&utf8_value));
return utf8_value;
}
bool ValueNotFound(const url::Origin& request_origin, std::string key) {
base::test::TestFuture<GetResult> future;
auto* manager = GetSharedStorageManager();
DCHECK(manager);
std::u16string utf16_key;
EXPECT_TRUE(base::UTF8ToUTF16(key.c_str(), key.size(), &utf16_key));
manager->Get(request_origin, utf16_key, future.GetCallback());
GetResult result = future.Take();
return result.result == OperationResult::kNotFound;
}
int Length(const url::Origin& request_origin) {
base::test::TestFuture<int> future;
auto* manager = GetSharedStorageManager();
DCHECK(manager);
manager->Length(request_origin, future.GetCallback());
return future.Get();
}
protected:
std::unique_ptr<TestSharedStorageHeaderObserver> observer_;
std::unique_ptr<MockContentBrowserClient> mock_content_browser_client_;
std::unique_ptr<NavigationSimulator> main_frame_navigation_simulator_;
std::unique_ptr<NavigationSimulator> iframe_navigation_simulator_;
scoped_refptr<NavigationOrDocumentHandle> handle_for_null_rfh_;
private:
base::test::ScopedFeatureList feature_list_;
raw_ptr<content::ContentBrowserClient> old_content_browser_client_ = nullptr;
std::vector<std::string> main_urls_;
std::vector<std::string> child_urls_;
size_t main_url_index_ = 0;
size_t child_url_index_ = 0;
};
auto describe_test_case_type = [](const auto& info) {
switch (info.param) {
case TestCaseType::kNavigationRequestContextIframePermissionEnabled:
return "IframeNavigationRequestPermissionEnabled";
case TestCaseType::kNavigationRequestContextIframePermissionDisabled:
return "IframeNavigationRequestPermissionDisabled";
case TestCaseType::kNavigationRequestContextMainFrame:
return "MainFrameNavigationRequest";
case TestCaseType::kRenderFrameHostContextIframePermissionEnabled:
return "IframeRFHPermissionEnabled";
case TestCaseType::kRenderFrameHostContextIframePermissionDisabled:
return "IframeRFHPermissionDisabled";
case TestCaseType::kRenderFrameHostContextIframeArriveBeforeCommit:
return "IframeRFHArriveBeforeCommit";
case TestCaseType::kRenderFrameHostContextMainFramePermissionEnabled:
return "MainFrameRFHPermissionEnabled";
case TestCaseType::kRenderFrameHostContextMainFramePermissionDisabled:
return "MainFrameRFHPermissionDisabled";
case TestCaseType::kRenderFrameHostContextMainFrameArriveBeforeCommit:
return "MainFrameRFHArriveBeforeCommit";
case TestCaseType::kRenderFrameHostContextNoRenderFrameHost:
return "NullRFH";
default:
NOTREACHED_NORETURN();
}
};
} // namespace
INSTANTIATE_TEST_SUITE_P(
All,
SharedStorageHeaderObserverTest,
testing::Values(
TestCaseType::kNavigationRequestContextIframePermissionEnabled,
TestCaseType::kNavigationRequestContextIframePermissionDisabled,
TestCaseType::kNavigationRequestContextMainFrame,
TestCaseType::kRenderFrameHostContextIframePermissionEnabled,
TestCaseType::kRenderFrameHostContextIframePermissionDisabled,
TestCaseType::kRenderFrameHostContextIframeArriveBeforeCommit,
TestCaseType::kRenderFrameHostContextMainFramePermissionEnabled,
TestCaseType::kRenderFrameHostContextMainFramePermissionDisabled,
TestCaseType::kRenderFrameHostContextMainFrameArriveBeforeCommit,
TestCaseType::kRenderFrameHostContextNoRenderFrameHost),
describe_test_case_type);
TEST_P(SharedStorageHeaderObserverTest, SharedStorageNotAllowed) {
// Simulate disabling shared storage in user preferences.
set_bypass_shared_storage_allowed_count(0);
const url::Origin kOrigin1 = url::Origin::Create(GURL(kTestOrigin1));
std::vector<OperationPtr> operations = MakeOperationVector(
{std::make_tuple(OperationType::kClear, /*key*/ std::nullopt,
/*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt),
std::make_tuple(OperationType::kSet, "key1", "value1",
/*ignore_if_present*/ std::nullopt),
std::make_tuple(OperationType::kAppend, "key1", "value1",
/*ignore_if_present*/ std::nullopt),
std::make_tuple(OperationType::kSet, "key1", "value2",
/*ignore_if_present*/ true),
std::make_tuple(OperationType::kSet, "key2", "value2",
/*ignore_if_present*/ false),
std::make_tuple(OperationType::kDelete, "key2", /*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt)});
// No operations are invoked because we've simulated shared storage being
// disabled in user preferences.
RunHeaderReceived(kOrigin1, std::move(operations));
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
EXPECT_EQ(Length(kOrigin1), 0);
}
TEST_P(SharedStorageHeaderObserverTest, Basic_SingleOrigin_AllSuccessful) {
set_bypass_shared_storage_allowed_count(1);
const url::Origin kOrigin1 = url::Origin::Create(GURL(kTestOrigin1));
std::vector<OperationPtr> operations = MakeOperationVector(
{std::make_tuple(OperationType::kClear, /*key*/ std::nullopt,
/*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt),
std::make_tuple(OperationType::kSet, "key1", "value1",
/*ignore_if_present*/ std::nullopt),
std::make_tuple(OperationType::kAppend, "key1", "value1",
/*ignore_if_present*/ std::nullopt),
std::make_tuple(OperationType::kSet, "key1", "value2",
/*ignore_if_present*/ true),
std::make_tuple(OperationType::kSet, "key2", "value2",
/*ignore_if_present*/ false),
std::make_tuple(OperationType::kDelete, "key2", /*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt)});
RunHeaderReceived(kOrigin1, std::move(operations));
if (!ExpectSuccess()) {
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
EXPECT_EQ(Length(kOrigin1), 0);
return;
}
ASSERT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().back().first, kOrigin1);
EXPECT_THAT(observer_->header_results().back().second,
testing::ElementsAre(true, true, true, true, true, true));
observer_->WaitForOperations(6);
EXPECT_THAT(
observer_->operations(),
testing::ElementsAre(
ClearOperation(kOrigin1, OperationResult::kSuccess),
SetOperation(kOrigin1, "key1", "value1", std::nullopt,
OperationResult::kSet),
AppendOperation(kOrigin1, "key1", "value1", OperationResult::kSet),
SetOperation(kOrigin1, "key1", "value2", true,
OperationResult::kIgnored),
SetOperation(kOrigin1, "key2", "value2", false,
OperationResult::kSet),
DeleteOperation(kOrigin1, "key2", OperationResult::kSuccess)));
EXPECT_EQ(GetExistingValue(kOrigin1, "key1"), "value1value1");
EXPECT_TRUE(ValueNotFound(kOrigin1, "key2"));
EXPECT_EQ(Length(kOrigin1), 1);
}
TEST_P(SharedStorageHeaderObserverTest, Basic_MultiOrigin_AllSuccessful) {
set_bypass_shared_storage_allowed_count(3);
const url::Origin kOrigin1 = url::Origin::Create(GURL(kTestOrigin1));
const url::Origin kOrigin2 = url::Origin::Create(GURL(kTestOrigin2));
const url::Origin kOrigin3 = url::Origin::Create(GURL(kTestOrigin3));
std::vector<OperationPtr> operations1 = MakeOperationVector(
{std::make_tuple(OperationType::kSet, "a", "b",
/*ignore_if_present*/ std::nullopt)});
std::vector<OperationPtr> operations2 =
MakeOperationVector({std::make_tuple(OperationType::kSet, "a", "c",
/*ignore_if_present*/ true)});
std::vector<OperationPtr> operations3 = MakeOperationVector(
{std::make_tuple(OperationType::kDelete, "a", /*value=*/std::nullopt,
/*ignore_if_present*/ std::nullopt)});
RunHeaderReceived(kOrigin1, std::move(operations1));
if (!ExpectSuccess()) {
RunHeaderReceived(kOrigin2, std::move(operations2));
RunHeaderReceived(kOrigin3, std::move(operations3));
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
EXPECT_EQ(Length(kOrigin1), 0);
EXPECT_EQ(Length(kOrigin2), 0);
EXPECT_EQ(Length(kOrigin3), 0);
return;
}
ASSERT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().back().first, kOrigin1);
EXPECT_THAT(observer_->header_results().back().second,
testing::ElementsAre(true));
RunHeaderReceived(kOrigin2, std::move(operations2));
ASSERT_EQ(observer_->header_results().size(), 2u);
EXPECT_EQ(observer_->header_results().back().first, kOrigin2);
EXPECT_THAT(observer_->header_results().back().second,
testing::ElementsAre(true));
RunHeaderReceived(kOrigin3, std::move(operations3));
ASSERT_EQ(observer_->header_results().size(), 3u);
EXPECT_EQ(observer_->header_results().back().first, kOrigin3);
EXPECT_THAT(observer_->header_results().back().second,
testing::ElementsAre(true));
observer_->WaitForOperations(3);
EXPECT_THAT(
observer_->operations(),
testing::ElementsAre(
SetOperation(kOrigin1, "a", "b", std::nullopt, OperationResult::kSet),
SetOperation(kOrigin2, "a", "c", true, OperationResult::kSet),
DeleteOperation(kOrigin3, "a", OperationResult::kSuccess)));
// Operations on different origins don't affect each other.
EXPECT_EQ(GetExistingValue(kOrigin1, "a"), "b");
EXPECT_EQ(GetExistingValue(kOrigin2, "a"), "c");
EXPECT_TRUE(ValueNotFound(kOrigin3, "a"));
EXPECT_EQ(Length(kOrigin1), 1);
EXPECT_EQ(Length(kOrigin2), 1);
EXPECT_EQ(Length(kOrigin3), 0);
}
TEST_P(SharedStorageHeaderObserverTest, SkipMissingParams) {
set_bypass_shared_storage_allowed_count(1);
const url::Origin kOrigin1 = url::Origin::Create(GURL(kTestOrigin1));
std::vector<OperationPtr> operations = MakeOperationVector({
std::make_tuple(OperationType::kClear, /*key*/ std::nullopt,
/*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt), // success
std::make_tuple(OperationType::kSet, "a", /*value*/ std::nullopt,
/*ignore_if_present*/ false), // fail
std::make_tuple(OperationType::kSet, /*key*/ std::nullopt, "b",
/*ignore_if_present*/ true), // fail
std::make_tuple(OperationType::kSet, /*key*/ std::nullopt,
/*value*/ std::nullopt,
/*ignore_if_present*/ true), // fail
std::make_tuple(OperationType::kAppend, "a", /*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kAppend, "a", "b",
/*ignore_if_present*/ std::nullopt), // success
std::make_tuple(OperationType::kAppend, /*key*/ std::nullopt, "b",
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kAppend, /*key*/ std::nullopt,
/*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kSet, "a", "c",
/*ignore_if_present*/ true), // success
std::make_tuple(OperationType::kSet, "d", "c",
/*ignore_if_present*/ std::nullopt), // success
std::make_tuple(OperationType::kDelete, /*key*/ std::nullopt,
/*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kDelete, "anything",
/*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt), // success
});
RunHeaderReceived(kOrigin1, std::move(operations));
if (!ExpectSuccess()) {
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
EXPECT_EQ(Length(kOrigin1), 0);
return;
}
ASSERT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().back().first, kOrigin1);
EXPECT_THAT(observer_->header_results().back().second,
testing::ElementsAre(true, false, false, false, false, true,
false, false, true, true, false, true));
observer_->WaitForOperations(5);
EXPECT_THAT(
observer_->operations(),
testing::ElementsAre(
ClearOperation(kOrigin1, OperationResult::kSuccess),
AppendOperation(kOrigin1, "a", "b", OperationResult::kSet),
SetOperation(kOrigin1, "a", "c", true, OperationResult::kIgnored),
SetOperation(kOrigin1, "d", "c", std::nullopt, OperationResult::kSet),
DeleteOperation(kOrigin1, "anything", OperationResult::kSuccess)));
EXPECT_EQ(GetExistingValue(kOrigin1, "a"), "b");
EXPECT_EQ(GetExistingValue(kOrigin1, "d"), "c");
EXPECT_TRUE(ValueNotFound(kOrigin1, "anything"));
EXPECT_EQ(Length(kOrigin1), 2);
}
TEST_P(SharedStorageHeaderObserverTest, SkipInvalidParams) {
set_bypass_shared_storage_allowed_count(1);
const url::Origin kOrigin1 = url::Origin::Create(GURL(kTestOrigin1));
const std::string kLong(2621441, 'x');
std::vector<OperationPtr> operations = MakeOperationVector({
std::make_tuple(OperationType::kClear, /*key*/ std::nullopt,
/*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt), // success
std::make_tuple(OperationType::kSet, "a", kLong,
/*ignore_if_present*/ false), // fail
std::make_tuple(OperationType::kSet, kLong, "b",
/*ignore_if_present*/ true), // fail
std::make_tuple(OperationType::kSet, "", "b",
/*ignore_if_present*/ true), // fail
std::make_tuple(OperationType::kSet, kLong, kLong,
/*ignore_if_present*/ true), // fail
std::make_tuple(OperationType::kSet, "", kLong,
/*ignore_if_present*/ true), // fail
std::make_tuple(OperationType::kAppend, "a", kLong,
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kAppend, "a", "b",
/*ignore_if_present*/ std::nullopt), // success
std::make_tuple(OperationType::kAppend, kLong, "b",
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kAppend, "", "b",
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kAppend, kLong, kLong,
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kAppend, "", kLong,
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kSet, "a", "c",
/*ignore_if_present*/ true), // success
std::make_tuple(OperationType::kSet, "d", "c",
/*ignore_if_present*/ std::nullopt), // success
std::make_tuple(OperationType::kDelete, kLong,
/*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kDelete, "",
/*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt), // fail
std::make_tuple(OperationType::kDelete, "anything",
/*value*/ std::nullopt,
/*ignore_if_present*/ std::nullopt), // success
});
RunHeaderReceived(kOrigin1, std::move(operations));
if (!ExpectSuccess()) {
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
EXPECT_EQ(Length(kOrigin1), 0);
return;
}
ASSERT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().back().first, kOrigin1);
EXPECT_THAT(observer_->header_results().back().second,
testing::ElementsAre(true, false, false, false, false, false,
false, true, false, false, false, false,
true, true, false, false, true));
observer_->WaitForOperations(5);
EXPECT_THAT(
observer_->operations(),
testing::ElementsAre(
ClearOperation(kOrigin1, OperationResult::kSuccess),
AppendOperation(kOrigin1, "a", "b", OperationResult::kSet),
SetOperation(kOrigin1, "a", "c", true, OperationResult::kIgnored),
SetOperation(kOrigin1, "d", "c", std::nullopt, OperationResult::kSet),
DeleteOperation(kOrigin1, "anything", OperationResult::kSuccess)));
EXPECT_EQ(GetExistingValue(kOrigin1, "a"), "b");
EXPECT_EQ(GetExistingValue(kOrigin1, "d"), "c");
EXPECT_TRUE(ValueNotFound(kOrigin1, "anything"));
EXPECT_EQ(Length(kOrigin1), 2);
}
TEST_P(SharedStorageHeaderObserverTest, IgnoreExtraParams) {
set_bypass_shared_storage_allowed_count(1);
const url::Origin kOrigin1 = url::Origin::Create(GURL(kTestOrigin1));
std::vector<OperationPtr> operations = MakeOperationVector(
{std::make_tuple(OperationType::kClear, "key1", "value2",
/*ignore_if_present*/ true), // extra params
std::make_tuple(OperationType::kSet, "key1", "value1",
/*ignore_if_present*/ std::nullopt),
std::make_tuple(OperationType::kAppend, "key1", "value1",
/*ignore_if_present*/ false), // extra param
std::make_tuple(OperationType::kSet, "key1", "value2",
/*ignore_if_present*/ true),
std::make_tuple(OperationType::kSet, "key2", "value2",
/*ignore_if_present*/ false),
std::make_tuple(OperationType::kDelete, "key2", "value2",
/*ignore_if_present*/ false)}); // extra params
RunHeaderReceived(kOrigin1, std::move(operations));
if (!ExpectSuccess()) {
EXPECT_TRUE(observer_->header_results().empty());
EXPECT_TRUE(observer_->operations().empty());
EXPECT_EQ(Length(kOrigin1), 0);
return;
}
ASSERT_EQ(observer_->header_results().size(), 1u);
EXPECT_EQ(observer_->header_results().back().first, kOrigin1);
EXPECT_THAT(observer_->header_results().back().second,
testing::ElementsAre(true, true, true, true, true, true));
// Superfluous parameters are omitted.
observer_->WaitForOperations(6);
EXPECT_THAT(
observer_->operations(),
testing::ElementsAre(
ClearOperation(kOrigin1, OperationResult::kSuccess),
SetOperation(kOrigin1, "key1", "value1", std::nullopt,
OperationResult::kSet),
AppendOperation(kOrigin1, "key1", "value1", OperationResult::kSet),
SetOperation(kOrigin1, "key1", "value2", true,
OperationResult::kIgnored),
SetOperation(kOrigin1, "key2", "value2", false,
OperationResult::kSet),
DeleteOperation(kOrigin1, "key2", OperationResult::kSuccess)));
EXPECT_EQ(GetExistingValue(kOrigin1, "key1"), "value1value1");
EXPECT_TRUE(ValueNotFound(kOrigin1, "key2"));
EXPECT_EQ(Length(kOrigin1), 1);
}
} // namespace content