1use std::path::PathBuf;
6
7use deno_core::GarbageCollected;
8use deno_core::OpState;
9use deno_core::op2;
10pub use rusqlite;
11use rusqlite::Connection;
12use rusqlite::OptionalExtension;
13use rusqlite::params;
14
15#[derive(Debug, thiserror::Error, deno_error::JsError)]
16pub enum WebStorageError {
17 #[class("DOMExceptionNotSupportedError")]
18 #[error("LocalStorage is not supported in this context.")]
19 ContextNotSupported,
20 #[class(generic)]
21 #[error(transparent)]
22 Sqlite(#[from] rusqlite::Error),
23 #[class(inherit)]
24 #[error(transparent)]
25 Io(std::io::Error),
26 #[class("DOMExceptionQuotaExceededError")]
27 #[error("Exceeded maximum storage size")]
28 StorageExceeded,
29}
30
31#[derive(Clone)]
32struct OriginStorageDir(PathBuf);
33
34const MAX_STORAGE_BYTES: usize = 10 * 1024 * 1024;
35
36deno_core::extension!(deno_webstorage,
37 deps = [ deno_webidl ],
38 ops = [
39 op_webstorage_iterate_keys,
40 ],
41 objects = [
42 Storage
43 ],
44 esm = [ "01_webstorage.js" ],
45 options = {
46 origin_storage_dir: Option<PathBuf>
47 },
48 state = |state, options| {
49 if let Some(origin_storage_dir) = options.origin_storage_dir {
50 state.put(OriginStorageDir(origin_storage_dir));
51 }
52 },
53);
54
55struct LocalStorage(Connection);
56struct SessionStorage(Connection);
57
58fn get_webstorage(
59 state: &mut OpState,
60 persistent: bool,
61) -> Result<&Connection, WebStorageError> {
62 let conn = if persistent {
63 if state.try_borrow::<LocalStorage>().is_none() {
64 let path = state
65 .try_borrow::<OriginStorageDir>()
66 .ok_or(WebStorageError::ContextNotSupported)?;
67 std::fs::create_dir_all(&path.0).map_err(WebStorageError::Io)?;
68 let conn = Connection::open(path.0.join("local_storage"))?;
69 let initial_pragmas = "
71 -- enable write-ahead-logging mode
72 PRAGMA journal_mode=WAL;
73 PRAGMA synchronous=NORMAL;
74 PRAGMA temp_store=memory;
75 PRAGMA page_size=4096;
76 PRAGMA mmap_size=6000000;
77 PRAGMA optimize;
78 ";
79
80 conn.execute_batch(initial_pragmas)?;
81 conn.set_prepared_statement_cache_capacity(128);
82 {
83 let mut stmt = conn.prepare_cached(
84 "CREATE TABLE IF NOT EXISTS data (key VARCHAR UNIQUE, value VARCHAR)",
85 )?;
86 stmt.execute(params![])?;
87 }
88 state.put(LocalStorage(conn));
89 }
90
91 &state.borrow::<LocalStorage>().0
92 } else {
93 if state.try_borrow::<SessionStorage>().is_none() {
94 let conn = Connection::open_in_memory()?;
95 {
96 let mut stmt = conn.prepare_cached(
97 "CREATE TABLE data (key VARCHAR UNIQUE, value VARCHAR)",
98 )?;
99 stmt.execute(params![])?;
100 }
101 state.put(SessionStorage(conn));
102 }
103
104 &state.borrow::<SessionStorage>().0
105 };
106
107 Ok(conn)
108}
109
110#[inline]
111fn size_check(input: usize) -> Result<(), WebStorageError> {
112 if input >= MAX_STORAGE_BYTES {
113 return Err(WebStorageError::StorageExceeded);
114 }
115
116 Ok(())
117}
118
119struct Storage {
120 persistent: bool,
121}
122
123unsafe impl GarbageCollected for Storage {
125 fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {}
126
127 fn get_name(&self) -> &'static std::ffi::CStr {
128 c"Storage"
129 }
130}
131
132#[op2]
133impl Storage {
134 #[constructor]
135 #[cppgc]
136 fn new(persistent: bool) -> Storage {
137 Storage { persistent }
138 }
139
140 #[getter]
141 #[smi]
142 fn length(&self, state: &mut OpState) -> Result<u32, WebStorageError> {
143 let conn = get_webstorage(state, self.persistent)?;
144
145 let mut stmt = conn.prepare_cached("SELECT COUNT(*) FROM data")?;
146 let length: u32 = stmt.query_row(params![], |row| row.get(0))?;
147
148 Ok(length)
149 }
150
151 #[required(1)]
152 #[string]
153 fn key(
154 &self,
155 state: &mut OpState,
156 #[smi] index: u32,
157 ) -> Result<Option<String>, WebStorageError> {
158 let conn = get_webstorage(state, self.persistent)?;
159
160 let mut stmt =
161 conn.prepare_cached("SELECT key FROM data LIMIT 1 OFFSET ?")?;
162
163 let key: Option<String> = stmt
164 .query_row(params![index], |row| row.get(0))
165 .optional()?;
166
167 Ok(key)
168 }
169
170 #[fast]
171 #[required(2)]
172 fn set_item(
173 &self,
174 state: &mut OpState,
175 #[string] key: &str,
176 #[string] value: &str,
177 ) -> Result<(), WebStorageError> {
178 let conn = get_webstorage(state, self.persistent)?;
179
180 size_check(key.len() + value.len())?;
181
182 let mut stmt = conn
183 .prepare_cached("SELECT SUM(pgsize) FROM dbstat WHERE name = 'data'")?;
184 let size: u32 = stmt.query_row(params![], |row| row.get(0))?;
185
186 size_check(size as usize)?;
187
188 let mut stmt = conn.prepare_cached(
189 "INSERT OR REPLACE INTO data (key, value) VALUES (?, ?)",
190 )?;
191 stmt.execute(params![key, value])?;
192
193 Ok(())
194 }
195
196 #[required(1)]
197 #[string]
198 fn get_item(
199 &self,
200 state: &mut OpState,
201 #[string] key: &str,
202 ) -> Result<Option<String>, WebStorageError> {
203 let conn = get_webstorage(state, self.persistent)?;
204
205 let mut stmt =
206 conn.prepare_cached("SELECT value FROM data WHERE key = ?")?;
207 let val = stmt.query_row(params![key], |row| row.get(0)).optional()?;
208
209 Ok(val)
210 }
211
212 #[fast]
213 #[required(1)]
214 fn remove_item(
215 &self,
216 state: &mut OpState,
217 #[string] key: &str,
218 ) -> Result<(), WebStorageError> {
219 let conn = get_webstorage(state, self.persistent)?;
220
221 let mut stmt = conn.prepare_cached("DELETE FROM data WHERE key = ?")?;
222 stmt.execute(params![key])?;
223
224 Ok(())
225 }
226
227 #[fast]
228 fn clear(&self, state: &mut OpState) -> Result<(), WebStorageError> {
229 let conn = get_webstorage(state, self.persistent)?;
230
231 let mut stmt = conn.prepare_cached("DELETE FROM data")?;
232 stmt.execute(params![])?;
233
234 Ok(())
235 }
236}
237
238#[op2]
239#[serde]
240fn op_webstorage_iterate_keys(
241 #[cppgc] storage: &Storage,
242 state: &mut OpState,
243) -> Result<Vec<String>, WebStorageError> {
244 let conn = get_webstorage(state, storage.persistent)?;
245
246 let mut stmt = conn.prepare_cached("SELECT key FROM data")?;
247 let keys = stmt
248 .query_map(params![], |row| row.get::<_, String>(0))?
249 .map(|r| r.unwrap())
250 .collect();
251
252 Ok(keys)
253}