Skip to content

Commit c34119c

Browse files
committed
LibWebView: Add a settings section to manage browsing data caches
This adds a section to allow users to clear the HTTP disk cache and cookies / local storage / session storage. There are a few options to limit this action to a specific time range (e.g. "last hour"). The user is informed how much disk space is being used currently, and how much will be removed given the selected time range. The idea is that in the future, we can add more settings here to auto- delete data on exit, disable caching altogether, etc.
1 parent 48aa16d commit c34119c

File tree

7 files changed

+307
-0
lines changed

7 files changed

+307
-0
lines changed

Base/res/ladybird/about-pages/settings.html

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,22 @@
110110
margin-top: 20px;
111111
}
112112

113+
.input-field-container {
114+
display: flex;
115+
align-items: center;
116+
justify-content: flex-start;
117+
gap: 10px;
118+
}
119+
120+
.input-field-container + .input-field-container {
121+
margin-top: 16px;
122+
}
123+
124+
.input-field-container label,
125+
.input-field-container p {
126+
margin: 0;
127+
}
128+
113129
.forcibly-enabled {
114130
font-size: 14px;
115131
opacity: 0.6;
@@ -372,6 +388,13 @@ <h1>Ladybird Settings</h1>
372388
<div class="card">
373389
<div class="card-header">Privacy</div>
374390
<div class="card-body">
391+
<div class="card-group inline-container">
392+
<span>Browsing Data</span>
393+
<button id="clear-browsing-data" class="secondary-button">Clear...</button>
394+
</div>
395+
396+
<hr />
397+
375398
<div class="card-group">
376399
<div class="inline-container">
377400
<label for="global-privacy-control-toggle">Enable Global Privacy Control</label>
@@ -495,6 +518,45 @@ <h3 id="site-settings-title" class="dialog-title"></h3>
495518
</div>
496519
</dialog>
497520

521+
<dialog id="clear-browsing-data-dialog">
522+
<div class="dialog-header">
523+
<h3 id="clear-browsing-data-title" class="dialog-title">Clear Browsing Data</h3>
524+
<button id="clear-browsing-data-close" class="close-button dialog-button">&times;</button>
525+
</div>
526+
<div class="dialog-body">
527+
<p id="clear-browsing-data-total-size" class="description"></p>
528+
<hr />
529+
530+
<div class="input-field-container">
531+
<p>From:</p>
532+
<select id="clear-browsing-data-time-range">
533+
<option value="lastHour">Last hour</option>
534+
<option value="last4Hours">Last 4 hours</option>
535+
<option value="today">Today</option>
536+
<option value="all" selected>All time</option>
537+
</select>
538+
</div>
539+
540+
<div class="input-field-container">
541+
<input id="clear-browsing-data-cached-files" type="checkbox" value="" checked />
542+
<label for="clear-browsing-data-cached-files">
543+
Cached files<span id="clear-browsing-data-cached-files-size"></span>
544+
<p class="description">Remove items that help pages load faster</p>
545+
</label>
546+
</div>
547+
<div class="input-field-container">
548+
<input id="clear-browsing-data-site-data" type="checkbox" value="" checked />
549+
<label for="clear-browsing-data-site-data">
550+
Cookies and site data<span id="clear-browsing-data-site-data-size"></span>
551+
<p class="description">Remove items that may sign you out of most sites</p>
552+
</label>
553+
</div>
554+
</div>
555+
<div class="dialog-footer">
556+
<button id="clear-browsing-data-remove-data" class="secondary-button">Remove data</button>
557+
</div>
558+
</dialog>
559+
498560
<script>
499561
// FIXME: When we support per-glyph font fallbacks, replace these SVGs with analogous code points.
500562
// https://github.com/LadybirdBrowser/ladybird/issues/864
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,125 @@
1+
const clearBrowsingData = document.querySelector("#clear-browsing-data");
2+
const clearBrowsingDataCachedFiles = document.querySelector("#clear-browsing-data-cached-files");
3+
const clearBrowsingDataCachedFilesSize = document.querySelector("#clear-browsing-data-cached-files-size");
4+
const clearBrowsingDataClose = document.querySelector("#clear-browsing-data-close");
5+
const clearBrowsingDataDialog = document.querySelector("#clear-browsing-data-dialog");
6+
const clearBrowsingDataRemoveData = document.querySelector("#clear-browsing-data-remove-data");
7+
const clearBrowsingDataSiteData = document.querySelector("#clear-browsing-data-site-data");
8+
const clearBrowsingDataSiteDataSize = document.querySelector("#clear-browsing-data-site-data-size");
9+
const clearBrowsingDataTimeRange = document.querySelector("#clear-browsing-data-time-range");
10+
const clearBrowsingDataTotalSize = document.querySelector("#clear-browsing-data-total-size");
111
const globalPrivacyControlToggle = document.querySelector("#global-privacy-control-toggle");
212

13+
const BYTE_UNITS = ["byte", "kilobyte", "megabyte", "gigabyte", "terabyte"];
14+
15+
const BYTE_FORMATTERS = {
16+
byte: undefined,
17+
kilobyte: undefined,
18+
megabyte: undefined,
19+
gigabyte: undefined,
20+
terabyte: undefined,
21+
};
22+
23+
function formatBytes(bytes) {
24+
let index = 0;
25+
while (bytes >= 1024 && index < BYTE_UNITS.length - 1) {
26+
bytes /= 1024;
27+
++index;
28+
}
29+
30+
const unit = BYTE_UNITS[index];
31+
32+
if (!BYTE_FORMATTERS[unit]) {
33+
BYTE_FORMATTERS[unit] = new Intl.NumberFormat([], {
34+
style: "unit",
35+
unit: unit,
36+
unitDisplay: unit === "byte" ? "long" : "short",
37+
maximumFractionDigits: 1,
38+
});
39+
}
40+
41+
return BYTE_FORMATTERS[unit].format(bytes);
42+
}
43+
344
function loadSettings(settings) {
445
globalPrivacyControlToggle.checked = settings.globalPrivacyControl;
546
}
647

48+
function computeTimeRange() {
49+
const now = Temporal.Now.zonedDateTimeISO();
50+
51+
switch (clearBrowsingDataTimeRange.value) {
52+
case "lastHour":
53+
return now.subtract({ hours: 1 });
54+
case "last4Hours":
55+
return now.subtract({ hours: 4 });
56+
case "today":
57+
return now.startOfDay();
58+
case "all":
59+
return null;
60+
default:
61+
console.error(`Unrecognized time range: ${clearBrowsingDataTimeRange.value}`);
62+
return now;
63+
}
64+
}
65+
66+
function estimateBrowsingDataSizes() {
67+
const since = computeTimeRange();
68+
69+
ladybird.sendMessage("estimateBrowsingDataSizes", {
70+
since: since?.epochMilliseconds,
71+
});
72+
}
73+
74+
function updateBrowsingDataSizes(sizes) {
75+
const totalSize = sizes.totalCacheSize + sizes.totalSiteDataSize;
76+
77+
clearBrowsingDataTotalSize.innerText = `Your browsing data is currently using ${formatBytes(totalSize)} of disk space`;
78+
79+
clearBrowsingDataCachedFilesSize.innerText = ` (remove ${formatBytes(sizes.cacheSizeSinceRequestedTime)})`;
80+
clearBrowsingDataSiteDataSize.innerText = ` (remove ${formatBytes(sizes.siteDataSizeSinceRequestedTime)})`;
81+
}
82+
83+
clearBrowsingData.addEventListener("click", () => {
84+
estimateBrowsingDataSizes();
85+
clearBrowsingDataDialog.showModal();
86+
});
87+
88+
clearBrowsingDataTimeRange.addEventListener("change", () => {
89+
estimateBrowsingDataSizes();
90+
});
91+
92+
clearBrowsingDataClose.addEventListener("click", () => {
93+
clearBrowsingDataDialog.close();
94+
});
95+
96+
function setRemoveDataEnabledState() {
97+
clearBrowsingDataRemoveData.disabled = !clearBrowsingDataCachedFiles.checked && !clearBrowsingDataSiteData.checked;
98+
}
99+
100+
clearBrowsingDataCachedFiles.addEventListener("change", setRemoveDataEnabledState);
101+
clearBrowsingDataSiteData.addEventListener("change", setRemoveDataEnabledState);
102+
103+
clearBrowsingDataRemoveData.addEventListener("click", () => {
104+
const since = computeTimeRange();
105+
106+
ladybird.sendMessage("clearBrowsingData", {
107+
since: since?.epochMilliseconds,
108+
cachedFiles: clearBrowsingDataCachedFiles.checked,
109+
siteData: clearBrowsingDataSiteData.checked,
110+
});
111+
112+
clearBrowsingDataDialog.close();
113+
});
114+
7115
globalPrivacyControlToggle.addEventListener("change", () => {
8116
ladybird.sendMessage("setGlobalPrivacyControl", globalPrivacyControlToggle.checked);
9117
});
10118

11119
document.addEventListener("WebUIMessage", event => {
12120
if (event.detail.name === "loadSettings") {
13121
loadSettings(event.detail.data);
122+
} else if (event.detail.name === "estimatedBrowsingDataSizes") {
123+
updateBrowsingDataSizes(event.detail.data);
14124
}
15125
});

Base/res/ladybird/ladybird.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@ select:focus {
150150
outline: none;
151151
}
152152

153+
input[type="checkbox"] {
154+
width: 14px;
155+
height: 14px;
156+
157+
accent-color: var(--violet-100);
158+
159+
outline: none;
160+
}
161+
153162
input[type="checkbox"][switch] {
154163
width: 50px;
155164
height: 24px;

Libraries/LibWebView/Application.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,52 @@ void Application::insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresen
661661
m_clipboard = move(entry);
662662
}
663663

664+
NonnullRefPtr<Core::Promise<Application::BrowsingDataSizes>> Application::estimate_browsing_data_size_accessed_since(UnixDateTime since)
665+
{
666+
auto promise = Core::Promise<BrowsingDataSizes>::construct();
667+
668+
m_request_server_client->estimate_cache_size_accessed_since(since)
669+
->when_resolved([this, promise, since](Requests::CacheSizes cache_sizes) {
670+
auto cookie_sizes = m_cookie_jar->estimate_storage_size_accessed_since(since);
671+
auto storage_sizes = m_storage_jar->estimate_storage_size_accessed_since(since);
672+
673+
BrowsingDataSizes sizes;
674+
675+
sizes.cache_size_since_requested_time = cache_sizes.since_requested_time;
676+
sizes.total_cache_size = cache_sizes.total;
677+
678+
sizes.site_data_size_since_requested_time = cookie_sizes.since_requested_time + storage_sizes.since_requested_time;
679+
sizes.total_site_data_size = cookie_sizes.total + storage_sizes.total;
680+
681+
promise->resolve(sizes);
682+
})
683+
.when_rejected([promise](Error& error) {
684+
promise->reject(move(error));
685+
});
686+
687+
return promise;
688+
}
689+
690+
void Application::clear_browsing_data(ClearBrowsingDataOptions const& options)
691+
{
692+
if (options.delete_cached_files == ClearBrowsingDataOptions::Delete::Yes) {
693+
m_request_server_client->async_remove_cache_entries_accessed_since(options.since);
694+
695+
// FIXME: Maybe we should forward the "since" parameter to the WebContent process, but the in-memory cache is
696+
// transient anyways, so just assuming they were all accessed in the last hour is fine for now.
697+
ViewImplementation::for_each_view([](ViewImplementation& view) {
698+
// FIXME: This should be promoted from a debug request to a proper endpoint.
699+
view.debug_request("clear-cache"sv);
700+
return IterationDecision::Continue;
701+
});
702+
}
703+
704+
if (options.delete_site_data == ClearBrowsingDataOptions::Delete::Yes) {
705+
m_cookie_jar->expire_cookies_accessed_since(options.since);
706+
m_storage_jar->remove_items_accessed_since(options.since);
707+
}
708+
}
709+
664710
void Application::initialize_actions()
665711
{
666712
auto debug_request = [this](auto request) {

Libraries/LibWebView/Application.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,27 @@ class WEBVIEW_API Application : public DevTools::DevToolsDelegate {
8383
virtual Vector<Web::Clipboard::SystemClipboardRepresentation> clipboard_entries() const;
8484
virtual void insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation);
8585

86+
struct BrowsingDataSizes {
87+
u64 cache_size_since_requested_time { 0 };
88+
u64 total_cache_size { 0 };
89+
90+
u64 site_data_size_since_requested_time { 0 };
91+
u64 total_site_data_size { 0 };
92+
};
93+
NonnullRefPtr<Core::Promise<BrowsingDataSizes>> estimate_browsing_data_size_accessed_since(UnixDateTime since);
94+
95+
struct ClearBrowsingDataOptions {
96+
enum class Delete {
97+
No,
98+
Yes,
99+
};
100+
101+
UnixDateTime since { UnixDateTime::earliest() };
102+
Delete delete_cached_files { Delete::No };
103+
Delete delete_site_data { Delete::No };
104+
};
105+
void clear_browsing_data(ClearBrowsingDataOptions const&);
106+
86107
Action& reload_action() { return *m_reload_action; }
87108
Action& copy_selection_action() { return *m_copy_selection_action; }
88109
Action& paste_action() { return *m_paste_action; }

Libraries/LibWebView/WebUI/SettingsUI.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ void SettingsUI::register_interfaces()
6363
remove_all_site_setting_filters(data);
6464
});
6565

66+
register_interface("estimateBrowsingDataSizes"sv, [this](auto const& data) {
67+
estimate_browsing_data_sizes(data);
68+
});
69+
register_interface("clearBrowsingData"sv, [this](auto const& data) {
70+
clear_browsing_data(data);
71+
});
6672
register_interface("setGlobalPrivacyControl"sv, [this](auto const& data) {
6773
set_global_privacy_control(data);
6874
});
@@ -275,6 +281,57 @@ void SettingsUI::remove_all_site_setting_filters(JsonValue const& site_setting)
275281
load_current_settings();
276282
}
277283

