├── .github
├── assets
│ └── tinyweb-youtube.jpg
├── notes
│ └── runtime.md
└── workflows
│ └── tests.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── examples
├── callbacks
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── Makefile
│ ├── public
│ │ └── index.html
│ └── src
│ │ └── lib.rs
├── dom
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── Makefile
│ ├── public
│ │ └── index.html
│ └── src
│ │ └── lib.rs
├── features
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── Makefile
│ ├── public
│ │ └── index.html
│ └── src
│ │ ├── keycodes.rs
│ │ └── lib.rs
└── minimal
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── src
├── js
├── main.js
└── main.test.js
└── rust
├── Cargo.toml
├── src
├── allocations.rs
├── callbacks.rs
├── element.rs
├── invoke.rs
├── lib.rs
├── router.rs
├── runtime.rs
└── signals.rs
└── tests
└── mod.rs
/.github/assets/tinyweb-youtube.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LiveDuo/tinyweb/45266dc878734e7d94e74d53472b994165828875/.github/assets/tinyweb-youtube.jpg
--------------------------------------------------------------------------------
/.github/notes/runtime.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Runtime flow
4 |
5 | ```js
6 | // 1. Register a callback and invoke `fetch` that triggers the callback when is finishes
7 | [Log] create_async_callback future_id=0 -> [Log] create_callback id=0 && [Log] js_invoke `fetch` id=0
8 |
9 | // 2. Use the `block_on` method and `await` the future inside
10 | // When the future is awaited it calls `poll` function that sets `FutureState` to `Pending(waker)`
11 | [Log] runtime block on -> [Log] future poll -> [Log] poll future pending
12 |
13 | // 3. When the `fetch` callback is triggered, schedule a `setTimeout(0)` callback that calls future poll
14 | [Log] handle_callback id=0 -> [Log] waker wake -> [Log] create_callback id=2 && [Log] js_invoke `setTimeout(0)` id=0
15 |
16 | // 4. When the `setTimeout(0)` callback is triggered, resolve future to `Poll::Ready(T)`
17 | [Log] handle_callback id=0 -> [Log] future poll -> [Log] poll future completed
18 | ```
19 |
20 |
21 | ### Implementation quirks
22 |
23 | 1. Updating `FutureState` in 2 different places
24 | - Notes: It's updated in `create_async_callback` and in the `Future` trait impl
25 | - Explanation: `create_async_callback` has the `result` value and `Future` has access to the concrete `self` type
26 |
27 | 2. Calling `poll` in `wake_fn` through a Javascript callback instead of directly calling
28 | - Notes: The `wake_fn` function schedules a callback with `setTimeout(0)` that does `Runtime::poll(&future)`
29 | - Explanation: `Runtime::poll` has a mutable borrow that still holds when `wake_fn` tries to borrow again
30 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Tests
3 |
4 | on:
5 | push:
6 | branches: [ "master" ]
7 |
8 | env:
9 | RUST_BACKTRACE: short
10 | SKIP_GUEST_BUILD: 1
11 | CARGO_INCREMENTAL: 0
12 | CARGO_NET_RETRY: 10
13 | CI: 1
14 |
15 | jobs:
16 | build:
17 | runs-on: ubuntu-latest
18 | timeout-minutes: 10
19 | steps:
20 |
21 | # setup geckodriver
22 | - uses: browser-actions/setup-geckodriver@latest
23 |
24 | # setup git
25 | - uses: actions/checkout@v3
26 | - uses: Swatinem/rust-cache@v2
27 |
28 | # setup rust
29 | - run: export PATH=~/.cargo/bin:/usr/local/bin/:$PATH
30 | - run: rustup target add wasm32-unknown-unknown
31 |
32 | # run rust tests
33 | - run: cargo test
34 |
35 | # run js tests
36 | - run: node src/js/main.test.js
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | target
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.24.2"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler2"
16 | version = "2.0.0"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
19 |
20 | [[package]]
21 | name = "autocfg"
22 | version = "1.4.0"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
25 |
26 | [[package]]
27 | name = "backtrace"
28 | version = "0.3.74"
29 | source = "registry+https://github.com/rust-lang/crates.io-index"
30 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
31 | dependencies = [
32 | "addr2line",
33 | "cfg-if",
34 | "libc",
35 | "miniz_oxide",
36 | "object",
37 | "rustc-demangle",
38 | "windows-targets",
39 | ]
40 |
41 | [[package]]
42 | name = "base64"
43 | version = "0.21.7"
44 | source = "registry+https://github.com/rust-lang/crates.io-index"
45 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
46 |
47 | [[package]]
48 | name = "base64"
49 | version = "0.22.1"
50 | source = "registry+https://github.com/rust-lang/crates.io-index"
51 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
52 |
53 | [[package]]
54 | name = "bitflags"
55 | version = "2.6.0"
56 | source = "registry+https://github.com/rust-lang/crates.io-index"
57 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
58 |
59 | [[package]]
60 | name = "bytes"
61 | version = "1.8.0"
62 | source = "registry+https://github.com/rust-lang/crates.io-index"
63 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
64 |
65 | [[package]]
66 | name = "callbacks"
67 | version = "0.1.0"
68 | dependencies = [
69 | "tinyweb",
70 | ]
71 |
72 | [[package]]
73 | name = "cc"
74 | version = "1.1.37"
75 | source = "registry+https://github.com/rust-lang/crates.io-index"
76 | checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf"
77 | dependencies = [
78 | "shlex",
79 | ]
80 |
81 | [[package]]
82 | name = "cfg-if"
83 | version = "1.0.0"
84 | source = "registry+https://github.com/rust-lang/crates.io-index"
85 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
86 |
87 | [[package]]
88 | name = "cookie"
89 | version = "0.16.2"
90 | source = "registry+https://github.com/rust-lang/crates.io-index"
91 | checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
92 | dependencies = [
93 | "time",
94 | "version_check",
95 | ]
96 |
97 | [[package]]
98 | name = "cookie"
99 | version = "0.18.1"
100 | source = "registry+https://github.com/rust-lang/crates.io-index"
101 | checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
102 | dependencies = [
103 | "percent-encoding",
104 | "time",
105 | "version_check",
106 | ]
107 |
108 | [[package]]
109 | name = "core-foundation"
110 | version = "0.9.4"
111 | source = "registry+https://github.com/rust-lang/crates.io-index"
112 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
113 | dependencies = [
114 | "core-foundation-sys",
115 | "libc",
116 | ]
117 |
118 | [[package]]
119 | name = "core-foundation-sys"
120 | version = "0.8.7"
121 | source = "registry+https://github.com/rust-lang/crates.io-index"
122 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
123 |
124 | [[package]]
125 | name = "deranged"
126 | version = "0.3.11"
127 | source = "registry+https://github.com/rust-lang/crates.io-index"
128 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
129 | dependencies = [
130 | "powerfmt",
131 | ]
132 |
133 | [[package]]
134 | name = "displaydoc"
135 | version = "0.2.5"
136 | source = "registry+https://github.com/rust-lang/crates.io-index"
137 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
138 | dependencies = [
139 | "proc-macro2",
140 | "quote",
141 | "syn",
142 | ]
143 |
144 | [[package]]
145 | name = "dom"
146 | version = "0.1.0"
147 | dependencies = [
148 | "tinyweb",
149 | ]
150 |
151 | [[package]]
152 | name = "errno"
153 | version = "0.3.9"
154 | source = "registry+https://github.com/rust-lang/crates.io-index"
155 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
156 | dependencies = [
157 | "libc",
158 | "windows-sys 0.52.0",
159 | ]
160 |
161 | [[package]]
162 | name = "fantoccini"
163 | version = "0.21.2"
164 | source = "registry+https://github.com/rust-lang/crates.io-index"
165 | checksum = "dd52b63e98251013cd5a9e881b9d460fc530e5df4eec58930c9694d6497c53e5"
166 | dependencies = [
167 | "base64 0.22.1",
168 | "cookie 0.18.1",
169 | "futures-core",
170 | "futures-util",
171 | "http 1.1.0",
172 | "http-body-util",
173 | "hyper",
174 | "hyper-tls",
175 | "hyper-util",
176 | "mime",
177 | "openssl",
178 | "serde",
179 | "serde_json",
180 | "time",
181 | "tokio",
182 | "url",
183 | "webdriver",
184 | ]
185 |
186 | [[package]]
187 | name = "fastrand"
188 | version = "2.2.0"
189 | source = "registry+https://github.com/rust-lang/crates.io-index"
190 | checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
191 |
192 | [[package]]
193 | name = "features"
194 | version = "0.1.0"
195 | dependencies = [
196 | "json",
197 | "tinyweb",
198 | ]
199 |
200 | [[package]]
201 | name = "fnv"
202 | version = "1.0.7"
203 | source = "registry+https://github.com/rust-lang/crates.io-index"
204 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
205 |
206 | [[package]]
207 | name = "foreign-types"
208 | version = "0.3.2"
209 | source = "registry+https://github.com/rust-lang/crates.io-index"
210 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
211 | dependencies = [
212 | "foreign-types-shared",
213 | ]
214 |
215 | [[package]]
216 | name = "foreign-types-shared"
217 | version = "0.1.1"
218 | source = "registry+https://github.com/rust-lang/crates.io-index"
219 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
220 |
221 | [[package]]
222 | name = "form_urlencoded"
223 | version = "1.2.1"
224 | source = "registry+https://github.com/rust-lang/crates.io-index"
225 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
226 | dependencies = [
227 | "percent-encoding",
228 | ]
229 |
230 | [[package]]
231 | name = "futures-channel"
232 | version = "0.3.31"
233 | source = "registry+https://github.com/rust-lang/crates.io-index"
234 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
235 | dependencies = [
236 | "futures-core",
237 | ]
238 |
239 | [[package]]
240 | name = "futures-core"
241 | version = "0.3.31"
242 | source = "registry+https://github.com/rust-lang/crates.io-index"
243 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
244 |
245 | [[package]]
246 | name = "futures-macro"
247 | version = "0.3.31"
248 | source = "registry+https://github.com/rust-lang/crates.io-index"
249 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
250 | dependencies = [
251 | "proc-macro2",
252 | "quote",
253 | "syn",
254 | ]
255 |
256 | [[package]]
257 | name = "futures-task"
258 | version = "0.3.31"
259 | source = "registry+https://github.com/rust-lang/crates.io-index"
260 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
261 |
262 | [[package]]
263 | name = "futures-util"
264 | version = "0.3.31"
265 | source = "registry+https://github.com/rust-lang/crates.io-index"
266 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
267 | dependencies = [
268 | "futures-core",
269 | "futures-macro",
270 | "futures-task",
271 | "pin-project-lite",
272 | "pin-utils",
273 | "slab",
274 | ]
275 |
276 | [[package]]
277 | name = "gimli"
278 | version = "0.31.1"
279 | source = "registry+https://github.com/rust-lang/crates.io-index"
280 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
281 |
282 | [[package]]
283 | name = "hermit-abi"
284 | version = "0.3.9"
285 | source = "registry+https://github.com/rust-lang/crates.io-index"
286 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
287 |
288 | [[package]]
289 | name = "http"
290 | version = "0.2.12"
291 | source = "registry+https://github.com/rust-lang/crates.io-index"
292 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
293 | dependencies = [
294 | "bytes",
295 | "fnv",
296 | "itoa",
297 | ]
298 |
299 | [[package]]
300 | name = "http"
301 | version = "1.1.0"
302 | source = "registry+https://github.com/rust-lang/crates.io-index"
303 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
304 | dependencies = [
305 | "bytes",
306 | "fnv",
307 | "itoa",
308 | ]
309 |
310 | [[package]]
311 | name = "http-body"
312 | version = "1.0.1"
313 | source = "registry+https://github.com/rust-lang/crates.io-index"
314 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
315 | dependencies = [
316 | "bytes",
317 | "http 1.1.0",
318 | ]
319 |
320 | [[package]]
321 | name = "http-body-util"
322 | version = "0.1.2"
323 | source = "registry+https://github.com/rust-lang/crates.io-index"
324 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
325 | dependencies = [
326 | "bytes",
327 | "futures-util",
328 | "http 1.1.0",
329 | "http-body",
330 | "pin-project-lite",
331 | ]
332 |
333 | [[package]]
334 | name = "httparse"
335 | version = "1.9.5"
336 | source = "registry+https://github.com/rust-lang/crates.io-index"
337 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
338 |
339 | [[package]]
340 | name = "hyper"
341 | version = "1.5.0"
342 | source = "registry+https://github.com/rust-lang/crates.io-index"
343 | checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
344 | dependencies = [
345 | "bytes",
346 | "futures-channel",
347 | "futures-util",
348 | "http 1.1.0",
349 | "http-body",
350 | "httparse",
351 | "itoa",
352 | "pin-project-lite",
353 | "smallvec",
354 | "tokio",
355 | "want",
356 | ]
357 |
358 | [[package]]
359 | name = "hyper-tls"
360 | version = "0.6.0"
361 | source = "registry+https://github.com/rust-lang/crates.io-index"
362 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
363 | dependencies = [
364 | "bytes",
365 | "http-body-util",
366 | "hyper",
367 | "hyper-util",
368 | "native-tls",
369 | "tokio",
370 | "tokio-native-tls",
371 | "tower-service",
372 | ]
373 |
374 | [[package]]
375 | name = "hyper-util"
376 | version = "0.1.10"
377 | source = "registry+https://github.com/rust-lang/crates.io-index"
378 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
379 | dependencies = [
380 | "bytes",
381 | "futures-channel",
382 | "futures-util",
383 | "http 1.1.0",
384 | "http-body",
385 | "hyper",
386 | "pin-project-lite",
387 | "socket2",
388 | "tokio",
389 | "tower-service",
390 | "tracing",
391 | ]
392 |
393 | [[package]]
394 | name = "icu_collections"
395 | version = "1.5.0"
396 | source = "registry+https://github.com/rust-lang/crates.io-index"
397 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
398 | dependencies = [
399 | "displaydoc",
400 | "yoke",
401 | "zerofrom",
402 | "zerovec",
403 | ]
404 |
405 | [[package]]
406 | name = "icu_locid"
407 | version = "1.5.0"
408 | source = "registry+https://github.com/rust-lang/crates.io-index"
409 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
410 | dependencies = [
411 | "displaydoc",
412 | "litemap",
413 | "tinystr",
414 | "writeable",
415 | "zerovec",
416 | ]
417 |
418 | [[package]]
419 | name = "icu_locid_transform"
420 | version = "1.5.0"
421 | source = "registry+https://github.com/rust-lang/crates.io-index"
422 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
423 | dependencies = [
424 | "displaydoc",
425 | "icu_locid",
426 | "icu_locid_transform_data",
427 | "icu_provider",
428 | "tinystr",
429 | "zerovec",
430 | ]
431 |
432 | [[package]]
433 | name = "icu_locid_transform_data"
434 | version = "1.5.0"
435 | source = "registry+https://github.com/rust-lang/crates.io-index"
436 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
437 |
438 | [[package]]
439 | name = "icu_normalizer"
440 | version = "1.5.0"
441 | source = "registry+https://github.com/rust-lang/crates.io-index"
442 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
443 | dependencies = [
444 | "displaydoc",
445 | "icu_collections",
446 | "icu_normalizer_data",
447 | "icu_properties",
448 | "icu_provider",
449 | "smallvec",
450 | "utf16_iter",
451 | "utf8_iter",
452 | "write16",
453 | "zerovec",
454 | ]
455 |
456 | [[package]]
457 | name = "icu_normalizer_data"
458 | version = "1.5.0"
459 | source = "registry+https://github.com/rust-lang/crates.io-index"
460 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
461 |
462 | [[package]]
463 | name = "icu_properties"
464 | version = "1.5.1"
465 | source = "registry+https://github.com/rust-lang/crates.io-index"
466 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
467 | dependencies = [
468 | "displaydoc",
469 | "icu_collections",
470 | "icu_locid_transform",
471 | "icu_properties_data",
472 | "icu_provider",
473 | "tinystr",
474 | "zerovec",
475 | ]
476 |
477 | [[package]]
478 | name = "icu_properties_data"
479 | version = "1.5.0"
480 | source = "registry+https://github.com/rust-lang/crates.io-index"
481 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
482 |
483 | [[package]]
484 | name = "icu_provider"
485 | version = "1.5.0"
486 | source = "registry+https://github.com/rust-lang/crates.io-index"
487 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
488 | dependencies = [
489 | "displaydoc",
490 | "icu_locid",
491 | "icu_provider_macros",
492 | "stable_deref_trait",
493 | "tinystr",
494 | "writeable",
495 | "yoke",
496 | "zerofrom",
497 | "zerovec",
498 | ]
499 |
500 | [[package]]
501 | name = "icu_provider_macros"
502 | version = "1.5.0"
503 | source = "registry+https://github.com/rust-lang/crates.io-index"
504 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
505 | dependencies = [
506 | "proc-macro2",
507 | "quote",
508 | "syn",
509 | ]
510 |
511 | [[package]]
512 | name = "idna"
513 | version = "1.0.3"
514 | source = "registry+https://github.com/rust-lang/crates.io-index"
515 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
516 | dependencies = [
517 | "idna_adapter",
518 | "smallvec",
519 | "utf8_iter",
520 | ]
521 |
522 | [[package]]
523 | name = "idna_adapter"
524 | version = "1.2.0"
525 | source = "registry+https://github.com/rust-lang/crates.io-index"
526 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
527 | dependencies = [
528 | "icu_normalizer",
529 | "icu_properties",
530 | ]
531 |
532 | [[package]]
533 | name = "itoa"
534 | version = "1.0.11"
535 | source = "registry+https://github.com/rust-lang/crates.io-index"
536 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
537 |
538 | [[package]]
539 | name = "json"
540 | version = "0.12.4"
541 | source = "registry+https://github.com/rust-lang/crates.io-index"
542 | checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
543 |
544 | [[package]]
545 | name = "libc"
546 | version = "0.2.162"
547 | source = "registry+https://github.com/rust-lang/crates.io-index"
548 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
549 |
550 | [[package]]
551 | name = "linux-raw-sys"
552 | version = "0.4.14"
553 | source = "registry+https://github.com/rust-lang/crates.io-index"
554 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
555 |
556 | [[package]]
557 | name = "litemap"
558 | version = "0.7.3"
559 | source = "registry+https://github.com/rust-lang/crates.io-index"
560 | checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
561 |
562 | [[package]]
563 | name = "lock_api"
564 | version = "0.4.12"
565 | source = "registry+https://github.com/rust-lang/crates.io-index"
566 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
567 | dependencies = [
568 | "autocfg",
569 | "scopeguard",
570 | ]
571 |
572 | [[package]]
573 | name = "log"
574 | version = "0.4.22"
575 | source = "registry+https://github.com/rust-lang/crates.io-index"
576 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
577 |
578 | [[package]]
579 | name = "memchr"
580 | version = "2.7.4"
581 | source = "registry+https://github.com/rust-lang/crates.io-index"
582 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
583 |
584 | [[package]]
585 | name = "mime"
586 | version = "0.3.17"
587 | source = "registry+https://github.com/rust-lang/crates.io-index"
588 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
589 |
590 | [[package]]
591 | name = "minimal"
592 | version = "0.1.0"
593 | dependencies = [
594 | "tinyweb",
595 | ]
596 |
597 | [[package]]
598 | name = "miniz_oxide"
599 | version = "0.8.0"
600 | source = "registry+https://github.com/rust-lang/crates.io-index"
601 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
602 | dependencies = [
603 | "adler2",
604 | ]
605 |
606 | [[package]]
607 | name = "mio"
608 | version = "1.0.2"
609 | source = "registry+https://github.com/rust-lang/crates.io-index"
610 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
611 | dependencies = [
612 | "hermit-abi",
613 | "libc",
614 | "wasi",
615 | "windows-sys 0.52.0",
616 | ]
617 |
618 | [[package]]
619 | name = "native-tls"
620 | version = "0.2.12"
621 | source = "registry+https://github.com/rust-lang/crates.io-index"
622 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
623 | dependencies = [
624 | "libc",
625 | "log",
626 | "openssl",
627 | "openssl-probe",
628 | "openssl-sys",
629 | "schannel",
630 | "security-framework",
631 | "security-framework-sys",
632 | "tempfile",
633 | ]
634 |
635 | [[package]]
636 | name = "num-conv"
637 | version = "0.1.0"
638 | source = "registry+https://github.com/rust-lang/crates.io-index"
639 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
640 |
641 | [[package]]
642 | name = "object"
643 | version = "0.36.5"
644 | source = "registry+https://github.com/rust-lang/crates.io-index"
645 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
646 | dependencies = [
647 | "memchr",
648 | ]
649 |
650 | [[package]]
651 | name = "once_cell"
652 | version = "1.20.2"
653 | source = "registry+https://github.com/rust-lang/crates.io-index"
654 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
655 |
656 | [[package]]
657 | name = "openssl"
658 | version = "0.10.68"
659 | source = "registry+https://github.com/rust-lang/crates.io-index"
660 | checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
661 | dependencies = [
662 | "bitflags",
663 | "cfg-if",
664 | "foreign-types",
665 | "libc",
666 | "once_cell",
667 | "openssl-macros",
668 | "openssl-sys",
669 | ]
670 |
671 | [[package]]
672 | name = "openssl-macros"
673 | version = "0.1.1"
674 | source = "registry+https://github.com/rust-lang/crates.io-index"
675 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
676 | dependencies = [
677 | "proc-macro2",
678 | "quote",
679 | "syn",
680 | ]
681 |
682 | [[package]]
683 | name = "openssl-probe"
684 | version = "0.1.5"
685 | source = "registry+https://github.com/rust-lang/crates.io-index"
686 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
687 |
688 | [[package]]
689 | name = "openssl-sys"
690 | version = "0.9.104"
691 | source = "registry+https://github.com/rust-lang/crates.io-index"
692 | checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
693 | dependencies = [
694 | "cc",
695 | "libc",
696 | "pkg-config",
697 | "vcpkg",
698 | ]
699 |
700 | [[package]]
701 | name = "parking_lot"
702 | version = "0.12.3"
703 | source = "registry+https://github.com/rust-lang/crates.io-index"
704 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
705 | dependencies = [
706 | "lock_api",
707 | "parking_lot_core",
708 | ]
709 |
710 | [[package]]
711 | name = "parking_lot_core"
712 | version = "0.9.10"
713 | source = "registry+https://github.com/rust-lang/crates.io-index"
714 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
715 | dependencies = [
716 | "cfg-if",
717 | "libc",
718 | "redox_syscall",
719 | "smallvec",
720 | "windows-targets",
721 | ]
722 |
723 | [[package]]
724 | name = "percent-encoding"
725 | version = "2.3.1"
726 | source = "registry+https://github.com/rust-lang/crates.io-index"
727 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
728 |
729 | [[package]]
730 | name = "pin-project-lite"
731 | version = "0.2.15"
732 | source = "registry+https://github.com/rust-lang/crates.io-index"
733 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
734 |
735 | [[package]]
736 | name = "pin-utils"
737 | version = "0.1.0"
738 | source = "registry+https://github.com/rust-lang/crates.io-index"
739 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
740 |
741 | [[package]]
742 | name = "pkg-config"
743 | version = "0.3.31"
744 | source = "registry+https://github.com/rust-lang/crates.io-index"
745 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
746 |
747 | [[package]]
748 | name = "powerfmt"
749 | version = "0.2.0"
750 | source = "registry+https://github.com/rust-lang/crates.io-index"
751 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
752 |
753 | [[package]]
754 | name = "proc-macro2"
755 | version = "1.0.89"
756 | source = "registry+https://github.com/rust-lang/crates.io-index"
757 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
758 | dependencies = [
759 | "unicode-ident",
760 | ]
761 |
762 | [[package]]
763 | name = "quote"
764 | version = "1.0.37"
765 | source = "registry+https://github.com/rust-lang/crates.io-index"
766 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
767 | dependencies = [
768 | "proc-macro2",
769 | ]
770 |
771 | [[package]]
772 | name = "redox_syscall"
773 | version = "0.5.7"
774 | source = "registry+https://github.com/rust-lang/crates.io-index"
775 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
776 | dependencies = [
777 | "bitflags",
778 | ]
779 |
780 | [[package]]
781 | name = "rustc-demangle"
782 | version = "0.1.24"
783 | source = "registry+https://github.com/rust-lang/crates.io-index"
784 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
785 |
786 | [[package]]
787 | name = "rustix"
788 | version = "0.38.39"
789 | source = "registry+https://github.com/rust-lang/crates.io-index"
790 | checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee"
791 | dependencies = [
792 | "bitflags",
793 | "errno",
794 | "libc",
795 | "linux-raw-sys",
796 | "windows-sys 0.52.0",
797 | ]
798 |
799 | [[package]]
800 | name = "ryu"
801 | version = "1.0.18"
802 | source = "registry+https://github.com/rust-lang/crates.io-index"
803 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
804 |
805 | [[package]]
806 | name = "schannel"
807 | version = "0.1.26"
808 | source = "registry+https://github.com/rust-lang/crates.io-index"
809 | checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1"
810 | dependencies = [
811 | "windows-sys 0.59.0",
812 | ]
813 |
814 | [[package]]
815 | name = "scopeguard"
816 | version = "1.2.0"
817 | source = "registry+https://github.com/rust-lang/crates.io-index"
818 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
819 |
820 | [[package]]
821 | name = "security-framework"
822 | version = "2.11.1"
823 | source = "registry+https://github.com/rust-lang/crates.io-index"
824 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
825 | dependencies = [
826 | "bitflags",
827 | "core-foundation",
828 | "core-foundation-sys",
829 | "libc",
830 | "security-framework-sys",
831 | ]
832 |
833 | [[package]]
834 | name = "security-framework-sys"
835 | version = "2.12.1"
836 | source = "registry+https://github.com/rust-lang/crates.io-index"
837 | checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
838 | dependencies = [
839 | "core-foundation-sys",
840 | "libc",
841 | ]
842 |
843 | [[package]]
844 | name = "serde"
845 | version = "1.0.214"
846 | source = "registry+https://github.com/rust-lang/crates.io-index"
847 | checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
848 | dependencies = [
849 | "serde_derive",
850 | ]
851 |
852 | [[package]]
853 | name = "serde_derive"
854 | version = "1.0.214"
855 | source = "registry+https://github.com/rust-lang/crates.io-index"
856 | checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
857 | dependencies = [
858 | "proc-macro2",
859 | "quote",
860 | "syn",
861 | ]
862 |
863 | [[package]]
864 | name = "serde_json"
865 | version = "1.0.132"
866 | source = "registry+https://github.com/rust-lang/crates.io-index"
867 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
868 | dependencies = [
869 | "itoa",
870 | "memchr",
871 | "ryu",
872 | "serde",
873 | ]
874 |
875 | [[package]]
876 | name = "shlex"
877 | version = "1.3.0"
878 | source = "registry+https://github.com/rust-lang/crates.io-index"
879 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
880 |
881 | [[package]]
882 | name = "signal-hook-registry"
883 | version = "1.4.2"
884 | source = "registry+https://github.com/rust-lang/crates.io-index"
885 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
886 | dependencies = [
887 | "libc",
888 | ]
889 |
890 | [[package]]
891 | name = "slab"
892 | version = "0.4.9"
893 | source = "registry+https://github.com/rust-lang/crates.io-index"
894 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
895 | dependencies = [
896 | "autocfg",
897 | ]
898 |
899 | [[package]]
900 | name = "smallvec"
901 | version = "1.13.2"
902 | source = "registry+https://github.com/rust-lang/crates.io-index"
903 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
904 |
905 | [[package]]
906 | name = "socket2"
907 | version = "0.5.7"
908 | source = "registry+https://github.com/rust-lang/crates.io-index"
909 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
910 | dependencies = [
911 | "libc",
912 | "windows-sys 0.52.0",
913 | ]
914 |
915 | [[package]]
916 | name = "stable_deref_trait"
917 | version = "1.2.0"
918 | source = "registry+https://github.com/rust-lang/crates.io-index"
919 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
920 |
921 | [[package]]
922 | name = "syn"
923 | version = "2.0.87"
924 | source = "registry+https://github.com/rust-lang/crates.io-index"
925 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
926 | dependencies = [
927 | "proc-macro2",
928 | "quote",
929 | "unicode-ident",
930 | ]
931 |
932 | [[package]]
933 | name = "synstructure"
934 | version = "0.13.1"
935 | source = "registry+https://github.com/rust-lang/crates.io-index"
936 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
937 | dependencies = [
938 | "proc-macro2",
939 | "quote",
940 | "syn",
941 | ]
942 |
943 | [[package]]
944 | name = "tempfile"
945 | version = "3.14.0"
946 | source = "registry+https://github.com/rust-lang/crates.io-index"
947 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
948 | dependencies = [
949 | "cfg-if",
950 | "fastrand",
951 | "once_cell",
952 | "rustix",
953 | "windows-sys 0.59.0",
954 | ]
955 |
956 | [[package]]
957 | name = "thiserror"
958 | version = "1.0.69"
959 | source = "registry+https://github.com/rust-lang/crates.io-index"
960 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
961 | dependencies = [
962 | "thiserror-impl",
963 | ]
964 |
965 | [[package]]
966 | name = "thiserror-impl"
967 | version = "1.0.69"
968 | source = "registry+https://github.com/rust-lang/crates.io-index"
969 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
970 | dependencies = [
971 | "proc-macro2",
972 | "quote",
973 | "syn",
974 | ]
975 |
976 | [[package]]
977 | name = "time"
978 | version = "0.3.36"
979 | source = "registry+https://github.com/rust-lang/crates.io-index"
980 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
981 | dependencies = [
982 | "deranged",
983 | "itoa",
984 | "num-conv",
985 | "powerfmt",
986 | "serde",
987 | "time-core",
988 | "time-macros",
989 | ]
990 |
991 | [[package]]
992 | name = "time-core"
993 | version = "0.1.2"
994 | source = "registry+https://github.com/rust-lang/crates.io-index"
995 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
996 |
997 | [[package]]
998 | name = "time-macros"
999 | version = "0.2.18"
1000 | source = "registry+https://github.com/rust-lang/crates.io-index"
1001 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
1002 | dependencies = [
1003 | "num-conv",
1004 | "time-core",
1005 | ]
1006 |
1007 | [[package]]
1008 | name = "tinystr"
1009 | version = "0.7.6"
1010 | source = "registry+https://github.com/rust-lang/crates.io-index"
1011 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
1012 | dependencies = [
1013 | "displaydoc",
1014 | "zerovec",
1015 | ]
1016 |
1017 | [[package]]
1018 | name = "tinyweb"
1019 | version = "0.1.0"
1020 | dependencies = [
1021 | "fantoccini",
1022 | "serde_json",
1023 | "tokio",
1024 | ]
1025 |
1026 | [[package]]
1027 | name = "tokio"
1028 | version = "1.41.1"
1029 | source = "registry+https://github.com/rust-lang/crates.io-index"
1030 | checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
1031 | dependencies = [
1032 | "backtrace",
1033 | "bytes",
1034 | "libc",
1035 | "mio",
1036 | "parking_lot",
1037 | "pin-project-lite",
1038 | "signal-hook-registry",
1039 | "socket2",
1040 | "tokio-macros",
1041 | "windows-sys 0.52.0",
1042 | ]
1043 |
1044 | [[package]]
1045 | name = "tokio-macros"
1046 | version = "2.4.0"
1047 | source = "registry+https://github.com/rust-lang/crates.io-index"
1048 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
1049 | dependencies = [
1050 | "proc-macro2",
1051 | "quote",
1052 | "syn",
1053 | ]
1054 |
1055 | [[package]]
1056 | name = "tokio-native-tls"
1057 | version = "0.3.1"
1058 | source = "registry+https://github.com/rust-lang/crates.io-index"
1059 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
1060 | dependencies = [
1061 | "native-tls",
1062 | "tokio",
1063 | ]
1064 |
1065 | [[package]]
1066 | name = "tower-service"
1067 | version = "0.3.3"
1068 | source = "registry+https://github.com/rust-lang/crates.io-index"
1069 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
1070 |
1071 | [[package]]
1072 | name = "tracing"
1073 | version = "0.1.40"
1074 | source = "registry+https://github.com/rust-lang/crates.io-index"
1075 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
1076 | dependencies = [
1077 | "pin-project-lite",
1078 | "tracing-core",
1079 | ]
1080 |
1081 | [[package]]
1082 | name = "tracing-core"
1083 | version = "0.1.32"
1084 | source = "registry+https://github.com/rust-lang/crates.io-index"
1085 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
1086 | dependencies = [
1087 | "once_cell",
1088 | ]
1089 |
1090 | [[package]]
1091 | name = "try-lock"
1092 | version = "0.2.5"
1093 | source = "registry+https://github.com/rust-lang/crates.io-index"
1094 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
1095 |
1096 | [[package]]
1097 | name = "unicode-ident"
1098 | version = "1.0.13"
1099 | source = "registry+https://github.com/rust-lang/crates.io-index"
1100 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
1101 |
1102 | [[package]]
1103 | name = "unicode-segmentation"
1104 | version = "1.12.0"
1105 | source = "registry+https://github.com/rust-lang/crates.io-index"
1106 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
1107 |
1108 | [[package]]
1109 | name = "url"
1110 | version = "2.5.3"
1111 | source = "registry+https://github.com/rust-lang/crates.io-index"
1112 | checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
1113 | dependencies = [
1114 | "form_urlencoded",
1115 | "idna",
1116 | "percent-encoding",
1117 | ]
1118 |
1119 | [[package]]
1120 | name = "utf16_iter"
1121 | version = "1.0.5"
1122 | source = "registry+https://github.com/rust-lang/crates.io-index"
1123 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
1124 |
1125 | [[package]]
1126 | name = "utf8_iter"
1127 | version = "1.0.4"
1128 | source = "registry+https://github.com/rust-lang/crates.io-index"
1129 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
1130 |
1131 | [[package]]
1132 | name = "vcpkg"
1133 | version = "0.2.15"
1134 | source = "registry+https://github.com/rust-lang/crates.io-index"
1135 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
1136 |
1137 | [[package]]
1138 | name = "version_check"
1139 | version = "0.9.5"
1140 | source = "registry+https://github.com/rust-lang/crates.io-index"
1141 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1142 |
1143 | [[package]]
1144 | name = "want"
1145 | version = "0.3.1"
1146 | source = "registry+https://github.com/rust-lang/crates.io-index"
1147 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
1148 | dependencies = [
1149 | "try-lock",
1150 | ]
1151 |
1152 | [[package]]
1153 | name = "wasi"
1154 | version = "0.11.0+wasi-snapshot-preview1"
1155 | source = "registry+https://github.com/rust-lang/crates.io-index"
1156 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1157 |
1158 | [[package]]
1159 | name = "webdriver"
1160 | version = "0.50.0"
1161 | source = "registry+https://github.com/rust-lang/crates.io-index"
1162 | checksum = "144ab979b12d36d65065635e646549925de229954de2eb3b47459b432a42db71"
1163 | dependencies = [
1164 | "base64 0.21.7",
1165 | "bytes",
1166 | "cookie 0.16.2",
1167 | "http 0.2.12",
1168 | "log",
1169 | "serde",
1170 | "serde_derive",
1171 | "serde_json",
1172 | "thiserror",
1173 | "time",
1174 | "unicode-segmentation",
1175 | "url",
1176 | ]
1177 |
1178 | [[package]]
1179 | name = "windows-sys"
1180 | version = "0.52.0"
1181 | source = "registry+https://github.com/rust-lang/crates.io-index"
1182 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
1183 | dependencies = [
1184 | "windows-targets",
1185 | ]
1186 |
1187 | [[package]]
1188 | name = "windows-sys"
1189 | version = "0.59.0"
1190 | source = "registry+https://github.com/rust-lang/crates.io-index"
1191 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
1192 | dependencies = [
1193 | "windows-targets",
1194 | ]
1195 |
1196 | [[package]]
1197 | name = "windows-targets"
1198 | version = "0.52.6"
1199 | source = "registry+https://github.com/rust-lang/crates.io-index"
1200 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
1201 | dependencies = [
1202 | "windows_aarch64_gnullvm",
1203 | "windows_aarch64_msvc",
1204 | "windows_i686_gnu",
1205 | "windows_i686_gnullvm",
1206 | "windows_i686_msvc",
1207 | "windows_x86_64_gnu",
1208 | "windows_x86_64_gnullvm",
1209 | "windows_x86_64_msvc",
1210 | ]
1211 |
1212 | [[package]]
1213 | name = "windows_aarch64_gnullvm"
1214 | version = "0.52.6"
1215 | source = "registry+https://github.com/rust-lang/crates.io-index"
1216 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
1217 |
1218 | [[package]]
1219 | name = "windows_aarch64_msvc"
1220 | version = "0.52.6"
1221 | source = "registry+https://github.com/rust-lang/crates.io-index"
1222 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
1223 |
1224 | [[package]]
1225 | name = "windows_i686_gnu"
1226 | version = "0.52.6"
1227 | source = "registry+https://github.com/rust-lang/crates.io-index"
1228 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
1229 |
1230 | [[package]]
1231 | name = "windows_i686_gnullvm"
1232 | version = "0.52.6"
1233 | source = "registry+https://github.com/rust-lang/crates.io-index"
1234 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
1235 |
1236 | [[package]]
1237 | name = "windows_i686_msvc"
1238 | version = "0.52.6"
1239 | source = "registry+https://github.com/rust-lang/crates.io-index"
1240 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
1241 |
1242 | [[package]]
1243 | name = "windows_x86_64_gnu"
1244 | version = "0.52.6"
1245 | source = "registry+https://github.com/rust-lang/crates.io-index"
1246 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
1247 |
1248 | [[package]]
1249 | name = "windows_x86_64_gnullvm"
1250 | version = "0.52.6"
1251 | source = "registry+https://github.com/rust-lang/crates.io-index"
1252 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
1253 |
1254 | [[package]]
1255 | name = "windows_x86_64_msvc"
1256 | version = "0.52.6"
1257 | source = "registry+https://github.com/rust-lang/crates.io-index"
1258 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
1259 |
1260 | [[package]]
1261 | name = "write16"
1262 | version = "1.0.0"
1263 | source = "registry+https://github.com/rust-lang/crates.io-index"
1264 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
1265 |
1266 | [[package]]
1267 | name = "writeable"
1268 | version = "0.5.5"
1269 | source = "registry+https://github.com/rust-lang/crates.io-index"
1270 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
1271 |
1272 | [[package]]
1273 | name = "yoke"
1274 | version = "0.7.4"
1275 | source = "registry+https://github.com/rust-lang/crates.io-index"
1276 | checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
1277 | dependencies = [
1278 | "serde",
1279 | "stable_deref_trait",
1280 | "yoke-derive",
1281 | "zerofrom",
1282 | ]
1283 |
1284 | [[package]]
1285 | name = "yoke-derive"
1286 | version = "0.7.4"
1287 | source = "registry+https://github.com/rust-lang/crates.io-index"
1288 | checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
1289 | dependencies = [
1290 | "proc-macro2",
1291 | "quote",
1292 | "syn",
1293 | "synstructure",
1294 | ]
1295 |
1296 | [[package]]
1297 | name = "zerofrom"
1298 | version = "0.1.4"
1299 | source = "registry+https://github.com/rust-lang/crates.io-index"
1300 | checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
1301 | dependencies = [
1302 | "zerofrom-derive",
1303 | ]
1304 |
1305 | [[package]]
1306 | name = "zerofrom-derive"
1307 | version = "0.1.4"
1308 | source = "registry+https://github.com/rust-lang/crates.io-index"
1309 | checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
1310 | dependencies = [
1311 | "proc-macro2",
1312 | "quote",
1313 | "syn",
1314 | "synstructure",
1315 | ]
1316 |
1317 | [[package]]
1318 | name = "zerovec"
1319 | version = "0.10.4"
1320 | source = "registry+https://github.com/rust-lang/crates.io-index"
1321 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
1322 | dependencies = [
1323 | "yoke",
1324 | "zerofrom",
1325 | "zerovec-derive",
1326 | ]
1327 |
1328 | [[package]]
1329 | name = "zerovec-derive"
1330 | version = "0.10.3"
1331 | source = "registry+https://github.com/rust-lang/crates.io-index"
1332 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
1333 | dependencies = [
1334 | "proc-macro2",
1335 | "quote",
1336 | "syn",
1337 | ]
1338 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = [
4 | "src/rust",
5 | "examples/minimal",
6 | "examples/features",
7 | "examples/dom",
8 | "examples/callbacks",
9 | ]
10 | default-members = ["src/rust"]
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Andreas Tzionis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TinyWeb 🌱 Rust on the client. No dependencies.
2 |
3 | Build the client side with Rust! Backend agnostic. Less than 800 lines of code.
4 |
5 | # What's TinyWeb?
6 |
7 | TinyWeb is a toolkit for building web applications focused on both correctness and simplicity.
8 |
9 | Enables client-side applications to be built in pure Rust, similar to backend applications, leveraging the language strict type system and great built-in tooling. Has a tiny footprint with less than 800 lines of code, has no build step and no external dependencies.
10 |
11 |
12 | # Features
13 |
14 | - No Javascript
15 | - No macros
16 | - No dependencies
17 | - No build step
18 | - Just HTML & Rust (Wasm)
19 |
20 | **Note:** No build step besides `cargo build`
21 |
22 | # Getting Started
23 |
24 | ### Use the starter project
25 |
26 | - Fork the [tinyweb-starter](https://github.com/LiveDuo/tinyweb-starter) project
27 |
28 | [](https://www.youtube.com/watch?v=44P3IVnjEqo "Tutorial")
29 |
30 | ### Create a new project
31 |
32 | 1. Create a new Rust project with `cargo new tinyweb-example --lib`. Add `crate-type =["cdylib"]` in `Cargo.toml` and install the crate with `cargo add tinyweb --git https://github.com/LiveDuo/tinyweb`.
33 |
34 | 2. Update the `src/lib.rs`:
35 | ```rs
36 | use tinyweb::element::El;
37 | use tinyweb::invoke::Js;
38 |
39 | fn component() -> El {
40 | El::new("div")
41 | .child(El::new("button").text("print").on("click", move |_| {
42 | Js::invoke("alert('hello browser')", &[]);
43 | }))
44 | }
45 |
46 | #[no_mangle]
47 | pub fn main() {
48 | let body = Js::invoke("return document.querySelector('body')", &[]).to_ref().unwrap();
49 | component().mount(&body);
50 | }
51 | ```
52 |
53 | 3. Create an `index.html` in a new `public` folder:
54 | ```html
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | ```
65 |
66 |
67 |
68 | 4. Build the project with `cargo build --target wasm32-unknown-unknown -r`. Then `cp target/wasm32-unknown-unknown/release/*.wasm public/client.wasm` to get the `.wasm` in the right place and serve the `public` folder with any static http server.
69 |
70 |
71 |
72 | # How it works
73 |
74 | **Initialization:** Each project built with TinyWeb has 3 components, an `index.html`, a static `main.js` and a `client.wasm` file compiled from Rust with `cargo build --target wasm32-unknown-unknown -r`. These files can be served with any static HTTP server. When the website is visited, the `index.html` file loads the [main.js](https://github.com/LiveDuo/tinyweb/blob/feature/readme/src/js/main.js) file which registers a [DOMContentLoaded](https://github.com/LiveDuo/tinyweb/blob/feature/readme/src/js/main.js) event listener. When the page finishes loading, the listener is triggered which [calls](https://github.com/LiveDuo/tinyweb/blob/feature/readme/src/js/main.js) the `main` function in the wasm file (usually making the initial DOM rendering and registering event listeners).
75 |
76 | **Browser APIs:** When a Rust function wants to invoke a browser API, it uses the [__invoke](https://github.com/LiveDuo/tinyweb/blob/feature/readme/src/rust/src/invoke.rs) function internally, which in turn calls its [counterpart](https://github.com/LiveDuo/tinyweb/blob/feature/readme/src/js/main.js) in Javascript.
77 |
78 | **Callbacks:** When a listener is registered in Rust, it takes a callback function as a parameter and that function is stored in [CALLBACK_HANDLERS](https://github.com/LiveDuo/tinyweb/blob/feature/readme/src/rust/src/callbacks.rs). Every time the callback is triggered, the [handle_callback](https://github.com/LiveDuo/tinyweb/blob/feature/readme/src/rust/src/handlers.rs) function is called which executes the callback function that was stored earlier.
79 |
80 | # How to's & guides
81 |
82 | ### Browser APIs
83 |
84 | ```rs
85 | use tinyweb::invoke::Js;
86 |
87 | Js::invoke("alert('hello browser')", &[]);
88 | ```
89 |
90 | Check it out [here](https://github.com/LiveDuo/tinyweb/blob/feature/readme/examples/features/src/lib.rs)
91 |
92 | ### Reactivity and Signals
93 |
94 | ```rs
95 |
96 | use tinyweb::signals::Signal;
97 | use tinyweb::element::El;
98 |
99 | let signal_count = Signal::new(0);
100 |
101 | El::new("button").text("add").on("click", move |_| {
102 | let count = signal_count.get() + 1;
103 | signal_count.set(count);
104 | });
105 | ```
106 |
107 | Check it out [here](https://github.com/LiveDuo/tinyweb/blob/feature/readme/examples/features/src/lib.rs)
108 |
109 | ### Router support
110 |
111 | ```rs
112 | use tinyweb::router::{Page, Router};
113 |
114 | thread_local! {
115 | pub static ROUTER: RefCell = RefCell::new(Router::default());
116 | }
117 |
118 | // initialize router
119 | let pages = &[Page::new("/page1", page_component())];
120 | ROUTER.with(|s| { *s.borrow_mut() = Router::new("body", pages); });
121 |
122 | // navigate to route
123 | ROUTER.with(|s| { s.borrow().navigate("/page1"); });
124 | ```
125 |
126 | Check it out [here](https://github.com/LiveDuo/tinyweb/blob/feature/readme/examples/features/src/lib.rs)
127 |
128 | ### Async Support
129 |
130 | ```rs
131 | use tinyweb::runtime::Runtime;
132 | use tinyweb::invoke::Js;
133 |
134 | Runtime::block_on(async move {
135 | Runtime::promise("window.setTimeout({},{})", move |c| vec![c.into(), 1_000.into()]).await;
136 | Js::invoke("alert('timer')");
137 | });
138 | ```
139 |
140 | Check it out [here](https://github.com/LiveDuo/tinyweb/blob/feature/readme/examples/features/src/lib.rs)
141 |
142 | # Roadmap
143 |
144 | ### Components & Utilities
145 |
146 | While this library tries to be minimal and has no dependencies the reality in web development is using libraries and ready-made components especially for a few slightly annoying tasks. Here are some ideas for commonly used utilities and UI components. Utilities can be included in the `examples` folder while components can be stored in a new `components` folder in this repo.
147 |
148 |
149 | Commonly used utilities
150 |
151 |
152 |
153 | - [ ] Drag & drop / resize
154 | - [ ] File upload
155 | - [ ] Markdown rendering
156 |
157 |
158 |
159 |
160 | Commonly used components
161 |
162 |
163 |
164 | - [ ] Table components
165 | - [ ] Modals, tooltips and toasts
166 | - [ ] Date / time pickers
167 | - [ ] Chart / visualization
168 |
169 |
170 |
171 | ### Benchmarks & Profiling
172 |
173 | Need benchmarks to see how this library performs against other Rust web frameworks but also against different Javascript frameworks. Need also profiling to evaluate if there are memory leaks in either Rust side or Javascript side of the library and to figure out if the compiled WASM size can be reduced further.
174 |
175 | ### Static analysis
176 |
177 | Right now `invoke` calls to the browser APIs are not type safe. Could use [webidl](https://github.com/whatwg/webidl) interfaces to do static analysis on the Javascript code against `invoke` parameters.
178 |
179 |
180 | # Backstory
181 |
182 |
183 | Show
184 |
185 | For quite some time, I couldn't decide if I like Typescript or not. On one hand, it offers stronger typing than pure JavaScript, providing more confidence in the code; but on the other hand, it comes with a heavy build system that complicates things and makes debugging significantly harder.
186 |
187 | When I had to build an application where I really cared about correctness, I realized how much I didn't trust Typescript even for what's designed to do and I tried different Rust based web frameworks instead. While these frameworks alleviated correctness concerns, they introduced significant complexity, requiring hundreds of dependencies just to get started. For reference, `leptos` depends on 231 crates and its development tool `cargo-leptos` depends on another 485 crates.
188 |
189 | Many of these dependencies come from the `wasm-bindgen` crate, which generates Rust bindings for browser APIs and the JavaScript glue code needed for these calls and is used almost universally by Rust based web frameworks as a lower level building block for accessing browser APIs.
190 |
191 | Yet, using this crate is not the only way to interact with browser APIs and many applications could benefit from a different tool that makes different tradeoffs. In particular, many applications might benefit from simplicity and ease of debugging, I know the application I'm building probably would.
192 |
193 | So, I set out to build a web framework that allows to build client side applications with Rust and has minimal footprint. The result is `TinyWeb`, a client side Rust framework built in <800 lines of code.
194 |
195 |
196 |
197 | # Credits
198 |
199 | Credits to [Richard Anaya](https://github.com/richardanaya) for his work on [web.rs](https://github.com/richardanaya/web.rs) that provided ideas to practical challenges on [async support](https://github.com/richardanaya/web.rs/blob/master/crates/web/src/executor.rs). Also, to [Greg Johnston](https://github.com/gbj) for [his videos](https://www.youtube.com/@gbjxc/videos) that show how to use Solid.js-like signals in Rust.
200 |
--------------------------------------------------------------------------------
/examples/callbacks/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/examples/callbacks/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "callbacks"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["cdylib"]
8 | test = false
9 |
10 | [dependencies]
11 | tinyweb = { path = "../../src/rust" }
12 |
--------------------------------------------------------------------------------
/examples/callbacks/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | cargo build --target wasm32-unknown-unknown -r
3 | mkdir -p /tmp/public
4 | cp ../../target/wasm32-unknown-unknown/release/callbacks.wasm /tmp/public/callbacks.wasm
5 | cp ../../src/js/main.js /tmp/public/main.js
6 | cp public/index.html /tmp/public/index.html
7 | start:
8 | python3 -m http.server -d /tmp/public
9 | dev:
10 | make build
11 | make start
12 |
--------------------------------------------------------------------------------
/examples/callbacks/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/callbacks/src/lib.rs:
--------------------------------------------------------------------------------
1 |
2 | use tinyweb::callbacks::{create_async_callback, create_callback};
3 | use tinyweb::runtime::Runtime;
4 | use tinyweb::invoke::*;
5 |
6 | #[no_mangle]
7 | pub fn main() {
8 |
9 | std::panic::set_hook(Box::new(|e| { Js::invoke("console.log({})", &[e.to_string().into()]); }));
10 |
11 | // invoke
12 | Js::invoke("console.log('invoke')", &[]);
13 |
14 | // invoke callback
15 | let function_ref = create_callback(move |_s| { Js::invoke("console.log('invoke timer')", &[]); });
16 | Js::invoke("setTimeout({}, 1000)", &[function_ref.into()]);
17 |
18 | // invoke async callback
19 | let url = "https://pokeapi.co/api/v2/pokemon/1";
20 | let (callback_ref, future) = create_async_callback();
21 | Js::invoke("fetch({}).then(r => r.json()).then(r => { {}(r) })", &[url.into(), callback_ref.into()]);
22 | Runtime::block_on(async move {
23 | let future_leak = Box::leak(Box::new(future));
24 | let object_ref = future_leak.await;
25 | let result = Js::invoke("return {}.name", &[object_ref.into()]).to_str().unwrap();
26 | Js::invoke("console.log('invoke fetch', {})", &[result.into()]);
27 | Js::deallocate(object_ref);
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/examples/dom/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/examples/dom/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "dom"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["cdylib"]
8 | test = false
9 |
10 | [dependencies]
11 | tinyweb = { path = "../../src/rust" }
12 |
--------------------------------------------------------------------------------
/examples/dom/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | cargo build --target wasm32-unknown-unknown -r
3 | mkdir -p /tmp/public
4 | cp ../../target/wasm32-unknown-unknown/release/dom.wasm /tmp/public/dom.wasm
5 | cp ../../src/js/main.js /tmp/public/main.js
6 | cp public/index.html /tmp/public/index.html
7 | start:
8 | python3 -m http.server -d /tmp/public
9 | dev:
10 | make build
11 | make start
12 |
--------------------------------------------------------------------------------
/examples/dom/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/dom/src/lib.rs:
--------------------------------------------------------------------------------
1 |
2 | use tinyweb::callbacks::create_callback;
3 | use tinyweb::invoke::*;
4 |
5 | #[no_mangle]
6 | pub fn main() {
7 |
8 | std::panic::set_hook(Box::new(|e| { Js::invoke("console.log({})", &[e.to_string().into()]); }));
9 |
10 | let button = Js::invoke("return document.createElement('button')", &[]).to_ref().unwrap();
11 | Js::invoke("{}.textContent = 'Click'", &[button.into()]);
12 |
13 | let function_ref = create_callback(move |e| { Js::invoke("alert('hello')", &[]); Js::deallocate(e); });
14 | Js::invoke("{}.addEventListener('click',{})", &[button.into(), function_ref.into()]);
15 |
16 | let body = Js::invoke("return document.querySelector('body')", &[]).to_ref().unwrap();
17 | Js::invoke("{}.appendChild({})", &[body.into(), button.into()]);
18 | }
19 |
--------------------------------------------------------------------------------
/examples/features/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/examples/features/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "features"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type =["cdylib"]
8 | test = false
9 |
10 | [dependencies]
11 | tinyweb = { path = "../../src/rust" }
12 | json = "0.12.4"
13 |
14 |
--------------------------------------------------------------------------------
/examples/features/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | cargo build --target wasm32-unknown-unknown -r
3 | mkdir -p /tmp/public
4 | cp ../../target/wasm32-unknown-unknown/release/features.wasm /tmp/public/features.wasm
5 | cp ../../src/js/main.js /tmp/public/main.js
6 | cp public/index.html /tmp/public/index.html
7 | start:
8 | python3 -m http.server -d /tmp/public
9 | dev:
10 | make build
11 | make start
12 |
--------------------------------------------------------------------------------
/examples/features/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/features/src/keycodes.rs:
--------------------------------------------------------------------------------
1 |
2 | // https://stackoverflow.com/a/23377822/413726
3 | // names of known key codes (0-255)
4 |
5 | pub const KEYBOARD_MAP: &[&str] = &[
6 | "", // [0]
7 | "", // [1]
8 | "", // [2]
9 | "CANCEL", // [3]
10 | "", // [4]
11 | "", // [5]
12 | "HELP", // [6]
13 | "", // [7]
14 | "BACK_SPACE", // [8]
15 | "TAB", // [9]
16 | "", // [10]
17 | "", // [11]
18 | "CLEAR", // [12]
19 | "ENTER", // [13]
20 | "ENTER_SPECIAL", // [14]
21 | "", // [15]
22 | "SHIFT", // [16]
23 | "CONTROL", // [17]
24 | "ALT", // [18]
25 | "PAUSE", // [19]
26 | "CAPS_LOCK", // [20]
27 | "KANA", // [21]
28 | "EISU", // [22]
29 | "JUNJA", // [23]
30 | "FINAL", // [24]
31 | "HANJA", // [25]
32 | "", // [26]
33 | "ESCAPE", // [27]
34 | "CONVERT", // [28]
35 | "NONCONVERT", // [29]
36 | "ACCEPT", // [30]
37 | "MODECHANGE", // [31]
38 | "SPACE", // [32]
39 | "PAGE_UP", // [33]
40 | "PAGE_DOWN", // [34]
41 | "END", // [35]
42 | "HOME", // [36]
43 | "LEFT", // [37]
44 | "UP", // [38]
45 | "RIGHT", // [39]
46 | "DOWN", // [40]
47 | "SELECT", // [41]
48 | "PRINT", // [42]
49 | "EXECUTE", // [43]
50 | "PRINTSCREEN", // [44]
51 | "INSERT", // [45]
52 | "DELETE", // [46]
53 | "", // [47]
54 | "0", // [48]
55 | "1", // [49]
56 | "2", // [50]
57 | "3", // [51]
58 | "4", // [52]
59 | "5", // [53]
60 | "6", // [54]
61 | "7", // [55]
62 | "8", // [56]
63 | "9", // [57]
64 | "COLON", // [58]
65 | "SEMICOLON", // [59]
66 | "LESS_THAN", // [60]
67 | "EQUALS", // [61]
68 | "GREATER_THAN", // [62]
69 | "QUESTION_MARK", // [63]
70 | "AT", // [64]
71 | "A", // [65]
72 | "B", // [66]
73 | "C", // [67]
74 | "D", // [68]
75 | "E", // [69]
76 | "F", // [70]
77 | "G", // [71]
78 | "H", // [72]
79 | "I", // [73]
80 | "J", // [74]
81 | "K", // [75]
82 | "L", // [76]
83 | "M", // [77]
84 | "N", // [78]
85 | "O", // [79]
86 | "P", // [80]
87 | "Q", // [81]
88 | "R", // [82]
89 | "S", // [83]
90 | "T", // [84]
91 | "U", // [85]
92 | "V", // [86]
93 | "W", // [87]
94 | "X", // [88]
95 | "Y", // [89]
96 | "Z", // [90]
97 | "OS_KEY", // [91] Windows Key (Windows) or Command Key (Mac)
98 | "", // [92]
99 | "CONTEXT_MENU", // [93]
100 | "", // [94]
101 | "SLEEP", // [95]
102 | "NUMPAD0", // [96]
103 | "NUMPAD1", // [97]
104 | "NUMPAD2", // [98]
105 | "NUMPAD3", // [99]
106 | "NUMPAD4", // [100]
107 | "NUMPAD5", // [101]
108 | "NUMPAD6", // [102]
109 | "NUMPAD7", // [103]
110 | "NUMPAD8", // [104]
111 | "NUMPAD9", // [105]
112 | "MULTIPLY", // [106]
113 | "ADD", // [107]
114 | "SEPARATOR", // [108]
115 | "SUBTRACT", // [109]
116 | "DECIMAL", // [110]
117 | "DIVIDE", // [111]
118 | "F1", // [112]
119 | "F2", // [113]
120 | "F3", // [114]
121 | "F4", // [115]
122 | "F5", // [116]
123 | "F6", // [117]
124 | "F7", // [118]
125 | "F8", // [119]
126 | "F9", // [120]
127 | "F10", // [121]
128 | "F11", // [122]
129 | "F12", // [123]
130 | "F13", // [124]
131 | "F14", // [125]
132 | "F15", // [126]
133 | "F16", // [127]
134 | "F17", // [128]
135 | "F18", // [129]
136 | "F19", // [130]
137 | "F20", // [131]
138 | "F21", // [132]
139 | "F22", // [133]
140 | "F23", // [134]
141 | "F24", // [135]
142 | "", // [136]
143 | "", // [137]
144 | "", // [138]
145 | "", // [139]
146 | "", // [140]
147 | "", // [141]
148 | "", // [142]
149 | "", // [143]
150 | "NUM_LOCK", // [144]
151 | "SCROLL_LOCK", // [145]
152 | "WIN_OEM_FJ_JISHO", // [146]
153 | "WIN_OEM_FJ_MASSHOU", // [147]
154 | "WIN_OEM_FJ_TOUROKU", // [148]
155 | "WIN_OEM_FJ_LOYA", // [149]
156 | "WIN_OEM_FJ_ROYA", // [150]
157 | "", // [151]
158 | "", // [152]
159 | "", // [153]
160 | "", // [154]
161 | "", // [155]
162 | "", // [156]
163 | "", // [157]
164 | "", // [158]
165 | "", // [159]
166 | "CIRCUMFLEX", // [160]
167 | "EXCLAMATION", // [161]
168 | "DOUBLE_QUOTE", // [162]
169 | "HASH", // [163]
170 | "DOLLAR", // [164]
171 | "PERCENT", // [165]
172 | "AMPERSAND", // [166]
173 | "UNDERSCORE", // [167]
174 | "OPEN_PAREN", // [168]
175 | "CLOSE_PAREN", // [169]
176 | "ASTERISK", // [170]
177 | "PLUS", // [171]
178 | "PIPE", // [172]
179 | "HYPHEN_MINUS", // [173]
180 | "OPEN_CURLY_BRACKET", // [174]
181 | "CLOSE_CURLY_BRACKET", // [175]
182 | "TILDE", // [176]
183 | "", // [177]
184 | "", // [178]
185 | "", // [179]
186 | "", // [180]
187 | "VOLUME_MUTE", // [181]
188 | "VOLUME_DOWN", // [182]
189 | "VOLUME_UP", // [183]
190 | "", // [184]
191 | "", // [185]
192 | "SEMICOLON", // [186]
193 | "EQUALS", // [187]
194 | "COMMA", // [188]
195 | "MINUS", // [189]
196 | "PERIOD", // [190]
197 | "SLASH", // [191]
198 | "BACK_QUOTE", // [192]
199 | "", // [193]
200 | "", // [194]
201 | "", // [195]
202 | "", // [196]
203 | "", // [197]
204 | "", // [198]
205 | "", // [199]
206 | "", // [200]
207 | "", // [201]
208 | "", // [202]
209 | "", // [203]
210 | "", // [204]
211 | "", // [205]
212 | "", // [206]
213 | "", // [207]
214 | "", // [208]
215 | "", // [209]
216 | "", // [210]
217 | "", // [211]
218 | "", // [212]
219 | "", // [213]
220 | "", // [214]
221 | "", // [215]
222 | "", // [216]
223 | "", // [217]
224 | "", // [218]
225 | "OPEN_BRACKET", // [219]
226 | "BACK_SLASH", // [220]
227 | "CLOSE_BRACKET", // [221]
228 | "QUOTE", // [222]
229 | "", // [223]
230 | "META", // [224]
231 | "ALTGR", // [225]
232 | "", // [226]
233 | "WIN_ICO_HELP", // [227]
234 | "WIN_ICO_00", // [228]
235 | "", // [229]
236 | "WIN_ICO_CLEAR", // [230]
237 | "", // [231]
238 | "", // [232]
239 | "WIN_OEM_RESET", // [233]
240 | "WIN_OEM_JUMP", // [234]
241 | "WIN_OEM_PA1", // [235]
242 | "WIN_OEM_PA2", // [236]
243 | "WIN_OEM_PA3", // [237]
244 | "WIN_OEM_WSCTRL", // [238]
245 | "WIN_OEM_CUSEL", // [239]
246 | "WIN_OEM_ATTN", // [240]
247 | "WIN_OEM_FINISH", // [241]
248 | "WIN_OEM_COPY", // [242]
249 | "WIN_OEM_AUTO", // [243]
250 | "WIN_OEM_ENLW", // [244]
251 | "WIN_OEM_BACKTAB", // [245]
252 | "ATTN", // [246]
253 | "CRSEL", // [247]
254 | "EXSEL", // [248]
255 | "EREOF", // [249]
256 | "PLAY", // [250]
257 | "ZOOM", // [251]
258 | "", // [252]
259 | "PA1", // [253]
260 | "WIN_OEM_CLEAR", // [254]
261 | "" // [255]
262 | ];
263 |
--------------------------------------------------------------------------------
/examples/features/src/lib.rs:
--------------------------------------------------------------------------------
1 |
2 | mod keycodes;
3 |
4 | use std::cell::RefCell;
5 |
6 | use json::JsonValue;
7 |
8 | use tinyweb::callbacks::create_async_callback;
9 | use tinyweb::router::{Page, Router};
10 | use tinyweb::runtime::Runtime;
11 | use tinyweb::signals::Signal;
12 | use tinyweb::element::El;
13 |
14 | use tinyweb::invoke::*;
15 |
16 | const BUTTON_CLASSES: &[&str] = &["bg-blue-500", "hover:bg-blue-700", "text-white", "p-2", "rounded", "m-2"];
17 |
18 | thread_local! {
19 | pub static ROUTER: RefCell = RefCell::new(Router::default());
20 | }
21 |
22 | async fn fetch_json(method: &str, url: &str, body: Option) -> Result {
23 | let body = body.map(|s| s.dump()).unwrap_or_default();
24 | let (callback_ref, future) = create_async_callback();
25 | let request = r#"
26 | const options = { method: {}, headers: { 'Content-Type': 'application/json' }, body: p0 !== 'GET' ? {} : null };
27 | fetch({}, options).then(r => r.json()).then(r => { {}(r) })
28 | "#;
29 | Js::invoke(request, &[method.into(), body.into(), url.into(), callback_ref.into()]);
30 | let future_leak = Box::leak(Box::new(future));
31 | let result_ref = future_leak.await;
32 | let result = Js::invoke("return JSON.stringify({})", &[result_ref.into()]).to_str().unwrap();
33 | Js::deallocate(result_ref);
34 | json::parse(&result).map_err(|_| "Parse error".to_owned())
35 | }
36 |
37 | fn page1() -> El {
38 |
39 | // signals
40 | let signal_key = Signal::new("-".to_owned());
41 | let signal_count = Signal::new(0);
42 | let signal_time = Signal::new("-");
43 |
44 | El::new("div")
45 | .once(move |_| {
46 |
47 | // add listener
48 | let body = Js::invoke("return document.querySelector({})", &["body".into()]).to_ref().unwrap();
49 |
50 | El::from(&body).on("keydown", move |e| {
51 | let key_code = Js::invoke("return {}[{}]", &[e.into(), "keyCode".into()]).to_num().unwrap();
52 | let key_name = keycodes::KEYBOARD_MAP[key_code as usize];
53 | let text = format!("Pressed: {}", key_name);
54 | signal_key.set(text);
55 | });
56 |
57 | // start timer
58 | Runtime::block_on(async move {
59 | loop {
60 | signal_time.set("⏰ tik");
61 | Runtime::promise("window.setTimeout({},{})", move |c| vec![c.into(), 1_000.into()]).await;
62 | signal_time.set("⏰ tok");
63 | Runtime::promise("window.setTimeout({},{})", move |c| vec![c.into(), 1_000.into()]).await;
64 | }
65 | });
66 |
67 | })
68 | .classes(&["m-2"])
69 | .child(El::new("button").text("api").classes(&BUTTON_CLASSES).on_async("click", move |_| async {
70 | let url = format!("https://pokeapi.co/api/v2/pokemon/{}", 1);
71 | let result = fetch_json("GET", &url, None).await.unwrap();
72 | let name = result["name"].as_str().unwrap();
73 | Js::invoke("alert({})", &[name.into()]);
74 | }))
75 | .child(El::new("button").text("page 2").classes(&BUTTON_CLASSES).on("click", move |_| {
76 | ROUTER.with(|s| { s.borrow().navigate("/page2"); });
77 | }))
78 | .child(El::new("br"))
79 | .child(El::new("button").text("add").classes(&BUTTON_CLASSES).on("click", move |_| {
80 | let count = signal_count.get() + 1;
81 | signal_count.set(count);
82 | }))
83 | .child(El::new("div").text("0").once(move |el| {
84 | signal_count.on(move |v| { Js::invoke("{}.innerHTML = {}", &[el.into(), v.to_string().into()]); });
85 | }))
86 | .child(El::new("div").text("-").once(move |el| {
87 | signal_time.on(move |v| { Js::invoke("{}.innerHTML = {}", &[el.into(), v.into()]); });
88 | }))
89 | .child(El::new("div").text("-").once(move |el| {
90 | signal_key.on(move |v| { Js::invoke("{}.innerHTML = {}", &[el.into(), v.into()]); });
91 | }))
92 | }
93 |
94 | fn page2() -> El {
95 | El::new("div")
96 | .classes(&["m-2"])
97 | .child(El::new("button").text("page 1").classes(&BUTTON_CLASSES).on("click", move |_| {
98 | ROUTER.with(|s| { s.borrow().navigate("/page1"); });
99 | }))
100 | }
101 |
102 | #[no_mangle]
103 | pub fn main() {
104 |
105 | std::panic::set_hook(Box::new(|e| { Js::invoke("console.log({})", &[e.to_string().into()]); }));
106 |
107 | // init router
108 | let pages = &[Page::new("/page1", page1()), Page::new("/page2", page2())];
109 | ROUTER.with(|s| { *s.borrow_mut() = Router::new("body", pages); });
110 | }
111 |
--------------------------------------------------------------------------------
/examples/minimal/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "minimal"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type =["cdylib"]
8 | test = false
9 |
10 | [dependencies]
11 | tinyweb = { path = "../../src/rust" }
12 |
13 |
--------------------------------------------------------------------------------
/examples/minimal/src/lib.rs:
--------------------------------------------------------------------------------
1 |
2 | use tinyweb::invoke::*;
3 |
4 | #[no_mangle]
5 | pub fn main() {
6 | let body = Js::invoke("return document.querySelector({})", &["body".into()]).to_ref().unwrap();
7 | Js::invoke("{}.innerHTML = {}", &[body.into(), "hello".into()]);
8 | }
9 |
--------------------------------------------------------------------------------
/src/js/main.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | let wasmModule = {}
4 |
5 | const objects = new Map()
6 | if (typeof window !== 'undefined') window.objects = objects
7 |
8 | const getRandomId = () => Math.floor(Math.random() * Number(0xFFFFn))
9 |
10 | const textEncoder = new TextEncoder()
11 | const textDecoder = new TextDecoder()
12 |
13 | const readParamsFromMemory = (ptr, len) => {
14 |
15 | const memory = new Uint8Array(wasmModule.instance.exports.memory.buffer)
16 | const params = new Uint8Array(memory.slice(ptr, ptr + len))
17 | const dataView = new DataView(params.buffer)
18 | const values = []
19 | let i = 0
20 | while (i < params.length) {
21 | if (params[i] === 0) { // undefined
22 | values.push(undefined)
23 | i += 1
24 | } else if (params[i] === 1) { // null
25 | values.push(null)
26 | i += 1
27 | } else if (params[i] === 2) { // f64
28 | values.push(dataView.getFloat64(i + 1, true))
29 | i += 1 + 8
30 | } else if (params[i] === 3) { // big int
31 | values.push(dataView.getBigInt64(i + 1, true))
32 | i += 1 + 8
33 | } else if (params[i] === 4) { // string
34 | const ptr = dataView.getInt32(i + 1, true)
35 | const len = dataView.getInt32(i + 1 + 4, true)
36 | values.push(textDecoder.decode(memory.subarray(ptr, ptr + len)))
37 | i += 1 + 4 + 4
38 | } else if (params[i] === 5) { // true
39 | values.push(true)
40 | i += 1
41 | } else if (params[i] === 6) { // false
42 | values.push(false)
43 | i += 1
44 | } else if (params[i] === 7) { // object ref
45 | const objectId = dataView.getUint32(i + 1, true)
46 | values.push(objects.get(objectId))
47 | i += 1 + 4
48 | } else {
49 | throw new Error('Invalid parameter type')
50 | }
51 | }
52 | return values
53 | }
54 |
55 | const runFunction = (c_ptr, c_len, p_ptr, p_len) => {
56 | const memory = new Uint8Array(wasmModule.instance.exports.memory.buffer)
57 | const functionBody = textDecoder.decode(memory.subarray(c_ptr, c_ptr + c_len))
58 | const _function = Function(`'use strict';return(${functionBody})`)()
59 |
60 | const values = readParamsFromMemory(p_ptr, p_len)
61 | return _function.call({}, ...values)
62 | }
63 |
64 | const getWasmImports = () => {
65 |
66 | const env = {
67 | __invoke (c_ptr, c_len, p_ptr, p_len) {
68 | const result = runFunction(c_ptr, c_len, p_ptr, p_len)
69 | if (typeof result === "undefined") {
70 | return (BigInt(0) << 32n) | BigInt(0)
71 | } else if (typeof result === "number") {
72 | const ptr = writeBufferToMemory(textEncoder.encode(result))
73 | return (BigInt(1) << 32n) | BigInt(ptr)
74 | } else if (typeof result === "function") {
75 |
76 | const objectId = getRandomId()
77 | objects.set(objectId, result)
78 |
79 | return (BigInt(2) << 32n) | BigInt(objectId)
80 | } else if (typeof result === "object") {
81 | // because js has no primitive types for arrays
82 | if (result instanceof Uint8Array) {
83 | const ptr = writeBufferToMemory(new Uint8Array(result))
84 | return (BigInt(3) << 32n) | BigInt(ptr)
85 | } else {
86 |
87 | const objectId = getRandomId()
88 | objects.set(objectId, result)
89 |
90 | return (BigInt(2) << 32n) | BigInt(objectId)
91 | }
92 | } else if (typeof result === "string") {
93 | const ptr = writeBufferToMemory(textEncoder.encode(result))
94 | return (BigInt(4) << 32n) | BigInt(ptr)
95 | } else if (typeof result === "bigint") {
96 | return (BigInt(5) << 32n) | BigInt(result)
97 | } else if (typeof result === "boolean") {
98 | return (BigInt(6) << 32n) | BigInt(result)
99 | } else {
100 | throw new Error("Invalid result type")
101 | }
102 | },
103 | __deallocate(objectId) {
104 | objects.delete(objectId)
105 | }
106 | }
107 | return { env }
108 | }
109 |
110 | const loadWasm = async () => {
111 | const imports = getWasmImports()
112 | const wasmScript = document.querySelector('script[type="application/wasm"]')
113 | const wasmBuffer = await fetch(wasmScript.src).then(r => r.arrayBuffer())
114 | wasmModule = await WebAssembly.instantiate(wasmBuffer, imports)
115 | wasmModule.instance.exports.main()
116 | }
117 |
118 | const writeBufferToMemory = (buffer) => {
119 | const allocationId = wasmModule.instance.exports.create_allocation(buffer.length)
120 | const allocationPtr = wasmModule.instance.exports.get_allocation(allocationId)
121 | const memory = new Uint8Array(wasmModule.instance.exports.memory.buffer)
122 | memory.set(buffer, allocationPtr)
123 | return allocationId
124 | }
125 |
126 | const loadExports = () => {
127 | exports.wasmModule = wasmModule
128 | exports.writeBufferToMemory = writeBufferToMemory
129 | exports.readParamsFromMemory = readParamsFromMemory
130 | }
131 |
132 | if (typeof window !== 'undefined') { // load wasm (browser)
133 | document.addEventListener('DOMContentLoaded', loadWasm)
134 | } else { // load exports (nodejs)
135 | loadExports()
136 | }
137 |
--------------------------------------------------------------------------------
/src/js/main.test.js:
--------------------------------------------------------------------------------
1 | const test = require('node:test')
2 | const assert = require('node:assert')
3 |
4 | const { readParamsFromMemory, writeBufferToMemory, wasmModule } = require('./main')
5 |
6 | // node src/js/main.test.js
7 |
8 | test('check read params', () => {
9 |
10 | const float64View = new DataView(new ArrayBuffer(8))
11 | float64View.setFloat64(0, 42.42, true)
12 | const float64Array = new Uint8Array(float64View.buffer)
13 |
14 | const bigInt64View = new DataView(new ArrayBuffer(8))
15 | bigInt64View.setBigInt64(0, 42n, true)
16 | const bigInt64Array = new Uint8Array(bigInt64View.buffer)
17 |
18 | const uint32View = new DataView(new ArrayBuffer(4))
19 | uint32View.setUint32(0, 42, true)
20 | const uint32Array = new Uint8Array(uint32View.buffer)
21 |
22 | const testCases = [
23 | {memory: [0], expected: [undefined]},
24 | {memory: [1], expected: [null]},
25 | {memory: [2, ...float64Array], expected: [42.42]},
26 | {memory: [3, ...bigInt64Array], expected: [42n]},
27 | {memory: [4, ...uint32Array, ...uint32Array], expected: ['']},
28 | {memory: [5], expected: [true]},
29 | {memory: [6], expected: [false]},
30 | {memory: [7, ...uint32Array], expected: [undefined]},
31 | ]
32 | for (const testCase of testCases) {
33 | wasmModule.instance = { exports: { memory: { buffer: testCase.memory } } }
34 |
35 | const result = readParamsFromMemory(0, testCase.memory.length)
36 | assert.deepStrictEqual(result, testCase.expected)
37 | }
38 | })
39 |
40 | test('check write buffer', () => {
41 |
42 | const testCases = [
43 | {memory: [], expected: 0},
44 | ]
45 | const create_allocation = () => { return 0 }
46 | const get_allocation = () => { return 0 }
47 | for (const testCase of testCases) {
48 |
49 | const exports = { create_allocation, get_allocation, memory: { buffer: testCase.memory } }
50 | wasmModule.instance = { exports }
51 |
52 | const result = writeBufferToMemory(0, [])
53 | assert.deepStrictEqual(result, testCase.expected)
54 | }
55 | })
56 |
--------------------------------------------------------------------------------
/src/rust/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "tinyweb"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | doc = false
8 | doctest = false
9 |
10 | [dev-dependencies]
11 | fantoccini = "0.21.1"
12 | serde_json = "1.0.127"
13 | tokio = { version = "1", features = ["full"] }
14 |
--------------------------------------------------------------------------------
/src/rust/src/allocations.rs:
--------------------------------------------------------------------------------
1 |
2 | use std::cell::RefCell;
3 |
4 | thread_local! {
5 | pub static ALLOCATIONS: RefCell>> = RefCell::new(Vec::new());
6 | }
7 |
8 | #[no_mangle]
9 | pub fn create_allocation(size: usize) -> usize {
10 | ALLOCATIONS.with_borrow_mut(|s| { s.push(vec![0; size]); s.len() - 1 })
11 | }
12 |
13 | #[no_mangle]
14 | pub fn get_allocation(allocation_id: usize) -> *const u8 {
15 | ALLOCATIONS.with_borrow(|s| s.get(allocation_id).unwrap().as_ptr())
16 | }
17 |
18 | #[cfg(test)]
19 | mod tests {
20 | use super::*;
21 |
22 | #[test]
23 | fn test_allocation() {
24 |
25 | // test string
26 | let text = "hello";
27 | let id = create_allocation(1);
28 | ALLOCATIONS.with_borrow_mut(|s| { s[id as usize] = text.as_bytes().to_vec(); });
29 | let allocation_data = ALLOCATIONS.with_borrow(|s| s.get(id as usize).unwrap().to_owned());
30 | let memory_text = String::from_utf8(allocation_data).unwrap();
31 | assert_eq!(memory_text, text);
32 |
33 | // test vec
34 | let vec = vec![1, 2];
35 | let id = create_allocation(1);
36 | ALLOCATIONS.with_borrow_mut(|s| { s[id as usize] = vec.clone(); });
37 | let memory_vec = ALLOCATIONS.with_borrow(|s| s.get(id as usize).unwrap().to_owned());
38 | assert_eq!(memory_vec, vec);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/rust/src/callbacks.rs:
--------------------------------------------------------------------------------
1 |
2 | use crate::runtime::{FutureState, FutureTask};
3 | use crate::invoke::{Js, ObjectRef};
4 |
5 | use std::collections::HashMap;
6 | use std::cell::RefCell;
7 | use std::rc::Rc;
8 |
9 | thread_local! {
10 | pub static CALLBACK_HANDLERS: RefCell>> = Default::default();
11 | }
12 |
13 | pub fn create_callback(mut handler: impl FnMut(ObjectRef) + 'static) -> ObjectRef {
14 | let code = r#"
15 | const handler = (e) => {
16 | const handlerObjectId = getRandomId();
17 | objects.set(handlerObjectId, e);
18 | wasmModule.instance.exports.handle_callback(objectId, handlerObjectId);
19 | };
20 | const objectId = getRandomId();
21 | objects.set(objectId, handler);
22 | return objectId;
23 | "#;
24 | let object_id = Js::invoke(code, &[]).to_num().unwrap();
25 | let function_ref = ObjectRef::new(object_id as u32);
26 | let cb = move |value| { handler(value); };
27 | CALLBACK_HANDLERS.with(|s| { s.borrow_mut().insert(function_ref.clone(), Box::new(cb)); });
28 | function_ref
29 | }
30 |
31 | #[no_mangle]
32 | pub fn handle_callback(callback_id: u32, param_id: u32) {
33 |
34 | let object_ref = ObjectRef::new(param_id as u32);
35 | let callback_ref = ObjectRef::new(callback_id);
36 |
37 | CALLBACK_HANDLERS.with(|s| {
38 | let handler = s.borrow_mut().get_mut(&callback_ref).unwrap() as *mut Box;
39 | unsafe { (*handler)(object_ref) }
40 | });
41 |
42 | Js::deallocate(callback_ref);
43 | }
44 |
45 | pub fn create_async_callback() -> (ObjectRef, FutureTask) {
46 | let future = FutureTask { state: Rc::new(RefCell::new(FutureState::Init)) };
47 | let state_clone = future.state.clone();
48 | let callback_ref = create_callback(move |e| {
49 | let mut future_state = state_clone.borrow_mut();
50 | if let FutureState::Pending(ref mut waker) = &mut *future_state { waker.to_owned().wake(); }
51 | *future_state = FutureState::Ready(e);
52 | });
53 | return (callback_ref, future);
54 | }
55 |
56 | #[cfg(test)]
57 | mod tests {
58 |
59 | use std::cell::RefCell;
60 | use std::rc::Rc;
61 |
62 | use super::*;
63 |
64 | #[test]
65 | fn test_callback() {
66 |
67 | // add listener
68 | let has_run = Rc::new(RefCell::new(false));
69 | let has_run_clone = has_run.clone();
70 | create_callback(move |_| { *has_run_clone.borrow_mut() = true; });
71 |
72 | // simulate callback
73 | let function_ref = ObjectRef::new(0);
74 | handle_callback(*function_ref, 0);
75 | assert_eq!(*has_run.borrow(), true);
76 |
77 | // remove listener
78 | CALLBACK_HANDLERS.with(|s| { s.borrow_mut().remove(&function_ref); });
79 | let count = CALLBACK_HANDLERS.with(|s| s.borrow().len());
80 | assert_eq!(count, 0);
81 | }
82 |
83 | #[test]
84 | fn test_future_callback() {
85 |
86 | // add listener
87 | let (function_ref, future) = create_async_callback();
88 |
89 | // simulate callback
90 | handle_callback(*function_ref, 0);
91 | crate::runtime::Runtime::block_on(async move { future.await; });
92 |
93 | // remove listener
94 | CALLBACK_HANDLERS.with(|s| { s.borrow_mut().remove(&function_ref); });
95 | let count = CALLBACK_HANDLERS.with(|s| s.borrow().len());
96 | assert_eq!(count, 0);
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/src/rust/src/element.rs:
--------------------------------------------------------------------------------
1 |
2 | use std::future::Future;
3 | use std::cell::RefCell;
4 | use std::rc::Rc;
5 |
6 | use crate::invoke::{Js, ObjectRef};
7 | use crate::runtime::Runtime;
8 |
9 | #[derive(Debug, Clone, PartialEq, Eq)]
10 | pub struct El { pub element: ObjectRef, pub callbacks: RefCell> }
11 |
12 | impl El {
13 | pub fn new(tag: &str) -> Self {
14 | let el = Js::invoke("return document.createElement({})", &[tag.into()]).to_ref().unwrap();
15 | Self { element: el, callbacks: RefCell::new(vec![]) }
16 | }
17 | pub fn from(el: &ObjectRef) -> Self {
18 | Self { element: el.to_owned(), callbacks: RefCell::new(vec![]) }
19 | }
20 | pub fn mount(&self, parent: &ObjectRef) {
21 | Js::invoke("{}.appendChild({})", &[parent.into(), self.element.into()]);
22 | }
23 | pub fn unmount(&self) {
24 | let mut c = self.callbacks.borrow_mut();
25 | c.iter().for_each(|p| {
26 | crate::callbacks::CALLBACK_HANDLERS.with(|s| { let _ = s.borrow_mut().remove(p).unwrap(); });
27 | });
28 | c.clear();
29 | }
30 | pub fn attr(self, name: &str, value: &str) -> Self {
31 | Js::invoke("{}.setAttribute({},{})", &[self.element.into(), name.into(), value.into()]);
32 | self
33 | }
34 | pub fn attr_fn(self, name: &str, value: &str, cb: impl Fn() -> bool + 'static) -> Self {
35 | if cb() {
36 | Js::invoke("{}.setAttribute({},{})", &[self.element.into(), name.into(), value.into()]);
37 | }
38 | self
39 | }
40 | pub fn classes(self, classes: &[&str]) -> Self {
41 | classes.iter().for_each(|&c| { Js::invoke("{}.classList.add({})", &[self.element.into(), c.into()]); });
42 | self
43 | }
44 | pub fn child(self, child: Self) -> Self {
45 | Js::invoke("{}.appendChild({})", &[self.element.into(), child.element.into()]);
46 | self
47 | }
48 | pub fn children(self, children: &[Self]) -> Self {
49 | Js::invoke("{}.innerHTML = {}", &[self.element.into(), "".into()]);
50 | for child in children {
51 | Js::invoke("{}.appendChild({})", &[self.element.into(), child.element.into()]);
52 | }
53 | self
54 | }
55 | pub fn once(self, cb: impl FnMut(ObjectRef) + 'static) -> Self {
56 |
57 | let cb = Rc::new(RefCell::new(cb));
58 | cb.borrow_mut()(self.element);
59 |
60 | self
61 | }
62 | pub fn once_async>(self, cb: impl FnMut(ObjectRef) -> Fut + 'static) -> Self {
63 |
64 | let cb = Rc::new(RefCell::new(cb));
65 |
66 | Runtime::block_on(async move {
67 | cb.borrow_mut()(self.element).await;
68 | });
69 |
70 | self
71 | }
72 | pub fn on(self, event: &str, cb: impl FnMut(ObjectRef) + 'static) -> Self {
73 |
74 | let function_ref = crate::callbacks::create_callback(cb);
75 | let code = &format!("{{}}.addEventListener('{}',{{}})", event);
76 | Js::invoke(code, &[self.element.into(), function_ref.into()]);
77 |
78 | self.callbacks.borrow_mut().push(function_ref);
79 |
80 | self
81 | }
82 | pub fn on_async>(self, event: &str, cb: impl FnMut(ObjectRef) -> Fut + 'static) -> Self {
83 |
84 | let cb = Rc::new(RefCell::new(cb));
85 | let cb_async = move |e| {
86 | let cb = cb.clone();
87 |
88 | Runtime::block_on(async move { cb.borrow_mut()(e).await; });
89 | Js::deallocate(e);
90 | };
91 |
92 | let function_ref = crate::callbacks::create_callback(cb_async);
93 | let code = &format!("{{}}.addEventListener('{}',{{}})", event);
94 | Js::invoke(code, &[self.element.into(), function_ref.into()]);
95 |
96 | self.callbacks.borrow_mut().push(function_ref);
97 |
98 | self
99 | }
100 | pub fn text(self, text: &str) -> Self {
101 |
102 | let el = Js::invoke("return document.createTextNode({})", &[text.into()]).to_ref().unwrap();
103 | Js::invoke("{}.appendChild({})", &[self.element.into(), el.into()]);
104 |
105 | self
106 | }
107 | }
108 |
109 | #[cfg(test)]
110 | mod tests {
111 |
112 | use super::*;
113 |
114 | #[test]
115 | fn test_element() {
116 |
117 | let el = El::new("div").classes(&[])
118 | .child(El::new("button").text("button 1"))
119 | .child(El::new("button").text("button 2"));
120 | assert_eq!(el, El::from(&ObjectRef::new(0)));
121 |
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/src/rust/src/invoke.rs:
--------------------------------------------------------------------------------
1 |
2 | use std::ops::Deref;
3 |
4 | #[cfg(not(test))]
5 | extern "C" {
6 | fn __invoke(c_ptr: *const u8, c_len: u32, p_ptr: *const u8, p_len: u32) -> u64;
7 | fn __deallocate(object_id: u32);
8 | }
9 |
10 | #[cfg(test)]
11 | unsafe fn __invoke(_c_ptr: *const u8, _c_len: u32, _p_ptr: *const u8, _p_len: u32) -> u64 { 0 }
12 | #[cfg(test)]
13 | unsafe fn __deallocate(_object_id: u32) {}
14 |
15 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16 | pub struct ObjectRef(u32);
17 |
18 | impl ObjectRef {
19 | pub fn new(object_id: u32) -> Self {
20 | Self (object_id)
21 | }
22 | }
23 |
24 | impl Deref for ObjectRef {
25 | type Target = u32;
26 |
27 | fn deref(&self) -> &Self::Target {
28 | &self.0
29 | }
30 | }
31 |
32 | // NOTE: Numbers in Javascript are represented by 64-bits floats
33 | // https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-ecmascript-language-types-number-type
34 | #[derive(Debug)]
35 | pub enum JsValue {
36 | Undefined,
37 | Null,
38 | Number(f64),
39 | BigInt(i64),
40 | Str(String),
41 | Bool(bool),
42 | Ref(ObjectRef),
43 | Buffer(Vec),
44 | }
45 |
46 | impl From<&str> for JsValue { fn from(s: &str) -> Self { Self::Str(s.to_string()) } }
47 | impl From for JsValue { fn from(s: String) -> Self { Self::Str(s) } }
48 | impl From for JsValue { fn from(n: f64) -> Self { Self::Number(n) } }
49 | impl From for JsValue { fn from(n: f32) -> Self { Self::Number(n as f64) } }
50 | impl From for JsValue { fn from(n: u32) -> Self { Self::Number(n as f64) } }
51 | impl From for JsValue { fn from(n: u64) -> Self { Self::Number(n as f64) } }
52 | impl From for JsValue { fn from(n: i32) -> Self { Self::Number(n as f64) } }
53 | impl From for JsValue { fn from(n: i64) -> Self { Self::Number(n as f64) } }
54 | impl From for JsValue { fn from(n: bool) -> Self { Self::Bool(n) } }
55 | impl From for JsValue { fn from(s: ObjectRef) -> Self { Self::Ref(s) } }
56 | impl From<&ObjectRef> for JsValue { fn from(s: &ObjectRef) -> Self { Self::Ref(s.to_owned()) } }
57 | impl From> for JsValue { fn from(s: Vec) -> Self { Self::Buffer(s) } }
58 |
59 | // pub use JsValue::*;
60 |
61 | impl JsValue {
62 |
63 | // layout: type (1 byte) - data (var length)
64 | pub fn serialize(&self) -> Vec {
65 | match self {
66 | JsValue::Undefined => vec![0],
67 | JsValue::Null => vec![1],
68 | JsValue::Number(i) => [vec![2], i.to_le_bytes().to_vec()].concat(),
69 | JsValue::BigInt(i) => [vec![3], i.to_le_bytes().to_vec()].concat(),
70 | JsValue::Str(s) => [vec![4], (s.as_ptr() as u32).to_le_bytes().to_vec(), s.len().to_le_bytes().to_vec()].concat(),
71 | JsValue::Bool(b) => vec![if *b { 5 } else { 6 }],
72 | JsValue::Ref(i) => [vec![7], i.0.to_le_bytes().to_vec()].concat(),
73 | JsValue::Buffer(b) => [vec![8], b.to_owned()].concat(),
74 | }
75 | }
76 |
77 | pub fn deserialize(r_type: u32, r_value: u32) -> Self {
78 | match r_type {
79 | 0 => JsValue::Undefined,
80 | 1 => {
81 | let allocation_data = crate::allocations::ALLOCATIONS.with_borrow_mut(|s| s.remove(r_value as usize));
82 | let value = String::from_utf8_lossy(&allocation_data);
83 | JsValue::Number(value.parse::().unwrap() as f64)
84 | },
85 | 2 => JsValue::Ref(ObjectRef(r_value)),
86 | 3 => {
87 | JsValue::Buffer(crate::allocations::ALLOCATIONS.with_borrow_mut(|s| s.remove(r_value as usize)))
88 | },
89 | 4 => {
90 | let allocation_data = crate::allocations::ALLOCATIONS.with_borrow_mut(|s| s.remove(r_value as usize));
91 | JsValue::Str(String::from_utf8_lossy(&allocation_data).into())
92 | },
93 | 5 => JsValue::BigInt(r_value as i64),
94 | 6 => JsValue::Bool(if r_value == 1 { true } else { false }),
95 |
96 | _ => unreachable!(),
97 | }
98 | }
99 |
100 | pub fn to_bool(&self) -> Result {
101 | match &self {
102 | JsValue::Bool(b) => Ok(b.to_owned()),
103 | #[cfg(not(test))] _ => Err("Invalid type".to_string()),
104 | #[cfg(test)] _ => Ok(true),
105 | }
106 | }
107 |
108 | pub fn to_str(&self) -> Result {
109 | match &self {
110 | JsValue::Str(s) => Ok(s.to_string()),
111 | #[cfg(not(test))] _ => Err("Invalid type".to_string()),
112 | #[cfg(test)] _ => Ok("".to_string()),
113 | }
114 | }
115 |
116 | pub fn to_num(&self) -> Result {
117 | match &self {
118 | JsValue::Number(s) => Ok(s.to_owned()),
119 | #[cfg(not(test))] _ => Err("Invalid type".to_string()),
120 | #[cfg(test)] _ => Ok(0.into()),
121 | }
122 | }
123 | pub fn to_ref(&self) -> Result {
124 | match &self {
125 | JsValue::Ref(s) => Ok(s.to_owned()),
126 | #[cfg(not(test))] _ => Err("Invalid type".to_string()),
127 | #[cfg(test)] _ => Ok(ObjectRef(0)),
128 | }
129 | }
130 | pub fn to_buffer(&self) -> Result, String> {
131 | match &self {
132 | JsValue::Buffer(s) => Ok(s.to_owned()),
133 | #[cfg(not(test))] _ => Err("Invalid type".to_string()),
134 | #[cfg(test)] _ => Ok(vec![]),
135 | }
136 | }
137 |
138 | pub fn to_bigint(&self) -> Result {
139 | match &self {
140 | JsValue::BigInt(s) => Ok(s.to_owned()),
141 | #[cfg(not(test))] _ => Err("Invalid type".to_string()),
142 | #[cfg(test)] _ => Ok(0.into()),
143 | }
144 | }
145 | }
146 |
147 |
148 | pub struct Js {}
149 |
150 | impl Js {
151 | fn __code(code: &str, params: &[JsValue]) -> String {
152 |
153 | let mut code_params = String::from(code);
154 |
155 | let params_names = params.iter().enumerate().map(|(i, _)| "p".to_owned() + &i.to_string()).collect::>();
156 | for param_name in ¶ms_names {
157 | if let Some(pos) = code_params.find("{}") {
158 | code_params.replace_range(pos..pos + 2, param_name);
159 | }
160 | }
161 | format!("function({}) {{ {} }}", params_names.join(","), code_params)
162 | }
163 | pub fn invoke<'a>(code: &'a str, params: &[JsValue]) -> JsValue {
164 | let code = Self::__code(code, params);
165 | let params = params.iter().flat_map(JsValue::serialize).collect::>();
166 | let r_packed = unsafe { __invoke(code.as_ptr(), code.len() as u32, params.as_ptr(), params.len() as u32) };
167 | let r_type = (r_packed >> 32) as u32;
168 | let r_value = (r_packed & 0xFFFFFFFF) as u32;
169 | JsValue::deserialize(r_type, r_value)
170 | }
171 | pub fn deallocate(object_id: ObjectRef) {
172 | unsafe { __deallocate(*object_id) };
173 | }
174 | }
175 |
176 | #[cfg(test)]
177 | mod tests {
178 |
179 | use super::*;
180 |
181 | fn cs(s: &str) -> String {
182 | s.chars().filter(|c| !c.is_whitespace()).collect::()
183 | }
184 |
185 | #[test]
186 | fn test_params() {
187 |
188 | // undefined
189 | assert_eq!(JsValue::Undefined.serialize(), vec![0]);
190 |
191 | // null
192 | assert_eq!(JsValue::Null.serialize(), vec![1]);
193 |
194 | // number
195 | assert_eq!(JsValue::Number(42.into()).serialize(), [vec![2], 42f64.to_le_bytes().to_vec()].concat());
196 |
197 | // bigint
198 | assert_eq!(JsValue::BigInt(42).serialize(), [vec![3], 42u64.to_le_bytes().to_vec()].concat());
199 |
200 | // string
201 | let text = "hello".to_owned();
202 | let text_ptr = text.as_ptr() as u32;
203 | let text_len = text.len() as u64;
204 | let expected = [vec![4], text_ptr.to_le_bytes().to_vec(), text_len.to_le_bytes().to_vec()].concat();
205 | assert_eq!(JsValue::Str(text).serialize(), expected);
206 |
207 | // bool
208 | assert_eq!(JsValue::Bool(true).serialize(), vec![5]);
209 | assert_eq!(JsValue::Bool(false).serialize(), vec![6]);
210 |
211 | // object ref
212 | assert_eq!(JsValue::Ref(ObjectRef(42)).serialize(), [vec![7], 42u32.to_le_bytes().to_vec()].concat());
213 |
214 | // buffer
215 | assert_eq!(JsValue::Buffer(vec![1, 2, 3]).serialize(), [vec![8], vec![1, 2, 3]].concat());
216 |
217 | }
218 |
219 | #[test]
220 | fn test_code() {
221 | // prompt
222 | let code = Js::__code("return prompt({},{})", &["a".into(), "b".into()]);
223 | let expected_code = "function(p0,p1){ return prompt(p0,p1) }";
224 | assert_eq!(cs(&code), cs(&expected_code));
225 |
226 | // console log
227 | let code = Js::__code("console.log({})", &["a".into()]);
228 | let expected_code = "function(p0){ console.log(p0) }";
229 | assert_eq!(cs(&code), cs(&expected_code));
230 |
231 | // alert
232 | let code = Js::__code("alert({})", &["a".into()]);
233 | let expected_code = "function(p0){ alert(p0) }";
234 | assert_eq!(cs(&code), cs(&expected_code));
235 |
236 | // set attribute
237 | let code = Js::__code("{}.setAttribute({},{})", &[ObjectRef(0).into(), "a".into(), "b".into()]);
238 | let expected_code = "function(p0,p1,p2){ p0.setAttribute(p1, p2) }";
239 | assert_eq!(cs(&code), cs(&expected_code));
240 |
241 | // append child
242 | let code = Js::__code("{}.appendChild({})", &[ObjectRef(0).into(), ObjectRef(0).into()]);
243 | let expected_code = "function(p0,p1){ p0.appendChild(p1) }";
244 | assert_eq!(cs(&code), cs(&expected_code));
245 |
246 | // add class
247 | let code = Js::__code("{}.classList.add({})", &[ObjectRef(0).into(), "a".into()]);
248 | let expected_code = "function(p0,p1){ p0.classList.add(p1) }";
249 | assert_eq!(cs(&code), cs(&expected_code));
250 |
251 | // set property
252 | let code = Js::__code("{}[{}] = {}", &[ObjectRef(0).into(), "a".into(), "a".into()]);
253 | let expected_code = "function(p0,p1,p2){ p0[p1] = p2 }";
254 | assert_eq!(cs(&code), cs(&expected_code));
255 |
256 | // set inner html
257 | let code = Js::__code("{}.innerHTML = {}", &[ObjectRef(0).into(), "a".into()]);
258 | let expected_code = "function(p0,p1){ p0.innerHTML = p1 }";
259 | assert_eq!(cs(&code), cs(&expected_code));
260 |
261 | // history push state
262 | // NOTE: {} is parsed as the first parameter
263 | let code = Js::__code("window.history.pushState({ },{},{})", &["a".into(), "b".into()]);
264 | let expected_code = "function(p0,p1){ window.history.pushState({ },p0,p1) }";
265 | assert_eq!(cs(&code), cs(&expected_code));
266 |
267 | // location pathname
268 | let code = Js::__code("return window.location.pathname", &[]);
269 | let expected_code = "function() { return window.location.pathname }";
270 | assert_eq!(cs(&code), cs(&expected_code));
271 |
272 | // get property string
273 | let code = Js::__code("return {}[{}]", &[ObjectRef(0).into(), "b".into()]);
274 | let expected_code = "function(p0,p1){ return p0[p1] }";
275 | assert_eq!(cs(&code), cs(&expected_code));
276 |
277 | // prompt dialog
278 | let code = Js::__code("return prompt({},{})", &["a".into(), "b".into()]);
279 | let expected_code = "function(p0,p1){ return prompt(p0,p1) }";
280 | assert_eq!(cs(&code), cs(&expected_code));
281 |
282 | // random number
283 | let code = Js::__code("return Math.random()", &[]);
284 | let expected_code = "function(){ return Math.random() }";
285 | assert_eq!(cs(&code), cs(&expected_code));
286 |
287 | // get property
288 | let code = Js::__code("return {}[{}]", &[ObjectRef(0).into(), "a".into()]);
289 | let expected_code = "function(p0,p1){ return p0[p1] }";
290 | assert_eq!(cs(&code), cs(&expected_code));
291 |
292 | // query selector
293 | let code = Js::__code("return document.querySelector({})", &["a".into()]);
294 | let expected_code = "function(p0){ return document.querySelector(p0) }";
295 | assert_eq!(cs(&code), cs(&expected_code));
296 |
297 | // create element
298 | let code = Js::__code("return document.createElement({})", &["a".into()]);
299 | let expected_code = "function(p0){ return document.createElement(p0) }";
300 | assert_eq!(cs(&code), cs(&expected_code));
301 |
302 | // create text node
303 | let code = Js::__code("return document.createTextNode({})", &["a".into()]);
304 | let expected_code = "function(p0){ return document.createTextNode(p0) }";
305 | assert_eq!(cs(&code), cs(&expected_code));
306 |
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/src/rust/src/lib.rs:
--------------------------------------------------------------------------------
1 |
2 | pub mod callbacks;
3 | pub mod allocations;
4 | pub mod runtime;
5 | pub mod invoke;
6 |
7 | pub mod signals;
8 | pub mod element;
9 | pub mod router;
10 |
11 | // Use: crate::println!("{}", 42);
12 | #[macro_export]
13 | macro_rules! println {
14 | ($fmt:expr) => { Js::invoke("console.log({})", &[format!($fmt).into()]); };
15 | ($fmt:expr, $($arg:tt)*) => { Js::invoke("console.log({})", &[format!($fmt, $($arg)*).into()]); };
16 | }
17 |
18 | // Web browser specification
19 | // https://github.com/w3c/webref
20 |
21 | // Count LOC (excluding tests)
22 | // ```
23 | // git ls-files ':(glob)src/rust/src/**' | xargs cat | sed '/#\[test\]/,/}/d' | wc -l
24 | // ```
25 |
26 | // List files
27 | // ```
28 | // git ls-files ':(glob)src/rust/src/**' | xargs wc -l | sort -r
29 | // ```
30 |
--------------------------------------------------------------------------------
/src/rust/src/router.rs:
--------------------------------------------------------------------------------
1 |
2 |
3 | use std::collections::HashMap;
4 |
5 | use crate::invoke::{Js, ObjectRef};
6 | use crate::element::El;
7 |
8 | #[derive(Debug, Clone)]
9 | pub struct Page { pub path: String, pub element: El, pub title: Option }
10 |
11 | impl Page {
12 | pub fn new(path: &str, element: El) -> Self {
13 | Self { path: path.to_owned(), element, title: None }
14 | }
15 | pub fn title(mut self, title: String) -> Self {
16 | self.title = Some(title);
17 | self
18 | }
19 | }
20 |
21 | #[derive(Debug, Default)]
22 | pub struct Router { pub root: Option, pub pages: HashMap:: }
23 |
24 | impl Router {
25 | pub fn new(root: &str, pages: &[Page]) -> Self {
26 | let body = Js::invoke("return document.querySelector({})", &[root.into()]).to_ref().unwrap();
27 | let pathname = Js::invoke("return window.location.pathname", &[]).to_str().unwrap();
28 | let page = pages.iter().find(|&s| *s.path == pathname).unwrap_or(&pages[0]);
29 | page.element.mount(&body);
30 |
31 | let mut default_page = pages.first().cloned().unwrap();
32 | default_page.path = "/".to_owned();
33 |
34 | let mut pages = pages.iter().map(|p| (p.path.clone(), p.to_owned())).collect::>();
35 | pages.push((default_page.path.clone(), default_page.to_owned()));
36 | Self { pages: HashMap::from_iter(pages), root: Some(body) }
37 | }
38 | pub fn navigate(&self, route: &str) {
39 |
40 | // unmount page
41 | let pathname = Js::invoke("return window.location.pathname", &[]).to_str().unwrap();
42 | let (_, current_page) = self.pages.iter().find(|&(s, _)| *s == pathname).unwrap();
43 | current_page.element.unmount();
44 |
45 | // set html
46 | let body = self.root.as_ref().unwrap();
47 | Js::invoke("{}.innerHTML = {}", &[body.into(), "".into()]);
48 |
49 | // mount new page
50 | let page = self.pages.get(route).unwrap();
51 | page.element.mount(&body);
52 |
53 | // push state
54 | let page_str = page.title.to_owned().unwrap_or_default();
55 | Js::invoke("window.history.pushState({ },{},{})", &[page_str.into(), route.into()]);
56 |
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/rust/src/runtime.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | cell::RefCell,
3 | future::Future,
4 | mem::ManuallyDrop,
5 | pin::Pin,
6 | rc::Rc,
7 | task::{Context, Poll, RawWaker, RawWakerVTable, Waker}
8 | };
9 |
10 | use crate::callbacks::{create_async_callback, create_callback};
11 | use crate::invoke::{Js, JsValue, ObjectRef};
12 |
13 | pub enum FutureState { Init, Pending(Waker), Ready(ObjectRef) }
14 | pub struct FutureTask { pub state: Rc> }
15 |
16 | pub struct Runtime {}
17 |
18 | type FutureRc = Rc>>>>;
19 |
20 | impl Future for FutureTask {
21 | type Output = ObjectRef;
22 |
23 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll {
24 |
25 | let mut future_state = self.state.borrow_mut();
26 | match &*future_state {
27 | FutureState::Ready(result) => {
28 | Poll::Ready(result.to_owned())
29 | },
30 | _ => {
31 | *future_state = FutureState::Pending(cx.waker().to_owned());
32 | Poll::Pending
33 | }
34 | }
35 | }
36 | }
37 |
38 | impl Drop for FutureTask {
39 | fn drop(&mut self) {
40 | match *self.state.borrow_mut() {
41 | FutureState::Ready(id) => Js::deallocate(id),
42 | _ => {}
43 | }
44 | }
45 | }
46 |
47 | impl Runtime {
48 |
49 | fn poll(future_rc: &FutureRc) {
50 | let waker = Self::waker(&future_rc);
51 | let waker_forget = ManuallyDrop::new(waker);
52 | let context = &mut Context::from_waker(&waker_forget);
53 | let _poll = future_rc.borrow_mut().as_mut().poll(context);
54 | }
55 |
56 | // https://rust-lang.github.io/async-book/02_execution/03_wakeups.html
57 | fn waker(future_rc: &FutureRc) -> Waker {
58 |
59 | fn clone_fn(ptr: *const ()) -> RawWaker {
60 | let future = unsafe { FutureRc::::from_raw(ptr as *const _) };
61 | let _ = ManuallyDrop::new(future).clone();
62 | RawWaker::new(ptr, waker_vtable::())
63 | }
64 | fn wake_fn(ptr: *const ()) {
65 | let future = unsafe { FutureRc::::from_raw(ptr as *const _) };
66 | let function_ref = create_callback(move |e| { Runtime::poll(&future); Js::deallocate(e); });
67 | Js::invoke("window.setTimeout({},0)", &[function_ref.into()]);
68 | }
69 | fn drop_fn(ptr: *const ()) {
70 | let future = unsafe { FutureRc::::from_raw(ptr as *const _) };
71 | drop(future);
72 | }
73 | fn waker_vtable() -> &'static RawWakerVTable {
74 | &RawWakerVTable::new(clone_fn::, wake_fn::, wake_fn::, drop_fn::)
75 | }
76 | let waker = RawWaker::new(&**future_rc as *const _ as *const (), waker_vtable::());
77 | unsafe { Waker::from_raw(waker) }
78 | }
79 |
80 | pub fn block_on(future: impl Future