Skip to content

Commit

Permalink
Add Storage Buckets to Chrome Devtools Protocol
Browse files Browse the repository at this point in the history
This adds Storage Buckets to the Chrome Devtools Protocol and implements
their backend.

Bug: 1406017
Change-Id: I5eaf6a28b81e815bc821364ea203a786e42a20ef
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4298203
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Commit-Queue: Nathan Memmott <memmott@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1121438}
  • Loading branch information
Nathan Memmott authored and Chromium LUCI CQ committed Mar 23, 2023
1 parent abffa44 commit e2789f9
Show file tree
Hide file tree
Showing 8 changed files with 528 additions and 63 deletions.
336 changes: 274 additions & 62 deletions content/browser/devtools/protocol/storage_handler.cc

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions content/browser/devtools/protocol/storage_handler.h
Expand Up @@ -16,6 +16,7 @@
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/shared_storage/shared_storage_worklet_host_manager.h"
#include "storage/browser/quota/quota_manager.h"

namespace storage {
class QuotaOverrideHandle;
Expand Down Expand Up @@ -129,12 +130,21 @@ class StorageHandler
const std::string& owner_origin_string,
std::unique_ptr<ResetSharedStorageBudgetCallback> callback) override;

DispatchResponse SetStorageBucketTracking(
const std::string& serialized_storage_key,
bool enable) override;

DispatchResponse DeleteStorageBucket(
const std::string& serialized_storage_key,
const std::string& bucket_name) override;

private:
// See definition for lifetime information.
class CacheStorageObserver;
class IndexedDBObserver;
class InterestGroupObserver;
class SharedStorageObserver;
class QuotaManagerObserver;

// Not thread safe.
CacheStorageObserver* GetCacheStorageObserver();
Expand All @@ -143,6 +153,7 @@ class StorageHandler
SharedStorageWorkletHostManager* GetSharedStorageWorkletHostManager();
absl::variant<protocol::Response, storage::SharedStorageManager*>
GetSharedStorageManager();
storage::QuotaManagerProxy* GetQuotaManagerProxy();

// content::InterestGroupManagerImpl::InterestGroupObserver
void OnInterestGroupAccessed(
Expand All @@ -168,6 +179,8 @@ class StorageHandler
const std::string& storage_key,
const std::u16string& database_name,
const std::u16string& object_store_name);
void NotifyCreateOrUpdateBucket(const storage::BucketInfo& bucket_info);
void NotifyDeleteBucket(const storage::BucketLocator& bucket_locator);

Response FindStoragePartition(const Maybe<std::string>& browser_context_id,
StoragePartition** storage_partition);
Expand All @@ -178,6 +191,7 @@ class StorageHandler
std::unique_ptr<CacheStorageObserver> cache_storage_observer_;
std::unique_ptr<IndexedDBObserver> indexed_db_observer_;
std::unique_ptr<SharedStorageObserver> shared_storage_observer_;
std::unique_ptr<QuotaManagerObserver> quota_manager_observer_;