284+
void SettingsUI::estimate_browsing_data_sizes(JsonValue const& options)
285+
{
286+
if (!options.is_object())
287+
return;
288+
289+
auto& application = Application::the();
290+
291+
auto since = [&]() {
292+
if (auto since = options.as_object().get_integer<i64>("since"sv); since.has_value())
293+
return UnixDateTime::from_milliseconds_since_epoch(*since);
294+
return UnixDateTime::earliest();
295+
}();
296+
297+
application.estimate_browsing_data_size_accessed_since(since)
298+
->when_resolved([this](Application::BrowsingDataSizes sizes) {
299+
JsonObject result;
300+
301+
result.set("cacheSizeSinceRequestedTime"sv, sizes.cache_size_since_requested_time);
302+
result.set("totalCacheSize"sv, sizes.total_cache_size);
303+
304+
result.set("siteDataSizeSinceRequestedTime"sv, sizes.site_data_size_since_requested_time);
305+
result.set("totalSiteDataSize"sv, sizes.total_site_data_size);
306+
307+
async_send_message("estimatedBrowsingDataSizes"sv, move(result));
308+
})
309+
.when_rejected([](Error const& error) {
310+
dbgln("Failed to estimate browsing data sizes: {}", error);
311+
});
312+
}
313+
314+
void SettingsUI::clear_browsing_data(JsonValue const& options)
315+
{
316+
if (!options.is_object())
317+
return;
318+
319+
Application::ClearBrowsingDataOptions clear_browsing_data_options;
320+
321+
if (auto since = options.as_object().get_integer<i64>("since"sv); since.has_value())
322+
clear_browsing_data_options.since = UnixDateTime::from_milliseconds_since_epoch(*since);
323+
324+
clear_browsing_data_options.delete_cached_files = options.as_object().get_bool("cachedFiles"sv).value_or(false)
325+
? Application::ClearBrowsingDataOptions::Delete::Yes
326+
: Application::ClearBrowsingDataOptions::Delete::No;
327+
328+
clear_browsing_data_options.delete_site_data = options.as_object().get_bool("siteData"sv).value_or(false)
329+
? Application::ClearBrowsingDataOptions::Delete::Yes
330+
: Application::ClearBrowsingDataOptions::Delete::No;
331+
332+
Application::the().clear_browsing_data(clear_browsing_data_options);
333+
}
334+
278335
void SettingsUI::set_global_privacy_control(JsonValue const& global_privacy_control)
279336
{
280337
if (!global_privacy_control.is_bool())

Libraries/LibWebView/WebUI/SettingsUI.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class WEBVIEW_API SettingsUI : public WebUI {
3636
void remove_site_setting_filter(JsonValue const&);
3737
void remove_all_site_setting_filters(JsonValue const&);
3838

39+
void estimate_browsing_data_sizes(JsonValue const&);
40+
void clear_browsing_data(JsonValue const&);
3941
void set_global_privacy_control(JsonValue const&);
4042

4143
void set_dns_settings(JsonValue const&);

0 commit comments

Comments
 (0)