Serenity Operating System
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/DeprecatedString.h>
9#include <LibWeb/Bindings/Intrinsics.h>
10#include <LibWeb/HTML/Storage.h>
11
12namespace Web::HTML {
13
14WebIDL::ExceptionOr<JS::NonnullGCPtr<Storage>> Storage::create(JS::Realm& realm)
15{
16 return MUST_OR_THROW_OOM(realm.heap().allocate<Storage>(realm, realm));
17}
18
19Storage::Storage(JS::Realm& realm)
20 : Bindings::LegacyPlatformObject(realm)
21{
22}
23
24Storage::~Storage() = default;
25
26JS::ThrowCompletionOr<void> Storage::initialize(JS::Realm& realm)
27{
28 MUST_OR_THROW_OOM(Base::initialize(realm));
29 set_prototype(&Bindings::ensure_web_prototype<Bindings::StoragePrototype>(realm, "Storage"));
30
31 return {};
32}
33
34// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-length
35size_t Storage::length() const
36{
37 // The length getter steps are to return this's map's size.
38 return m_map.size();
39}
40
41// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key
42DeprecatedString Storage::key(size_t index)
43{
44 // 1. If index is greater than or equal to this's map's size, then return null.
45 if (index >= m_map.size())
46 return {};
47
48 // 2. Let keys be the result of running get the keys on this's map.
49 auto keys = m_map.keys();
50
51 // 3. Return keys[index].
52 return keys[index];
53}
54
55// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-getitem
56DeprecatedString Storage::get_item(DeprecatedString const& key) const
57{
58 // 1. If this's map[key] does not exist, then return null.
59 auto it = m_map.find(key);
60 if (it == m_map.end())
61 return {};
62
63 // 2. Return this's map[key].
64 return it->value;
65}
66
67// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-setitem
68WebIDL::ExceptionOr<void> Storage::set_item(DeprecatedString const& key, DeprecatedString const& value)
69{
70 // 1. Let oldValue be null.
71 DeprecatedString old_value;
72
73 // 2. Let reorder be true.
74 bool reorder = true;
75
76 // 3. If this's map[key] exists:
77 if (auto it = m_map.find(key); it != m_map.end()) {
78 // 1. Set oldValue to this's map[key].
79 old_value = it->value;
80
81 // 2. If oldValue is value, then return.
82 if (old_value == value)
83 return {};
84
85 // 3. Set reorder to false.
86 reorder = false;
87 }
88
89 // FIXME: 4. If value cannot be stored, then throw a "QuotaExceededError" DOMException exception.
90
91 // 5. Set this's map[key] to value.
92 m_map.set(key, value);
93
94 // 6. If reorder is true, then reorder this.
95 if (reorder)
96 this->reorder();
97
98 // 7. Broadcast this with key, oldValue, and value.
99 broadcast(key, old_value, value);
100
101 return {};
102}
103
104// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-removeitem
105void Storage::remove_item(DeprecatedString const& key)
106{
107 // 1. If this's map[key] does not exist, then return null.
108 // FIXME: Return null?
109 auto it = m_map.find(key);
110 if (it == m_map.end())
111 return;
112
113 // 2. Set oldValue to this's map[key].
114 auto old_value = it->value;
115
116 // 3. Remove this's map[key].
117 m_map.remove(it);
118
119 // 4. Reorder this.
120 reorder();
121
122 // 5. Broadcast this with key, oldValue, and null.
123 broadcast(key, old_value, {});
124}
125
126// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-clear
127void Storage::clear()
128{
129 // 1. Clear this's map.
130 m_map.clear();
131
132 // 2. Broadcast this with null, null, and null.
133 broadcast({}, {}, {});
134}
135
136// https://html.spec.whatwg.org/multipage/webstorage.html#concept-storage-reorder
137void Storage::reorder()
138{
139 // To reorder a Storage object storage, reorder storage's map's entries in an implementation-defined manner.
140 // NOTE: This basically means that we're not required to maintain any particular iteration order.
141}
142
143// https://html.spec.whatwg.org/multipage/webstorage.html#concept-storage-broadcast
144void Storage::broadcast(DeprecatedString const& key, DeprecatedString const& old_value, DeprecatedString const& new_value)
145{
146 (void)key;
147 (void)old_value;
148 (void)new_value;
149 // FIXME: Implement.
150}
151
152Vector<DeprecatedString> Storage::supported_property_names() const
153{
154 // The supported property names on a Storage object storage are the result of running get the keys on storage's map.
155 return m_map.keys();
156}
157
158WebIDL::ExceptionOr<JS::Value> Storage::named_item_value(DeprecatedFlyString const& name) const
159{
160 auto value = get_item(name);
161 if (value.is_null())
162 return JS::js_null();
163 return JS::PrimitiveString::create(vm(), value);
164}
165
166WebIDL::ExceptionOr<Bindings::LegacyPlatformObject::DidDeletionFail> Storage::delete_value(DeprecatedString const& name)
167{
168 remove_item(name);
169 return DidDeletionFail::NotRelevant;
170}
171
172WebIDL::ExceptionOr<void> Storage::set_value_of_named_property(DeprecatedString const& key, JS::Value unconverted_value)
173{
174 // NOTE: Since LegacyPlatformObject does not know the type of value, we must convert it ourselves.
175 // The type of `value` is `DOMString`.
176 auto value = TRY(unconverted_value.to_deprecated_string(vm()));
177 return set_item(key, value);
178}
179
180void Storage::dump() const
181{
182 dbgln("Storage ({} key(s))", m_map.size());
183 size_t i = 0;
184 for (auto const& it : m_map) {
185 dbgln("[{}] \"{}\": \"{}\"", i, it.key, it.value);
186 ++i;
187 }
188}
189
190}