// Exposes the API for managing storage quota overrides.
std::unique_ptr<storage::QuotaOverrideHandle> quota_override_handle_;
Expand Down
2 changes: 1 addition & 1 deletion content/browser/devtools/protocol_config.json
Expand Up @@ -99,7 +99,7 @@
},
{
"domain": "Storage",
"async": ["getUsageAndQuota", "clearDataForOrigin", "clearDataForStorageKey", "getCookies", "setCookies", "clearCookies", "overrideQuotaForOrigin", "getTrustTokens", "clearTrustTokens", "getInterestGroupDetails", "getSharedStorageMetadata", "getSharedStorageEntries", "setSharedStorageEntry", "deleteSharedStorageEntry", "clearSharedStorageEntries", "resetSharedStorageBudget"]
"async": ["getUsageAndQuota", "clearDataForOrigin", "clearDataForStorageKey", "getCookies", "setCookies", "clearCookies", "overrideQuotaForOrigin", "getTrustTokens", "clearTrustTokens", "getInterestGroupDetails", "getSharedStorageMetadata", "getSharedStorageEntries", "setSharedStorageEntry", "deleteSharedStorageEntry", "clearSharedStorageEntries", "resetSharedStorageBudget", "getStorageBucketList"]
},
{
"domain": "SystemInfo",
Expand Down
38 changes: 38 additions & 0 deletions third_party/blink/public/devtools_protocol/browser_protocol.pdl
Expand Up @@ -8984,6 +8984,7 @@ experimental domain Storage
cache_storage
interest_groups
shared_storage
storage_buckets
all
other

Expand Down Expand Up @@ -9119,6 +9120,23 @@ experimental domain Storage
# SharedStorageAccessType.workletSet.
optional boolean ignoreIfPresent

type StorageBucketsDurability extends string
enum
relaxed
strict

type StorageBucketInfo extends object
properties
SerializedStorageKey storageKey
string id
string name
boolean isDefault
Network.TimeSinceEpoch expiration
# Storage quota (bytes).
number quota
boolean persistent
StorageBucketsDurability durability

# Returns a storage key given a frame id.
command getStorageKeyForFrame
parameters
Expand Down Expand Up @@ -9315,6 +9333,18 @@ experimental domain Storage
parameters
boolean enable

# Set tracking for a storage key's buckets.
experimental command setStorageBucketTracking
parameters
string storageKey
boolean enable

# Deletes the Storage Bucket with the given storage key and bucket name.
experimental command deleteStorageBucket
parameters
string storageKey
string bucketName

# A cache's contents have been modified.
event cacheStorageContentUpdated
parameters
Expand Down Expand Up @@ -9377,6 +9407,14 @@ experimental domain Storage
# presence/absence depends on `type`.
SharedStorageAccessParams params

event storageBucketCreatedOrUpdated
parameters
StorageBucketInfo bucket

event storageBucketDeleted
parameters
string bucketId

# The SystemInfo domain defines methods and events for querying low-level system information.
experimental domain SystemInfo

Expand Down
@@ -0,0 +1,39 @@
Tests that tracking and untracking Storage Bucket for storage key works

Create bucket
bucket created successfully
Created bucket: {
durability : relaxed
expiration : <number>
id : <string>
isDefault : false
name : test-bucket
persistent : false
quota : 0
storageKey : <string>
}
bucket.expiration equals zero

Update bucket
bucket updated successfully
Updated bucket: {
durability : relaxed
expiration : <number>
id : <string>
isDefault : false
name : test-bucket
persistent : false
quota : 0
storageKey : <string>
}
bucket.expiration does not equal zero

Delete bucket
bucket deleted successfully
Deleted bucket: {
bucketId : <string>
}

Create another bucket after untracking.
Another bucket opened successfully

@@ -0,0 +1,113 @@
(async function(testRunner) {
const stabilizeNames =
[...TestRunner.stabilizeNames, 'storageKey', 'bucketId', 'expiration'];
const {dp, session} = await testRunner.startBlank(
`Tests that tracking and untracking Storage Bucket for storage key works\n`);
await dp.Page.enable();

const frameId = (await dp.Page.getResourceTree()).result.frameTree.frame.id;
const storageKey = (await dp.Storage.getStorageKeyForFrame({
frameId: frameId
})).result.storageKey;

await dp.Storage.setStorageBucketTracking({storageKey, enable: true});

{
testRunner.log(`Create bucket`);

const eventPromise = dp.Storage.onceStorageBucketCreatedOrUpdated();

// Create bucket.
const result = await session.evaluateAsync(`
(async function() {
try {
await navigator.storageBuckets.open("test-bucket");
return 'bucket created successfully';
} catch (err) {
return err;
}
})()`);

testRunner.log(result);
const {params: {bucket}} = await eventPromise;
testRunner.log(bucket, `Created bucket: `, stabilizeNames);
if (bucket.expiration === 0) {
testRunner.log('bucket.expiration equals zero\n');
} else {
testRunner.log(`bucket.expiration equals ${bucket.expiration}`);
}
}

{
testRunner.log(`Update bucket`);

const eventPromise = dp.Storage.onceStorageBucketCreatedOrUpdated();

// Update bucket.
const result = await session.evaluateAsync(`
(async function() {
try {
await navigator.storageBuckets.open("test-bucket", {expires: Number.MAX_SAFE_INTEGER});
return 'bucket updated successfully';
} catch (err) {
return err;
}
})()`);

testRunner.log(result);
const {params: {bucket}} = await eventPromise;
testRunner.log(bucket, `Updated bucket: `, stabilizeNames);
if (bucket.expiration !== 0) {
testRunner.log('bucket.expiration does not equal zero\n');
} else {
testRunner.log(`bucket.expiration equals ${bucket.expiration}`);
}
}

{
testRunner.log(`Delete bucket`);

const eventPromise = dp.Storage.onceStorageBucketDeleted();

// Delete bucket.
const result = await session.evaluateAsync(`
(async function() {
try {
await navigator.storageBuckets.delete("test-bucket");
return 'bucket deleted successfully';
} catch (err) {
return err;
}
})()`);

testRunner.log(result);
const {params} = await eventPromise;
testRunner.log(params, `Deleted bucket: `, stabilizeNames);
}

await dp.Storage.setStorageBucketTracking({storageKey, enable: false});

{
dp.Storage.onStorageBucketCreatedOrUpdated(
message => {testRunner.log(message.params.bucket)});
dp.Storage.onStorageBucketDeleted(
message => {testRunner.log(message.params.bucketLocator)});

testRunner.log('\nCreate another bucket after untracking.');

// Create one more bucket.
const result = await session.evaluateAsync(`
(async function() {
try {
await navigator.storageBuckets.open("test-bucket-2");
return 'Another bucket opened successfully';
} catch (err) {
return err;
}
})()`);

testRunner.log(result);
}

testRunner.completeTest();
})
@@ -0,0 +1,9 @@
Tests that deleting a bucket works correctly

Create bucket
buckets added successfully
Delete bucket
Deleted bucket: {
bucketId : <string>
}

@@ -0,0 +1,40 @@
(async function(testRunner) {
const {dp, session} = await testRunner.startBlank(
`Tests that deleting a bucket works correctly\n`);
await dp.Page.enable();

const stabilizeNames =
[...TestRunner.stabilizeNames, 'storageKey', 'bucketId'];

const frameId = (await dp.Page.getResourceTree()).result.frameTree.frame.id;
const storageKey =
(await dp.Storage.getStorageKeyForFrame({frameId})).result.storageKey;
await dp.Storage.setStorageBucketTracking({storageKey, enable: true});
const bucketName = 'test-bucket';

{
testRunner.log(`Create bucket`);
const result = await session.evaluateAsync(`
(async function() {
try {
await navigator.storageBuckets.open("${bucketName}");
return 'buckets added successfully';
} catch (err) {
return err;
}
})()
`);

testRunner.log(result);
}

{
testRunner.log(`Delete bucket`);

dp.Storage.deleteStorageBucket({storageKey, bucketName});
const {params} = await dp.Storage.onceStorageBucketDeleted();
testRunner.log(params, 'Deleted bucket: ', stabilizeNames);
}

testRunner.completeTest();
})

0 comments on commit e2789f9

Please sign in to comment.