mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 11:49:44 +00:00
LibWeb/HTML: Enforce quota limit for local and session storage
Fixes a timeout/crash for the two commited WPT tests.
This commit is contained in:
parent
e767029e24
commit
f3ecd23a21
Notes:
github-actions[bot]
2025-01-02 10:39:50 +00:00
Author: https://github.com/shannonbooth
Commit: f3ecd23a21
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3070
Reviewed-by: https://github.com/awesomekling
Reviewed-by: https://github.com/konradekk
7 changed files with 74 additions and 8 deletions
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
||||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
|
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -14,13 +15,14 @@ namespace Web::HTML {
|
||||||
|
|
||||||
GC_DEFINE_ALLOCATOR(Storage);
|
GC_DEFINE_ALLOCATOR(Storage);
|
||||||
|
|
||||||
GC::Ref<Storage> Storage::create(JS::Realm& realm)
|
GC::Ref<Storage> Storage::create(JS::Realm& realm, u64 quota_bytes)
|
||||||
{
|
{
|
||||||
return realm.create<Storage>(realm);
|
return realm.create<Storage>(realm, quota_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::Storage(JS::Realm& realm)
|
Storage::Storage(JS::Realm& realm, u64 quota_bytes)
|
||||||
: Bindings::PlatformObject(realm)
|
: Bindings::PlatformObject(realm)
|
||||||
|
, m_quota_bytes(quota_bytes)
|
||||||
{
|
{
|
||||||
m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
|
m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
|
||||||
.supports_indexed_properties = true,
|
.supports_indexed_properties = true,
|
||||||
|
@ -78,6 +80,8 @@ Optional<String> Storage::get_item(StringView key) const
|
||||||
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-setitem
|
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-setitem
|
||||||
WebIDL::ExceptionOr<void> Storage::set_item(String const& key, String const& value)
|
WebIDL::ExceptionOr<void> Storage::set_item(String const& key, String const& value)
|
||||||
{
|
{
|
||||||
|
auto& realm = this->realm();
|
||||||
|
|
||||||
// 1. Let oldValue be null.
|
// 1. Let oldValue be null.
|
||||||
String old_value;
|
String old_value;
|
||||||
|
|
||||||
|
@ -85,6 +89,7 @@ WebIDL::ExceptionOr<void> Storage::set_item(String const& key, String const& val
|
||||||
bool reorder = true;
|
bool reorder = true;
|
||||||
|
|
||||||
// 3. If this's map[key] exists:
|
// 3. If this's map[key] exists:
|
||||||
|
auto new_size = m_stored_bytes;
|
||||||
if (auto it = m_map.find(key); it != m_map.end()) {
|
if (auto it = m_map.find(key); it != m_map.end()) {
|
||||||
// 1. Set oldValue to this's map[key].
|
// 1. Set oldValue to this's map[key].
|
||||||
old_value = it->value;
|
old_value = it->value;
|
||||||
|
@ -95,12 +100,18 @@ WebIDL::ExceptionOr<void> Storage::set_item(String const& key, String const& val
|
||||||
|
|
||||||
// 3. Set reorder to false.
|
// 3. Set reorder to false.
|
||||||
reorder = false;
|
reorder = false;
|
||||||
|
} else {
|
||||||
|
new_size += key.bytes().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: 4. If value cannot be stored, then throw a "QuotaExceededError" DOMException exception.
|
// 4. If value cannot be stored, then throw a "QuotaExceededError" DOMException exception.
|
||||||
|
new_size += value.bytes().size() - old_value.bytes().size();
|
||||||
|
if (new_size > m_quota_bytes)
|
||||||
|
return WebIDL::QuotaExceededError::create(realm, MUST(String::formatted("Unable to store more than {} bytes in storage"sv, m_quota_bytes)));
|
||||||
|
|
||||||
// 5. Set this's map[key] to value.
|
// 5. Set this's map[key] to value.
|
||||||
m_map.set(key, value);
|
m_map.set(key, value);
|
||||||
|
m_stored_bytes = new_size;
|
||||||
|
|
||||||
// 6. If reorder is true, then reorder this.
|
// 6. If reorder is true, then reorder this.
|
||||||
if (reorder)
|
if (reorder)
|
||||||
|
@ -126,6 +137,7 @@ void Storage::remove_item(StringView key)
|
||||||
|
|
||||||
// 3. Remove this's map[key].
|
// 3. Remove this's map[key].
|
||||||
m_map.remove(it);
|
m_map.remove(it);
|
||||||
|
m_stored_bytes = m_stored_bytes - key.bytes().size() - old_value.bytes().size();
|
||||||
|
|
||||||
// 4. Reorder this.
|
// 4. Reorder this.
|
||||||
reorder();
|
reorder();
|
||||||
|
|
|
@ -18,7 +18,7 @@ class Storage : public Bindings::PlatformObject {
|
||||||
GC_DECLARE_ALLOCATOR(Storage);
|
GC_DECLARE_ALLOCATOR(Storage);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] static GC::Ref<Storage> create(JS::Realm&);
|
[[nodiscard]] static GC::Ref<Storage> create(JS::Realm&, u64 quota_bytes);
|
||||||
~Storage();
|
~Storage();
|
||||||
|
|
||||||
size_t length() const;
|
size_t length() const;
|
||||||
|
@ -33,7 +33,7 @@ public:
|
||||||
void dump() const;
|
void dump() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit Storage(JS::Realm&);
|
explicit Storage(JS::Realm&, u64 quota_limit);
|
||||||
|
|
||||||
virtual void initialize(JS::Realm&) override;
|
virtual void initialize(JS::Realm&) override;
|
||||||
|
|
||||||
|
@ -49,6 +49,8 @@ private:
|
||||||
void broadcast(StringView key, StringView old_value, StringView new_value);
|
void broadcast(StringView key, StringView old_value, StringView new_value);
|
||||||
|
|
||||||
OrderedHashMap<String, String> m_map;
|
OrderedHashMap<String, String> m_map;
|
||||||
|
u64 m_quota_bytes { 0 };
|
||||||
|
u64 m_stored_bytes { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -447,10 +447,13 @@ void Window::fire_a_page_transition_event(FlyString const& event_name, bool pers
|
||||||
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-localstorage
|
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-localstorage
|
||||||
WebIDL::ExceptionOr<GC::Ref<Storage>> Window::local_storage()
|
WebIDL::ExceptionOr<GC::Ref<Storage>> Window::local_storage()
|
||||||
{
|
{
|
||||||
|
// See table in: https://storage.spec.whatwg.org/#registered-storage-endpoints
|
||||||
|
constexpr u64 quota_bytes = 5 * MiB;
|
||||||
|
|
||||||
// FIXME: Implement according to spec.
|
// FIXME: Implement according to spec.
|
||||||
static HashMap<URL::Origin, GC::Root<Storage>> local_storage_per_origin;
|
static HashMap<URL::Origin, GC::Root<Storage>> local_storage_per_origin;
|
||||||
auto storage = local_storage_per_origin.ensure(associated_document().origin(), [this]() -> GC::Root<Storage> {
|
auto storage = local_storage_per_origin.ensure(associated_document().origin(), [this]() -> GC::Root<Storage> {
|
||||||
return Storage::create(realm());
|
return Storage::create(realm(), quota_bytes);
|
||||||
});
|
});
|
||||||
return GC::Ref { *storage };
|
return GC::Ref { *storage };
|
||||||
}
|
}
|
||||||
|
@ -458,10 +461,13 @@ WebIDL::ExceptionOr<GC::Ref<Storage>> Window::local_storage()
|
||||||
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-sessionstorage
|
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-sessionstorage
|
||||||
WebIDL::ExceptionOr<GC::Ref<Storage>> Window::session_storage()
|
WebIDL::ExceptionOr<GC::Ref<Storage>> Window::session_storage()
|
||||||
{
|
{
|
||||||
|
// See table in: https://storage.spec.whatwg.org/#registered-storage-endpoints
|
||||||
|
constexpr u64 quota_bytes = 5 * MiB;
|
||||||
|
|
||||||
// FIXME: Implement according to spec.
|
// FIXME: Implement according to spec.
|
||||||
static HashMap<URL::Origin, GC::Root<Storage>> session_storage_per_origin;
|
static HashMap<URL::Origin, GC::Root<Storage>> session_storage_per_origin;
|
||||||
auto storage = session_storage_per_origin.ensure(associated_document().origin(), [this]() -> GC::Root<Storage> {
|
auto storage = session_storage_per_origin.ensure(associated_document().origin(), [this]() -> GC::Root<Storage> {
|
||||||
return Storage::create(realm());
|
return Storage::create(realm(), quota_bytes);
|
||||||
});
|
});
|
||||||
return GC::Ref { *storage };
|
return GC::Ref { *storage };
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
QuotaExceededError: Unable to store more than 5242880 bytes in storage
|
|
@ -0,0 +1 @@
|
||||||
|
QuotaExceededError: Unable to store more than 5242880 bytes in storage
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script src="../../include.js"></script>
|
||||||
|
<script>
|
||||||
|
// FIXME: This should be an import of WPT from webstorage/storage_local_setitem_quotaexceedederr.window.html
|
||||||
|
test(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
var key = "name";
|
||||||
|
var val = "x".repeat(1024);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
index++;
|
||||||
|
localStorage.setItem("" + key + index, "" + val + index);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
println(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script src="../../include.js"></script>
|
||||||
|
<script>
|
||||||
|
// FIXME: This should be an import of WPT from webstorage/storage_session_setitem_quotaexceedederr.window.html
|
||||||
|
test(() => {
|
||||||
|
sessionStorage.clear();
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
var key = "name";
|
||||||
|
var val = "x".repeat(1024);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
index++;
|
||||||
|
sessionStorage.setItem("" + key + index, "" + val + index);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
println(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.clear();
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue