/src/serenity/Userland/Libraries/LibWeb/HTML/Storage.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2023, Luke Wilde <lukew@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/String.h> |
9 | | #include <LibWeb/Bindings/Intrinsics.h> |
10 | | #include <LibWeb/Bindings/StoragePrototype.h> |
11 | | #include <LibWeb/HTML/Storage.h> |
12 | | |
13 | | namespace Web::HTML { |
14 | | |
15 | | JS_DEFINE_ALLOCATOR(Storage); |
16 | | |
17 | | JS::NonnullGCPtr<Storage> Storage::create(JS::Realm& realm) |
18 | 0 | { |
19 | 0 | return realm.heap().allocate<Storage>(realm, realm); |
20 | 0 | } |
21 | | |
22 | | Storage::Storage(JS::Realm& realm) |
23 | 0 | : Bindings::PlatformObject(realm) |
24 | 0 | { |
25 | 0 | m_legacy_platform_object_flags = LegacyPlatformObjectFlags { |
26 | 0 | .supports_indexed_properties = true, |
27 | 0 | .supports_named_properties = true, |
28 | 0 | .has_indexed_property_setter = true, |
29 | 0 | .has_named_property_setter = true, |
30 | 0 | .has_named_property_deleter = true, |
31 | 0 | .indexed_property_setter_has_identifier = true, |
32 | 0 | .named_property_setter_has_identifier = true, |
33 | 0 | .named_property_deleter_has_identifier = true, |
34 | 0 | }; |
35 | 0 | } |
36 | | |
37 | 0 | Storage::~Storage() = default; |
38 | | |
39 | | void Storage::initialize(JS::Realm& realm) |
40 | 0 | { |
41 | 0 | Base::initialize(realm); |
42 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(Storage); |
43 | 0 | } |
44 | | |
45 | | // https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-length |
46 | | size_t Storage::length() const |
47 | 0 | { |
48 | | // The length getter steps are to return this's map's size. |
49 | 0 | return m_map.size(); |
50 | 0 | } |
51 | | |
52 | | // https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key |
53 | | Optional<String> Storage::key(size_t index) |
54 | 0 | { |
55 | | // 1. If index is greater than or equal to this's map's size, then return null. |
56 | 0 | if (index >= m_map.size()) |
57 | 0 | return {}; |
58 | | |
59 | | // 2. Let keys be the result of running get the keys on this's map. |
60 | 0 | auto keys = m_map.keys(); |
61 | | |
62 | | // 3. Return keys[index]. |
63 | 0 | return keys[index]; |
64 | 0 | } |
65 | | |
66 | | // https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-getitem |
67 | | Optional<String> Storage::get_item(StringView key) const |
68 | 0 | { |
69 | | // 1. If this's map[key] does not exist, then return null. |
70 | 0 | auto it = m_map.find(key); |
71 | 0 | if (it == m_map.end()) |
72 | 0 | return {}; |
73 | | |
74 | | // 2. Return this's map[key]. |
75 | 0 | return it->value; |
76 | 0 | } |
77 | | |
78 | | // https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-setitem |
79 | | WebIDL::ExceptionOr<void> Storage::set_item(String const& key, String const& value) |
80 | 0 | { |
81 | | // 1. Let oldValue be null. |
82 | 0 | String old_value; |
83 | | |
84 | | // 2. Let reorder be true. |
85 | 0 | bool reorder = true; |
86 | | |
87 | | // 3. If this's map[key] exists: |
88 | 0 | if (auto it = m_map.find(key); it != m_map.end()) { |
89 | | // 1. Set oldValue to this's map[key]. |
90 | 0 | old_value = it->value; |
91 | | |
92 | | // 2. If oldValue is value, then return. |
93 | 0 | if (old_value == value) |
94 | 0 | return {}; |
95 | | |
96 | | // 3. Set reorder to false. |
97 | 0 | reorder = false; |
98 | 0 | } |
99 | | |
100 | | // FIXME: 4. If value cannot be stored, then throw a "QuotaExceededError" DOMException exception. |
101 | | |
102 | | // 5. Set this's map[key] to value. |
103 | 0 | m_map.set(key, value); |
104 | | |
105 | | // 6. If reorder is true, then reorder this. |
106 | 0 | if (reorder) |
107 | 0 | this->reorder(); |
108 | | |
109 | | // 7. Broadcast this with key, oldValue, and value. |
110 | 0 | broadcast(key, old_value, value); |
111 | |
|
112 | 0 | return {}; |
113 | 0 | } |
114 | | |
115 | | // https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-removeitem |
116 | | void Storage::remove_item(StringView key) |
117 | 0 | { |
118 | | // 1. If this's map[key] does not exist, then return null. |
119 | | // FIXME: Return null? |
120 | 0 | auto it = m_map.find(key); |
121 | 0 | if (it == m_map.end()) |
122 | 0 | return; |
123 | | |
124 | | // 2. Set oldValue to this's map[key]. |
125 | 0 | auto old_value = it->value; |
126 | | |
127 | | // 3. Remove this's map[key]. |
128 | 0 | m_map.remove(it); |
129 | | |
130 | | // 4. Reorder this. |
131 | 0 | reorder(); |
132 | | |
133 | | // 5. Broadcast this with key, oldValue, and null. |
134 | 0 | broadcast(key, old_value, {}); |
135 | 0 | } |
136 | | |
137 | | // https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-clear |
138 | | void Storage::clear() |
139 | 0 | { |
140 | | // 1. Clear this's map. |
141 | 0 | m_map.clear(); |
142 | | |
143 | | // 2. Broadcast this with null, null, and null. |
144 | 0 | broadcast({}, {}, {}); |
145 | 0 | } |
146 | | |
147 | | // https://html.spec.whatwg.org/multipage/webstorage.html#concept-storage-reorder |
148 | | void Storage::reorder() |
149 | 0 | { |
150 | | // To reorder a Storage object storage, reorder storage's map's entries in an implementation-defined manner. |
151 | | // NOTE: This basically means that we're not required to maintain any particular iteration order. |
152 | 0 | } |
153 | | |
154 | | // https://html.spec.whatwg.org/multipage/webstorage.html#concept-storage-broadcast |
155 | | void Storage::broadcast(StringView key, StringView old_value, StringView new_value) |
156 | 0 | { |
157 | 0 | (void)key; |
158 | 0 | (void)old_value; |
159 | 0 | (void)new_value; |
160 | | // FIXME: Implement. |
161 | 0 | } |
162 | | |
163 | | Vector<FlyString> Storage::supported_property_names() const |
164 | 0 | { |
165 | | // The supported property names on a Storage object storage are the result of running get the keys on storage's map. |
166 | 0 | Vector<FlyString> names; |
167 | 0 | names.ensure_capacity(m_map.size()); |
168 | 0 | for (auto const& key : m_map.keys()) |
169 | 0 | names.unchecked_append(key); |
170 | 0 | return names; |
171 | 0 | } |
172 | | |
173 | | Optional<JS::Value> Storage::item_value(size_t index) const |
174 | 0 | { |
175 | | // Handle index as a string since that's our key type |
176 | 0 | auto key = String::number(index); |
177 | 0 | auto value = get_item(key); |
178 | 0 | if (!value.has_value()) |
179 | 0 | return {}; |
180 | 0 | return JS::PrimitiveString::create(vm(), value.release_value()); |
181 | 0 | } |
182 | | |
183 | | JS::Value Storage::named_item_value(FlyString const& name) const |
184 | 0 | { |
185 | 0 | auto value = get_item(name); |
186 | 0 | if (!value.has_value()) |
187 | | // AD-HOC: Spec leaves open to a description at: https://html.spec.whatwg.org/multipage/webstorage.html#the-storage-interface |
188 | | // However correct behavior expected here: https://github.com/whatwg/html/issues/8684 |
189 | 0 | return JS::js_undefined(); |
190 | 0 | return JS::PrimitiveString::create(vm(), value.release_value()); |
191 | 0 | } |
192 | | |
193 | | WebIDL::ExceptionOr<Bindings::PlatformObject::DidDeletionFail> Storage::delete_value(String const& name) |
194 | 0 | { |
195 | 0 | remove_item(name); |
196 | 0 | return DidDeletionFail::NotRelevant; |
197 | 0 | } |
198 | | |
199 | | WebIDL::ExceptionOr<void> Storage::set_value_of_indexed_property(u32 index, JS::Value unconverted_value) |
200 | 0 | { |
201 | | // Handle index as a string since that's our key type |
202 | 0 | auto key = String::number(index); |
203 | 0 | return set_value_of_named_property(key, unconverted_value); |
204 | 0 | } |
205 | | |
206 | | WebIDL::ExceptionOr<void> Storage::set_value_of_named_property(String const& key, JS::Value unconverted_value) |
207 | 0 | { |
208 | | // NOTE: Since PlatformObject does not know the type of value, we must convert it ourselves. |
209 | | // The type of `value` is `DOMString`. |
210 | 0 | auto value = TRY(unconverted_value.to_string(vm())); |
211 | 0 | return set_item(key, value); |
212 | 0 | } |
213 | | |
214 | | void Storage::dump() const |
215 | 0 | { |
216 | 0 | dbgln("Storage ({} key(s))", m_map.size()); |
217 | 0 | size_t i = 0; |
218 | 0 | for (auto const& it : m_map) { |
219 | 0 | dbgln("[{}] \"{}\": \"{}\"", i, it.key, it.value); |
220 | 0 | ++i; |
221 | 0 | } |
222 | 0 | } |
223 | | |
224 | | } |