diff --git a/docs/api/session.md b/docs/api/session.md index c347de9898855..311279e545d9e 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -46,7 +46,8 @@ of an existing `Session` object. * `path` string * `options` Object (optional) - * `cache` boolean - Whether to enable cache. + * `cache` boolean (optional) - Whether to enable cache. + * `quota` number (optional) - Use a quota size for a partition (in bytes). Returns `Session` - A session instance from the absolute path as specified by the `path` string. When there is an existing `Session` with the same absolute path, it @@ -56,7 +57,11 @@ be thrown if an empty string is provided. To create a `Session` with `options`, you have to ensure the `Session` with the `path` has never been used before. There is no way to change the `options` -of an existing `Session` object. +of an existing `Session` object. The optional `quota` parameter can be used to +specify the a quota size that can be used by the session, which will be returned as the quota provided by navigator.storage.estimate(). If the `quota` is not +provided, the size of the volume on which path is located will be used. A smaller +quota size will allow the LRU algorithm to evict more data from the cache, though there +is no guarantee that the cache will not exceed the quota size. ## Properties diff --git a/patches/chromium/.patches b/patches/chromium/.patches index 97553efb6b586..e099ff22ce05c 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -127,3 +127,4 @@ chore_patch_out_profile_methods_in_profile_selections_cc.patch add_gin_converter_support_for_arraybufferview.patch chore_defer_usb_service_getdevices_request_until_usb_service_is.patch revert_roll_clang_rust_llvmorg-16-init-17653-g39da55e8-3.patch +feat_add_storage_quota_capability_for_partitions.patch diff --git a/patches/chromium/feat_add_storage_quota_capability_for_partitions.patch b/patches/chromium/feat_add_storage_quota_capability_for_partitions.patch new file mode 100644 index 0000000000000..5ef8ea5da1b97 --- /dev/null +++ b/patches/chromium/feat_add_storage_quota_capability_for_partitions.patch @@ -0,0 +1,234 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: George Kottackal +Date: Sun, 2 Apr 2023 23:04:40 +0100 +Subject: feat: add storage quota capability for partitions + +This patch adds the capability for a storage quota +to be set on partitions.This capability will be +exercised only when physical storage based +partitions are used. + +diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc +index e5185febf2cc3bc28353a0ec3e2c57aa536a3303..80989ba382778819b912b5bb9904a583c958215d 100644 +--- a/content/browser/storage_partition_impl.cc ++++ b/content/browser/storage_partition_impl.cc +@@ -1285,6 +1285,10 @@ void StoragePartitionImpl::Initialize( + base::BindRepeating(&StoragePartitionImpl::GetQuotaSettings, + weak_factory_.GetWeakPtr())); + quota_manager_ = quota_context_->quota_manager(); ++ ++ if (config_.quota()) ++ quota_manager_->SetQuota(config_.quota().value()); ++ + scoped_refptr quota_manager_proxy = + quota_manager_->proxy(); + +diff --git a/content/public/browser/storage_partition_config.cc b/content/public/browser/storage_partition_config.cc +index 81013d6eb993a9a1bfbdf0bea388e249c9045c05..55c4770b8933afbc7ae7e2c4dd17f73f193a0225 100644 +--- a/content/public/browser/storage_partition_config.cc ++++ b/content/public/browser/storage_partition_config.cc +@@ -21,8 +21,10 @@ StoragePartitionConfig& StoragePartitionConfig::operator=( + + // static + StoragePartitionConfig StoragePartitionConfig::CreateDefault( +- BrowserContext* browser_context) { +- return StoragePartitionConfig("", "", browser_context->IsOffTheRecord()); ++ BrowserContext* browser_context, ++ absl::optional quota_size) { ++ return StoragePartitionConfig("", "", browser_context->IsOffTheRecord(), ++ quota_size); + } + + // static +@@ -30,22 +32,26 @@ StoragePartitionConfig StoragePartitionConfig::Create( + BrowserContext* browser_context, + const std::string& partition_domain, + const std::string& partition_name, +- bool in_memory) { ++ bool in_memory, ++ absl::optional quota_size) { + // If a caller tries to pass an empty partition_domain something is seriously + // wrong or the calling code is not explicitly signalling its desire to create + // a default partition by calling CreateDefault(). + CHECK(!partition_domain.empty()); + return StoragePartitionConfig(partition_domain, partition_name, +- in_memory || browser_context->IsOffTheRecord()); ++ in_memory || browser_context->IsOffTheRecord(), ++ quota_size); + } + + StoragePartitionConfig::StoragePartitionConfig( + const std::string& partition_domain, + const std::string& partition_name, +- bool in_memory) ++ bool in_memory, ++ absl::optional quota_size) + : partition_domain_(partition_domain), + partition_name_(partition_name), +- in_memory_(in_memory) {} ++ in_memory_(in_memory), ++ quota_size_(quota_size){} + + absl::optional + StoragePartitionConfig::GetFallbackForBlobUrls() const { +@@ -55,7 +61,8 @@ StoragePartitionConfig::GetFallbackForBlobUrls() const { + return StoragePartitionConfig( + partition_domain_, "", + /*in_memory=*/fallback_to_partition_domain_for_blob_urls_ == +- FallbackMode::kFallbackPartitionInMemory); ++ FallbackMode::kFallbackPartitionInMemory, ++ quota_size_); + } + + bool StoragePartitionConfig::operator<( +diff --git a/content/public/browser/storage_partition_config.h b/content/public/browser/storage_partition_config.h +index e765f81673b08f51f6b7c3370c535fae03d41362..786d53df985637560737230d4c1182daf196d3bc 100644 +--- a/content/public/browser/storage_partition_config.h ++++ b/content/public/browser/storage_partition_config.h +@@ -28,7 +28,8 @@ class CONTENT_EXPORT StoragePartitionConfig { + + // Creates a default config for |browser_context|. If |browser_context| is an + // off-the-record profile, then the config will have |in_memory_| set to true. +- static StoragePartitionConfig CreateDefault(BrowserContext* browser_context); ++ static StoragePartitionConfig CreateDefault(BrowserContext* browser_context, ++ absl::optional quota_size = absl::nullopt); + + // Creates a config tied to a specific domain. + // The |partition_domain| is [a-z]* UTF-8 string, specifying the domain in +@@ -43,11 +44,13 @@ class CONTENT_EXPORT StoragePartitionConfig { + static StoragePartitionConfig Create(BrowserContext* browser_context, + const std::string& partition_domain, + const std::string& partition_name, +- bool in_memory); ++ bool in_memory, ++ absl::optional quota_size = absl::nullopt); + + std::string partition_domain() const { return partition_domain_; } + std::string partition_name() const { return partition_name_; } + bool in_memory() const { return in_memory_; } ++ absl::optional quota() const { return quota_size_; } + + // Returns true if this config was created by CreateDefault() or is + // a copy of a config created with that method. +@@ -94,11 +97,13 @@ class CONTENT_EXPORT StoragePartitionConfig { + + StoragePartitionConfig(const std::string& partition_domain, + const std::string& partition_name, +- bool in_memory); ++ bool in_memory, ++ absl::optional quota_size = absl::nullopt); + + std::string partition_domain_; + std::string partition_name_; + bool in_memory_ = false; ++ absl::optional quota_size_ = absl::nullopt; + FallbackMode fallback_to_partition_domain_for_blob_urls_ = + FallbackMode::kNone; + }; +diff --git a/storage/browser/quota/quota_manager_impl.cc b/storage/browser/quota/quota_manager_impl.cc +index a7cf23f4f4c1876f7b01b6275b218c4622a225e3..1a939a07d8bb205b8063ed8b4fb166672682624a 100644 +--- a/storage/browser/quota/quota_manager_impl.cc ++++ b/storage/browser/quota/quota_manager_impl.cc +@@ -2619,7 +2619,7 @@ void QuotaManagerImpl::GetStorageCapacity(StorageCapacityCallback callback) { + db_runner_->PostTaskAndReplyWithResult( + FROM_HERE, + base::BindOnce(&QuotaManagerImpl::CallGetVolumeInfo, get_volume_info_fn_, +- profile_path_), ++ profile_path_,start_quota_), + base::BindOnce(&QuotaManagerImpl::DidGetStorageCapacity, + weak_factory_.GetWeakPtr())); + } +@@ -3038,16 +3038,26 @@ void QuotaManagerImpl::PostTaskAndReplyWithResultForDBThread( + std::move(reply)); + } + ++ ++void QuotaManagerImpl::SetQuota(int start_quota) { ++ // Set the quota for a browser context */ ++ // If an electron::BrowserWindow uses a partition path ++ // with no existing browser context, then ++ // this quota takes effect ++ start_quota_ = start_quota; ++} ++ + // static + QuotaAvailability QuotaManagerImpl::CallGetVolumeInfo( + GetVolumeInfoFn get_volume_info_fn, +- const base::FilePath& path) { ++ const base::FilePath& path, ++ const absl::optional& quota_size) { + if (!base::CreateDirectory(path)) { + LOG(WARNING) << "Create directory failed for path" << path.value(); + return QuotaAvailability(0, 0); + } + +- const QuotaAvailability quotaAvailability = get_volume_info_fn(path); ++ const QuotaAvailability quotaAvailability = get_volume_info_fn(path,quota_size); + const auto total = quotaAvailability.total; + const auto available = quotaAvailability.available; + +@@ -3069,9 +3079,16 @@ QuotaAvailability QuotaManagerImpl::CallGetVolumeInfo( + } + + // static +-QuotaAvailability QuotaManagerImpl::GetVolumeInfo(const base::FilePath& path) { +- return QuotaAvailability(base::SysInfo::AmountOfTotalDiskSpace(path), +- base::SysInfo::AmountOfFreeDiskSpace(path)); ++QuotaAvailability QuotaManagerImpl::GetVolumeInfo( ++ const base::FilePath& path, ++ const absl::optional& quota_size) { ++ if (!quota_size) { ++ return QuotaAvailability(base::SysInfo::AmountOfTotalDiskSpace(path), ++ base::SysInfo::AmountOfFreeDiskSpace(path)); ++ } else { ++ return QuotaAvailability(base::SysInfo::AmountOfTotalDiskSpace(path), ++ quota_size.value()); ++ } + } + + void QuotaManagerImpl::AddObserver( +diff --git a/storage/browser/quota/quota_manager_impl.h b/storage/browser/quota/quota_manager_impl.h +index 9598b8674ecf386bfcd0423b51dd8ac3f0d7e379..3f5ef0bd50253ba07cc50ffc6a2378bce88e542b 100644 +--- a/storage/browser/quota/quota_manager_impl.h ++++ b/storage/browser/quota/quota_manager_impl.h +@@ -159,7 +159,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl + // Function pointer type used to store the function which returns + // information about the volume containing the given FilePath. + // The value returned is the QuotaAvailability struct. +- using GetVolumeInfoFn = QuotaAvailability (*)(const base::FilePath&); ++ using GetVolumeInfoFn = QuotaAvailability (*)(const base::FilePath&, ++ const absl::optional&); + + static constexpr int64_t kGBytes = 1024 * 1024 * 1024; + static constexpr int64_t kNoLimit = INT64_MAX; +@@ -468,6 +469,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl + eviction_disabled_ = disable; + } + ++ void SetQuota(const int start_quota); ++ + // Testing support for handling corruption in the underlying database. + // + // Runs `corrupter` on the same sequence used to do database I/O, +@@ -743,8 +746,10 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl + bool is_bootstrap_task = false); + + static QuotaAvailability CallGetVolumeInfo(GetVolumeInfoFn get_volume_info_fn, +- const base::FilePath& path); +- static QuotaAvailability GetVolumeInfo(const base::FilePath& path); ++ const base::FilePath& path, ++ const absl::optional& quota_size = absl::nullopt); ++ static QuotaAvailability GetVolumeInfo(const base::FilePath& path, ++ const absl::optional& quota_size = absl::nullopt); + + const bool is_incognito_; + const base::FilePath profile_path_; +@@ -838,6 +843,8 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) QuotaManagerImpl + // QuotaManagerImpl::GetVolumeInfo. + GetVolumeInfoFn get_volume_info_fn_; + ++ absl::optional start_quota_ = absl::nullopt; ++ + std::unique_ptr eviction_helper_; + std::map> + bucket_set_data_deleters_; diff --git a/shell/browser/electron_browser_context.cc b/shell/browser/electron_browser_context.cc index 3331898adc13a..8191dccf7b39c 100644 --- a/shell/browser/electron_browser_context.cc +++ b/shell/browser/electron_browser_context.cc @@ -139,6 +139,9 @@ ElectronBrowserContext::ElectronBrowserContext( &partition_location)) { const base::FilePath& partition_path = filepath_partition->get(); path_ = std::move(partition_path); + if (auto quota_size_opt = options.FindInt("quota")) { + user_set_quota_ = quota_size_opt.value(); + } } BrowserContextDependencyManager::GetInstance()->MarkBrowserContextLive(this); @@ -173,6 +176,15 @@ ElectronBrowserContext::~ElectronBrowserContext() { std::move(resource_context_)); } +content::StoragePartition* +ElectronBrowserContext::GetDefaultStoragePartition() { + if (user_set_quota_) { + return GetStoragePartition( + content::StoragePartitionConfig::CreateDefault(this, user_set_quota_)); + } + return BrowserContext::GetDefaultStoragePartition(); +} + void ElectronBrowserContext::InitPrefs() { auto prefs_path = GetPath().Append(FILE_PATH_LITERAL("Preferences")); ScopedAllowBlockingForElectron allow_blocking; diff --git a/shell/browser/electron_browser_context.h b/shell/browser/electron_browser_context.h index 2774f0a9df7d2..50bd56a55e268 100644 --- a/shell/browser/electron_browser_context.h +++ b/shell/browser/electron_browser_context.h @@ -155,6 +155,7 @@ class ElectronBrowserContext : public content::BrowserContext { std::string GetMediaDeviceIDSalt() override; content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; content::BrowserPluginGuestManager* GetGuestManager() override; + content::StoragePartition* GetDefaultStoragePartition(); content::PlatformNotificationService* GetPlatformNotificationService() override; content::PermissionControllerDelegate* GetPermissionControllerDelegate() @@ -255,6 +256,7 @@ class ElectronBrowserContext : public content::BrowserContext { base::FilePath path_; bool in_memory_ = false; bool use_cache_ = true; + absl::optional user_set_quota_ = absl::nullopt; int max_cache_size_ = 0; #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) diff --git a/spec/api-session-spec.ts b/spec/api-session-spec.ts index 18cf97a1f8e40..c3b6341edcc89 100644 --- a/spec/api-session-spec.ts +++ b/spec/api-session-spec.ts @@ -39,6 +39,37 @@ describe('session module', () => { }); }); + describe('session.fromPath(path) with a quota.', () => { + const pathloc = 'testsession'; + after(() => { + const tmppath = require('electron').app.getPath('temp') + path.sep + pathloc; + if (fs.existsSync(tmppath)) { fs.rmSync(tmppath, { recursive: true, force: true }); } + }); + it('Assert that a set quota value is returned when a quota value is specified for a session', async () => { + const quotasize = 256000; + const tmppath = require('electron').app.getPath('temp') + path.sep + pathloc; + if (fs.existsSync(tmppath)) { fs.rmSync(tmppath, { recursive: true, force: true }); } + fs.mkdirSync(tmppath); + const localsession = session.fromPath(tmppath, { quota: quotasize }); + const w = new BrowserWindow({ + show: false, + webPreferences: { + session: localsession + } + }); + + const readQuotaSize: any = () => { + return w.webContents.executeJavaScript(` + navigator.storage.estimate().then(estimate => estimate.quota).catch(err => err.message); + `); + }; + + await w.loadFile(path.join(fixtures, 'api', 'localstorage.html')); + const size = await readQuotaSize(); + expect(size).to.equal(quotasize); + }); + }); + describe('ses.cookies', () => { const name = '0'; const value = '0';