feat: Add shared dictionary management APIs (#44950)

* Add bare-bones GetSharedDictionaryUsageInfo

* Add GetSharedDictionaryInfo()

* Improve API, use isolation keys

* Add documentation

* Update docs/api/session.md



* Update shell/browser/api/electron_api_session.cc



* Add tests

* Implement feedback <3

* Improve tests

* chore: lint

* docs: add note about clearing cache in ses.clearData

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Felix Rieseberg <fr@makenotion.com>
This commit is contained in:
trop[bot] 2024-12-04 13:35:08 -08:00 committed by GitHub
parent c54f1f98fe
commit 9a7848ced1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 384 additions and 0 deletions

View file

@ -54,6 +54,7 @@
#include "net/http/http_util.h"
#include "services/network/network_service.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/request_destination.h"
#include "services/network/public/mojom/clear_data_filter.mojom.h"
#include "shell/browser/api/electron_api_app.h"
#include "shell/browser/api/electron_api_cookies.h"
@ -79,6 +80,7 @@
#include "shell/common/gin_converters/gurl_converter.h"
#include "shell/common/gin_converters/media_converter.h"
#include "shell/common/gin_converters/net_converter.h"
#include "shell/common/gin_converters/time_converter.h"
#include "shell/common/gin_converters/usb_protected_classes_converter.h"
#include "shell/common/gin_converters/value_converter.h"
#include "shell/common/gin_helper/dictionary.h"
@ -1074,6 +1076,178 @@ std::vector<base::FilePath> Session::GetPreloads() const {
return prefs->preloads();
}
/**
* Exposes the network service's ClearSharedDictionaryCacheForIsolationKey
* method, allowing clearing the Shared Dictionary cache for a given isolation
* key. Details about the feature available at
* https://developer.chrome.com/blog/shared-dictionary-compression
*/
v8::Local<v8::Promise> Session::ClearSharedDictionaryCacheForIsolationKey(
const gin_helper::Dictionary& options) {
gin_helper::Promise<void> promise(isolate_);
auto handle = promise.GetHandle();
GURL frame_origin_url, top_frame_site_url;
if (!options.Get("frameOrigin", &frame_origin_url) ||
!options.Get("topFrameSite", &top_frame_site_url)) {
promise.RejectWithErrorMessage(
"Must provide frameOrigin and topFrameSite strings to "
"`clearSharedDictionaryCacheForIsolationKey`");
return handle;
}
if (!frame_origin_url.is_valid() || !top_frame_site_url.is_valid()) {
promise.RejectWithErrorMessage(
"Invalid URLs provided for frameOrigin or topFrameSite");
return handle;
}
url::Origin frame_origin = url::Origin::Create(frame_origin_url);
net::SchemefulSite top_frame_site(top_frame_site_url);
net::SharedDictionaryIsolationKey isolation_key(frame_origin, top_frame_site);
browser_context_->GetDefaultStoragePartition()
->GetNetworkContext()
->ClearSharedDictionaryCacheForIsolationKey(
isolation_key,
base::BindOnce(gin_helper::Promise<void>::ResolvePromise,
std::move(promise)));
return handle;
}
/**
* Exposes the network service's ClearSharedDictionaryCache
* method, allowing clearing the Shared Dictionary cache.
* https://developer.chrome.com/blog/shared-dictionary-compression
*/
v8::Local<v8::Promise> Session::ClearSharedDictionaryCache() {
gin_helper::Promise<void> promise(isolate_);
auto handle = promise.GetHandle();
browser_context_->GetDefaultStoragePartition()
->GetNetworkContext()
->ClearSharedDictionaryCache(
base::Time(), base::Time::Max(),
nullptr /*mojom::ClearDataFilterPtr*/,
base::BindOnce(gin_helper::Promise<void>::ResolvePromise,
std::move(promise)));
return handle;
}
/**
* Exposes the network service's GetSharedDictionaryInfo method, allowing
* inspection of Shared Dictionary information. Details about the feature
* available at https://developer.chrome.com/blog/shared-dictionary-compression
*/
v8::Local<v8::Promise> Session::GetSharedDictionaryInfo(
const gin_helper::Dictionary& options) {
gin_helper::Promise<std::vector<gin_helper::Dictionary>> promise(isolate_);
auto handle = promise.GetHandle();
GURL frame_origin_url, top_frame_site_url;
if (!options.Get("frameOrigin", &frame_origin_url) ||
!options.Get("topFrameSite", &top_frame_site_url)) {
promise.RejectWithErrorMessage(
"Must provide frameOrigin and topFrameSite strings");
return handle;
}
if (!frame_origin_url.is_valid() || !top_frame_site_url.is_valid()) {
promise.RejectWithErrorMessage(
"Invalid URLs provided for frameOrigin or topFrameSite");
return handle;
}
url::Origin frame_origin = url::Origin::Create(frame_origin_url);
net::SchemefulSite top_frame_site(top_frame_site_url);
net::SharedDictionaryIsolationKey isolation_key(frame_origin, top_frame_site);
browser_context_->GetDefaultStoragePartition()
->GetNetworkContext()
->GetSharedDictionaryInfo(
isolation_key,
base::BindOnce(
[](gin_helper::Promise<std::vector<gin_helper::Dictionary>>
promise,
std::vector<network::mojom::SharedDictionaryInfoPtr> info) {
v8::Isolate* isolate = promise.isolate();
v8::HandleScope handle_scope(isolate);
std::vector<gin_helper::Dictionary> result;
result.reserve(info.size());
for (const auto& item : info) {
gin_helper::Dictionary dict =
gin_helper::Dictionary::CreateEmpty(isolate);
dict.Set("match", item->match);
// Convert RequestDestination enum values to strings
std::vector<std::string> destinations;
for (const auto& dest : item->match_dest) {
destinations.push_back(
network::RequestDestinationToString(dest));
}
dict.Set("matchDestinations", destinations);
dict.Set("id", item->id);
dict.Set("dictionaryUrl", item->dictionary_url.spec());
dict.Set("lastFetchTime", item->last_fetch_time);
dict.Set("responseTime", item->response_time);
dict.Set("expirationDuration",
item->expiration.InMillisecondsF());
dict.Set("lastUsedTime", item->last_used_time);
dict.Set("size", item->size);
dict.Set("hash", net::HashValue(item->hash).ToString());
result.push_back(dict);
}
promise.Resolve(result);
},
std::move(promise)));
return handle;
}
/**
* Exposes the network service's GetSharedDictionaryUsageInfo method, allowing
* inspection of Shared Dictionary information. Details about the feature
* available at https://developer.chrome.com/blog/shared-dictionary-compression
*/
v8::Local<v8::Promise> Session::GetSharedDictionaryUsageInfo() {
gin_helper::Promise<std::vector<gin_helper::Dictionary>> promise(isolate_);
auto handle = promise.GetHandle();
browser_context_->GetDefaultStoragePartition()
->GetNetworkContext()
->GetSharedDictionaryUsageInfo(base::BindOnce(
[](gin_helper::Promise<std::vector<gin_helper::Dictionary>> promise,
const std::vector<net::SharedDictionaryUsageInfo>& info) {
v8::Isolate* isolate = promise.isolate();
v8::HandleScope handle_scope(isolate);
std::vector<gin_helper::Dictionary> result;
result.reserve(info.size());
for (const auto& item : info) {
gin_helper::Dictionary dict =
gin_helper::Dictionary::CreateEmpty(isolate);
dict.Set("frameOrigin",
item.isolation_key.frame_origin().Serialize());
dict.Set("topFrameSite",
item.isolation_key.top_frame_site().Serialize());
dict.Set("totalSizeBytes", item.total_size_bytes);
result.push_back(dict);
}
promise.Resolve(result);
},
std::move(promise)));
return handle;
}
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
v8::Local<v8::Promise> Session::LoadExtension(
const base::FilePath& extension_path,
@ -1627,6 +1801,13 @@ void Session::FillObjectTemplate(v8::Isolate* isolate,
&Session::CreateInterruptedDownload)
.SetMethod("setPreloads", &Session::SetPreloads)
.SetMethod("getPreloads", &Session::GetPreloads)
.SetMethod("getSharedDictionaryUsageInfo",
&Session::GetSharedDictionaryUsageInfo)
.SetMethod("getSharedDictionaryInfo", &Session::GetSharedDictionaryInfo)
.SetMethod("clearSharedDictionaryCache",
&Session::ClearSharedDictionaryCache)
.SetMethod("clearSharedDictionaryCacheForIsolationKey",
&Session::ClearSharedDictionaryCacheForIsolationKey)
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
.SetMethod("loadExtension", &Session::LoadExtension)
.SetMethod("removeExtension", &Session::RemoveExtension)